Skip to content

Commit cac81e9

Browse files
committed
Gen api types
1 parent e192998 commit cac81e9

5 files changed

Lines changed: 297 additions & 3 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 OpenWorkers
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

bun.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
{
22
"name": "openworkers-api",
33
"version": "1.0.0",
4+
"license": "MIT",
45
"module": "src/index.ts",
56
"type": "module",
67
"private": true,
78
"scripts": {
89
"dev": "bun --hot src/index.ts",
910
"start": "bun src/index.ts",
1011
"compile": "bun build --compile --minify --sourcemap server.ts --outfile dist/openworkers-api",
11-
"test": "bun test"
12+
"test": "bun test",
13+
"generate-types": "bun scripts/generate-types.ts"
1214
},
1315
"dependencies": {
1416
"@openworkers/croner-wasm": "^0.3.0",
1517
"hono": "^4.10.6",
1618
"zod": "^4.1.12"
1719
},
1820
"devDependencies": {
19-
"@types/bun": "latest"
21+
"@types/bun": "latest",
22+
"prettier": "^3.6.2",
23+
"zod-to-ts": "^2.0.0"
2024
},
2125
"peerDependencies": {
2226
"typescript": "^5"

scripts/generate-types.ts

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { zodToTs } from "zod-to-ts";
2+
import ts from "typescript";
3+
import prettier from "prettier";
4+
5+
// Helper to print TypeScript node as string
6+
function printNode(node: ts.Node): string {
7+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
8+
const sourceFile = ts.createSourceFile(
9+
"temp.ts",
10+
"",
11+
ts.ScriptTarget.Latest,
12+
false,
13+
ts.ScriptKind.TS
14+
);
15+
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
16+
}
17+
18+
// Import all schemas
19+
import {
20+
ResourceSchema,
21+
TimestampsSchema,
22+
} from "../src/types/schemas/base.schema";
23+
24+
import { LoginResponseSchema } from "../src/types/schemas/auth.schema";
25+
26+
import {
27+
SelfSchema,
28+
ResourceLimitsSchema,
29+
} from "../src/types/schemas/user.schema";
30+
31+
import {
32+
EnvironmentSchema,
33+
EnvironmentCreateInputSchema,
34+
EnvironmentUpdateInputSchema,
35+
EnvironmentValueSchema,
36+
EnvironmentValueUpdateInputSchema,
37+
} from "../src/types/schemas/environment.schema";
38+
39+
import {
40+
CronSchema,
41+
CronCreateInputSchema,
42+
CronUpdateInputSchema,
43+
} from "../src/types/schemas/cron.schema";
44+
45+
import {
46+
DomainSchema,
47+
DomainCreateInputSchema,
48+
} from "../src/types/schemas/domain.schema";
49+
50+
import {
51+
WorkerSchema,
52+
WorkerCreateInputSchema,
53+
WorkerUpdateInputSchema,
54+
WorkerLanguageSchema,
55+
} from "../src/types/schemas/worker.schema";
56+
57+
// Schema definitions to generate
58+
const schemas = [
59+
// Base schemas
60+
{ schema: ResourceSchema, name: "Resource" },
61+
{ schema: TimestampsSchema, name: "Timestamps" },
62+
63+
// Auth
64+
{ schema: LoginResponseSchema, name: "LoginResponse" },
65+
66+
// User
67+
{ schema: SelfSchema, name: "Self" },
68+
{ schema: ResourceLimitsSchema, name: "ResourceLimits" },
69+
70+
// Environment
71+
{ schema: EnvironmentSchema, name: "Environment" },
72+
{ schema: EnvironmentCreateInputSchema, name: "EnvironmentCreateInput" },
73+
{ schema: EnvironmentUpdateInputSchema, name: "EnvironmentUpdateInput" },
74+
{ schema: EnvironmentValueSchema, name: "EnvironmentValue" },
75+
{
76+
schema: EnvironmentValueUpdateInputSchema,
77+
name: "EnvironmentValueUpdateInput",
78+
},
79+
80+
// Cron
81+
{ schema: CronSchema, name: "Cron" },
82+
{ schema: CronCreateInputSchema, name: "CronCreateInput" },
83+
{ schema: CronUpdateInputSchema, name: "CronUpdateInput" },
84+
85+
// Domain
86+
{ schema: DomainSchema, name: "Domain" },
87+
{ schema: DomainCreateInputSchema, name: "DomainCreateInput" },
88+
89+
// Worker
90+
{ schema: WorkerSchema, name: "Worker" },
91+
{ schema: WorkerCreateInputSchema, name: "WorkerCreateInput" },
92+
{ schema: WorkerUpdateInputSchema, name: "WorkerUpdateInput" },
93+
{ schema: WorkerLanguageSchema, name: "WorkerLanguage" },
94+
];
95+
96+
async function generateTypes() {
97+
console.log("🔄 Generating TypeScript types from Zod schemas...");
98+
99+
// Clean dist directory
100+
const distDir = `${import.meta.dir}/../dist`;
101+
console.log(" 🧹 Cleaning dist directory...");
102+
try {
103+
await Bun.$`rm -rf ${distDir}`;
104+
} catch (error) {
105+
// Ignore error if dist doesn't exist
106+
}
107+
108+
// FIRST PASS: Generate all types and build a map
109+
const typesMap = new Map<string, string>();
110+
let idCounter = 0;
111+
const auxiliaryTypeStore = {
112+
nextId: () => `T${idCounter++}`,
113+
definitions: new Map(),
114+
};
115+
116+
console.log(" 📋 First pass: building types map...");
117+
for (const { schema, name } of schemas) {
118+
try {
119+
const { node } = zodToTs(schema, { auxiliaryTypeStore });
120+
let typeStr = printNode(node);
121+
// Normalize whitespace for better matching
122+
typeStr = typeStr.replace(/\s+/g, " ").trim();
123+
typesMap.set(typeStr, name);
124+
} catch (error) {
125+
console.error(`❌ Failed to generate type for ${name}:`, error);
126+
}
127+
}
128+
129+
// SECOND PASS: Replace nested types with references
130+
console.log(" 🔄 Second pass: replacing nested types...");
131+
const finalTypesMap = new Map<string, string>();
132+
133+
// Sort types by length (longest first) for better matching
134+
const sortedTypes = Array.from(typesMap.entries()).sort(
135+
([a], [b]) => b.length - a.length
136+
);
137+
138+
for (const [typeStr, name] of typesMap) {
139+
let updatedTypeStr = typeStr;
140+
141+
// Replace all occurrences of other types with their names (with I prefix)
142+
for (const [otherTypeStr, otherName] of sortedTypes) {
143+
if (otherName !== name && updatedTypeStr.includes(otherTypeStr)) {
144+
// Use a more precise replacement to avoid partial matches
145+
updatedTypeStr = updatedTypeStr.replaceAll(
146+
otherTypeStr,
147+
`I${otherName}`
148+
);
149+
}
150+
}
151+
152+
finalTypesMap.set(name, updatedTypeStr);
153+
}
154+
155+
// THIRD PASS: Recursive replacement until no more changes
156+
console.log(" 🔁 Third pass: recursive replacement...");
157+
let changed = true;
158+
let iterations = 0;
159+
const maxIterations = 10;
160+
161+
while (changed && iterations < maxIterations) {
162+
changed = false;
163+
iterations++;
164+
165+
for (const [name, typeStr] of finalTypesMap) {
166+
let updatedTypeStr = typeStr;
167+
168+
for (const [otherTypeStr, otherName] of sortedTypes) {
169+
if (otherName !== name && updatedTypeStr.includes(otherTypeStr)) {
170+
const newStr = updatedTypeStr.replaceAll(
171+
otherTypeStr,
172+
`I${otherName}`
173+
);
174+
if (newStr !== updatedTypeStr) {
175+
updatedTypeStr = newStr;
176+
changed = true;
177+
}
178+
}
179+
}
180+
181+
if (updatedTypeStr !== typeStr) {
182+
finalTypesMap.set(name, updatedTypeStr);
183+
}
184+
}
185+
}
186+
187+
console.log(` ✓ Completed in ${iterations} iteration(s)`);
188+
189+
// Generate output
190+
let output = `/**
191+
* Auto-generated TypeScript types from Zod schemas
192+
* DO NOT EDIT THIS FILE MANUALLY
193+
* Generated on: ${new Date().toISOString()}
194+
*/
195+
196+
`;
197+
198+
// Add global types
199+
output += `// Global types
200+
export type hex = string;
201+
export type uuid = string;
202+
export type timestamp = number;
203+
export type Dictionary<T> = Record<string, T>;
204+
205+
`;
206+
207+
// Add all final types with "I" prefix
208+
for (const { name } of schemas) {
209+
const typeStr = finalTypesMap.get(name);
210+
if (typeStr) {
211+
output += `export type I${name} = ${typeStr};\n\n`;
212+
}
213+
}
214+
215+
// Format with prettier
216+
console.log(" 🎨 Formatting with prettier...");
217+
const formatted = await prettier.format(output, {
218+
parser: "typescript",
219+
semi: true,
220+
singleQuote: true,
221+
trailingComma: "all",
222+
});
223+
224+
// Write types file
225+
await Bun.write(`${distDir}/types.d.ts`, formatted);
226+
227+
// Generate package.json for npm publish
228+
const packageJson = {
229+
name: "@openworkers/api-types",
230+
version: "1.0.0",
231+
license: "MIT",
232+
type: "module",
233+
private: false,
234+
main: "./types.d.ts",
235+
types: "./types.d.ts",
236+
exports: {
237+
".": "./types.d.ts",
238+
},
239+
description: "TypeScript types for OpenWorkers API",
240+
keywords: ["openworkers", "types", "typescript"],
241+
repository: {
242+
type: "git",
243+
url: "https://github.com/openworkers/openworkers-api",
244+
},
245+
publishConfig: {
246+
access: "public",
247+
},
248+
};
249+
250+
await Bun.write(
251+
`${distDir}/package.json`,
252+
JSON.stringify(packageJson, null, 2)
253+
);
254+
255+
// Copy LICENSE
256+
const license = await Bun.file(`${import.meta.dir}/../LICENSE`).text();
257+
await Bun.write(`${distDir}/LICENSE`, license);
258+
259+
console.log(`✅ Types package generated successfully`);
260+
console.log(` 📦 Run "cd dist && npm publish" to publish`);
261+
}
262+
263+
generateTypes().catch(console.error);

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import environments from "./routes/environments";
1010
import domains from "./routes/domains";
1111
import pkg from "../package.json";
1212

13-
const app = new Hono();
13+
export const app = new Hono();
1414

1515
// Global middlewares
1616
app.use("*", logger());

0 commit comments

Comments
 (0)