export-runtime 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/generate-types.mjs +223 -0
- package/entry.js +2 -1
- package/handler.js +89 -1
- package/package.json +8 -3
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseSync } from "oxc-parser";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
|
|
9
|
+
// Read wrangler.toml to find user module path
|
|
10
|
+
const wranglerPath = path.join(cwd, "wrangler.toml");
|
|
11
|
+
if (!fs.existsSync(wranglerPath)) {
|
|
12
|
+
console.error("wrangler.toml not found in", cwd);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const wranglerContent = fs.readFileSync(wranglerPath, "utf8");
|
|
16
|
+
const aliasMatch = wranglerContent.match(/"__USER_MODULE__"\s*=\s*"([^"]+)"/);
|
|
17
|
+
if (!aliasMatch) {
|
|
18
|
+
console.error('Could not find __USER_MODULE__ alias in wrangler.toml');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const userModulePath = path.resolve(cwd, aliasMatch[1]);
|
|
23
|
+
if (!fs.existsSync(userModulePath)) {
|
|
24
|
+
console.error("User module not found:", userModulePath);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const source = fs.readFileSync(userModulePath, "utf8");
|
|
29
|
+
const isTS = userModulePath.endsWith(".ts") || userModulePath.endsWith(".tsx");
|
|
30
|
+
const fileName = path.basename(userModulePath);
|
|
31
|
+
const result = parseSync(fileName, source, { sourceType: "module" });
|
|
32
|
+
const program = result.program;
|
|
33
|
+
|
|
34
|
+
// --- Type extraction helpers ---
|
|
35
|
+
|
|
36
|
+
function extractType(node) {
|
|
37
|
+
if (!node) return "any";
|
|
38
|
+
const ta = node.type === "TSTypeAnnotation" ? node.typeAnnotation : node;
|
|
39
|
+
switch (ta.type) {
|
|
40
|
+
case "TSStringKeyword": return "string";
|
|
41
|
+
case "TSNumberKeyword": return "number";
|
|
42
|
+
case "TSBooleanKeyword": return "boolean";
|
|
43
|
+
case "TSVoidKeyword": return "void";
|
|
44
|
+
case "TSAnyKeyword": return "any";
|
|
45
|
+
case "TSNullKeyword": return "null";
|
|
46
|
+
case "TSUndefinedKeyword": return "undefined";
|
|
47
|
+
case "TSNeverKeyword": return "never";
|
|
48
|
+
case "TSUnknownKeyword": return "unknown";
|
|
49
|
+
case "TSBigIntKeyword": return "bigint";
|
|
50
|
+
case "TSSymbolKeyword": return "symbol";
|
|
51
|
+
case "TSObjectKeyword": return "object";
|
|
52
|
+
case "TSArrayType":
|
|
53
|
+
return `${extractType(ta.elementType)}[]`;
|
|
54
|
+
case "TSTupleType":
|
|
55
|
+
return `[${(ta.elementTypes || []).map(e => extractType(e)).join(", ")}]`;
|
|
56
|
+
case "TSUnionType":
|
|
57
|
+
return ta.types.map(t => extractType(t)).join(" | ");
|
|
58
|
+
case "TSIntersectionType":
|
|
59
|
+
return ta.types.map(t => extractType(t)).join(" & ");
|
|
60
|
+
case "TSLiteralType": {
|
|
61
|
+
const lit = ta.literal;
|
|
62
|
+
if (lit.type === "StringLiteral") return JSON.stringify(lit.value);
|
|
63
|
+
if (lit.type === "NumericLiteral") return String(lit.value);
|
|
64
|
+
if (lit.type === "BooleanLiteral") return String(lit.value);
|
|
65
|
+
if (lit.type === "UnaryExpression") return `-${lit.argument.value}`;
|
|
66
|
+
return "any";
|
|
67
|
+
}
|
|
68
|
+
case "TSTypeReference": {
|
|
69
|
+
const name = ta.typeName?.name || ta.typeName?.right?.name || "any";
|
|
70
|
+
const typeArgs = ta.typeArguments || ta.typeParameters;
|
|
71
|
+
if (typeArgs?.params?.length) {
|
|
72
|
+
const args = typeArgs.params.map(p => extractType(p)).join(", ");
|
|
73
|
+
return `${name}<${args}>`;
|
|
74
|
+
}
|
|
75
|
+
return name;
|
|
76
|
+
}
|
|
77
|
+
case "TSFunctionType": {
|
|
78
|
+
const params = extractParams(ta.params);
|
|
79
|
+
const ret = ta.returnType ? extractType(ta.returnType) : "any";
|
|
80
|
+
return `(${params.join(", ")}) => ${ret}`;
|
|
81
|
+
}
|
|
82
|
+
case "TSTypeLiteral": {
|
|
83
|
+
const members = (ta.members || []).map(m => {
|
|
84
|
+
if (m.type === "TSPropertySignature") {
|
|
85
|
+
const key = m.key?.name || m.key?.value;
|
|
86
|
+
const type = m.typeAnnotation ? extractType(m.typeAnnotation) : "any";
|
|
87
|
+
const opt = m.optional ? "?" : "";
|
|
88
|
+
return `${key}${opt}: ${type}`;
|
|
89
|
+
}
|
|
90
|
+
return "";
|
|
91
|
+
}).filter(Boolean);
|
|
92
|
+
return `{ ${members.join("; ")} }`;
|
|
93
|
+
}
|
|
94
|
+
case "TSTypeAnnotation":
|
|
95
|
+
return extractType(ta.typeAnnotation);
|
|
96
|
+
default:
|
|
97
|
+
return "any";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function extractParams(params) {
|
|
102
|
+
return params.map(p => {
|
|
103
|
+
if (p.type === "AssignmentPattern") {
|
|
104
|
+
const name = p.left?.name || "arg";
|
|
105
|
+
const type = p.left?.typeAnnotation ? extractType(p.left.typeAnnotation) : "any";
|
|
106
|
+
return `${name}?: ${type}`;
|
|
107
|
+
}
|
|
108
|
+
const name = p.name || p.argument?.name || "arg";
|
|
109
|
+
const type = p.typeAnnotation ? extractType(p.typeAnnotation) : "any";
|
|
110
|
+
const opt = p.optional ? "?" : "";
|
|
111
|
+
const rest = p.type === "RestElement" ? "..." : "";
|
|
112
|
+
return `${rest}${name}${opt}: ${type}`;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Wrap return type: all functions become async over the network
|
|
117
|
+
function wrapReturnType(returnType, isAsync, isGenerator) {
|
|
118
|
+
if (isGenerator) {
|
|
119
|
+
// async generator → AsyncIterable<YieldType>
|
|
120
|
+
// extract inner type from AsyncGenerator<T> if present
|
|
121
|
+
if (returnType.startsWith("AsyncGenerator")) {
|
|
122
|
+
const inner = returnType.match(/^AsyncGenerator<(.+?)(?:,.*)?>/);
|
|
123
|
+
return `Promise<AsyncIterable<${inner ? inner[1] : "any"}>>`;
|
|
124
|
+
}
|
|
125
|
+
return `Promise<AsyncIterable<${returnType === "any" ? "any" : returnType}>>`;
|
|
126
|
+
}
|
|
127
|
+
// Already Promise<T> → keep as-is
|
|
128
|
+
if (returnType.startsWith("Promise<")) return returnType;
|
|
129
|
+
// ReadableStream<T> → Promise<ReadableStream<T>>
|
|
130
|
+
if (returnType.startsWith("ReadableStream")) return `Promise<${returnType}>`;
|
|
131
|
+
// Wrap in Promise
|
|
132
|
+
return `Promise<${returnType}>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Generate .d.ts ---
|
|
136
|
+
|
|
137
|
+
const lines = [
|
|
138
|
+
"// Auto-generated type definitions (oxc-parser)",
|
|
139
|
+
"// All functions are async over the network",
|
|
140
|
+
"",
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const node of program.body) {
|
|
144
|
+
if (node.type !== "ExportNamedDeclaration" || !node.declaration) continue;
|
|
145
|
+
const decl = node.declaration;
|
|
146
|
+
|
|
147
|
+
if (decl.type === "FunctionDeclaration") {
|
|
148
|
+
const name = decl.id.name;
|
|
149
|
+
const params = extractParams(decl.params);
|
|
150
|
+
const rawRet = decl.returnType ? extractType(decl.returnType) : "any";
|
|
151
|
+
const ret = wrapReturnType(rawRet, decl.async, decl.generator);
|
|
152
|
+
lines.push(`export declare function ${name}(${params.join(", ")}): ${ret};`);
|
|
153
|
+
lines.push("");
|
|
154
|
+
|
|
155
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
156
|
+
const name = decl.id.name;
|
|
157
|
+
lines.push(`export declare class ${name} {`);
|
|
158
|
+
for (const member of decl.body.body) {
|
|
159
|
+
if (member.type === "MethodDefinition") {
|
|
160
|
+
const mName = member.key.name || member.key.value;
|
|
161
|
+
if (member.kind === "constructor") {
|
|
162
|
+
const params = extractParams(member.value.params);
|
|
163
|
+
lines.push(` constructor(${params.join(", ")});`);
|
|
164
|
+
} else {
|
|
165
|
+
const params = extractParams(member.value.params);
|
|
166
|
+
const rawRet = member.value.returnType ? extractType(member.value.returnType) : "any";
|
|
167
|
+
const ret = wrapReturnType(rawRet, member.value.async, member.value.generator);
|
|
168
|
+
lines.push(` ${mName}(${params.join(", ")}): ${ret};`);
|
|
169
|
+
}
|
|
170
|
+
} else if (member.type === "PropertyDefinition") {
|
|
171
|
+
// Skip private members
|
|
172
|
+
if (member.accessibility === "private") continue;
|
|
173
|
+
const mName = member.key.name;
|
|
174
|
+
const type = member.typeAnnotation ? extractType(member.typeAnnotation) : "any";
|
|
175
|
+
lines.push(` ${mName}: ${type};`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
lines.push(` [Symbol.dispose](): Promise<void>;`);
|
|
179
|
+
lines.push(` "[release]"(): Promise<void>;`);
|
|
180
|
+
lines.push(`}`);
|
|
181
|
+
lines.push("");
|
|
182
|
+
|
|
183
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
184
|
+
for (const d of decl.declarations) {
|
|
185
|
+
const name = d.id.name;
|
|
186
|
+
if (d.init?.type === "ObjectExpression") {
|
|
187
|
+
lines.push(`export declare const ${name}: {`);
|
|
188
|
+
for (const prop of d.init.properties) {
|
|
189
|
+
if (prop.type === "SpreadElement") continue;
|
|
190
|
+
const key = prop.key?.name || prop.key?.value;
|
|
191
|
+
if (prop.value?.type === "FunctionExpression" || prop.value?.type === "ArrowFunctionExpression") {
|
|
192
|
+
const params = extractParams(prop.value.params);
|
|
193
|
+
const rawRet = prop.value.returnType ? extractType(prop.value.returnType) : "any";
|
|
194
|
+
const ret = wrapReturnType(rawRet, prop.value.async, prop.value.generator);
|
|
195
|
+
lines.push(` ${key}(${params.join(", ")}): ${ret};`);
|
|
196
|
+
} else {
|
|
197
|
+
const type = d.id.typeAnnotation ? "any" : "any";
|
|
198
|
+
lines.push(` ${key}: any;`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
lines.push(`};`);
|
|
202
|
+
lines.push("");
|
|
203
|
+
} else {
|
|
204
|
+
const type = d.id.typeAnnotation ? extractType(d.id.typeAnnotation) : "any";
|
|
205
|
+
lines.push(`export declare const ${name}: ${type};`);
|
|
206
|
+
lines.push("");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Add createUploadStream helper type
|
|
213
|
+
lines.push("export declare function createUploadStream(): Promise<{");
|
|
214
|
+
lines.push(" stream: WritableStream<any>;");
|
|
215
|
+
lines.push(" writableId: number;");
|
|
216
|
+
lines.push("}>;");
|
|
217
|
+
|
|
218
|
+
const typeDefinitions = lines.join("\n");
|
|
219
|
+
|
|
220
|
+
// Write as a JS module that exports the string
|
|
221
|
+
const outPath = path.join(cwd, ".export-types.js");
|
|
222
|
+
fs.writeFileSync(outPath, `export default ${JSON.stringify(typeDefinitions)};\n`);
|
|
223
|
+
console.log("Generated type definitions →", outPath);
|
package/entry.js
CHANGED
package/handler.js
CHANGED
|
@@ -19,7 +19,79 @@ const isReadableStream = (value) =>
|
|
|
19
19
|
const isClass = (fn) =>
|
|
20
20
|
typeof fn === "function" && /^class\s/.test(Function.prototype.toString.call(fn));
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Generate TypeScript type definitions from exports
|
|
23
|
+
const generateTypeDefinitions = (exports, exportKeys) => {
|
|
24
|
+
const lines = [
|
|
25
|
+
"// Auto-generated type definitions",
|
|
26
|
+
"// All functions are async over the network",
|
|
27
|
+
"",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const generateType = (value, name, indent = "") => {
|
|
31
|
+
if (isClass(value)) {
|
|
32
|
+
// Extract class method names
|
|
33
|
+
const proto = value.prototype;
|
|
34
|
+
const methodNames = Object.getOwnPropertyNames(proto).filter(
|
|
35
|
+
(n) => n !== "constructor" && typeof proto[n] === "function"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
lines.push(`${indent}export declare class ${name} {`);
|
|
39
|
+
lines.push(`${indent} constructor(...args: any[]);`);
|
|
40
|
+
for (const method of methodNames) {
|
|
41
|
+
lines.push(`${indent} ${method}(...args: any[]): Promise<any>;`);
|
|
42
|
+
}
|
|
43
|
+
lines.push(`${indent} [Symbol.dispose](): Promise<void>;`);
|
|
44
|
+
lines.push(`${indent} "[release]"(): Promise<void>;`);
|
|
45
|
+
lines.push(`${indent}}`);
|
|
46
|
+
} else if (typeof value === "function") {
|
|
47
|
+
// Check if it's an async generator
|
|
48
|
+
const fnStr = Function.prototype.toString.call(value);
|
|
49
|
+
if (fnStr.startsWith("async function*") || fnStr.includes("async *")) {
|
|
50
|
+
lines.push(
|
|
51
|
+
`${indent}export declare function ${name}(...args: any[]): Promise<AsyncIterable<any>>;`
|
|
52
|
+
);
|
|
53
|
+
} else if (fnStr.includes("ReadableStream")) {
|
|
54
|
+
lines.push(
|
|
55
|
+
`${indent}export declare function ${name}(...args: any[]): Promise<ReadableStream<any>>;`
|
|
56
|
+
);
|
|
57
|
+
} else {
|
|
58
|
+
lines.push(
|
|
59
|
+
`${indent}export declare function ${name}(...args: any[]): Promise<any>;`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
} else if (typeof value === "object" && value !== null) {
|
|
63
|
+
// Nested object with methods
|
|
64
|
+
const keys = Object.keys(value);
|
|
65
|
+
lines.push(`${indent}export declare const ${name}: {`);
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
const v = value[key];
|
|
68
|
+
if (typeof v === "function") {
|
|
69
|
+
lines.push(`${indent} ${key}(...args: any[]): Promise<any>;`);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(`${indent} ${key}: any;`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
lines.push(`${indent}};`);
|
|
75
|
+
} else {
|
|
76
|
+
lines.push(`${indent}export declare const ${name}: any;`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const key of exportKeys) {
|
|
81
|
+
generateType(exports[key], key);
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add createUploadStream helper type
|
|
86
|
+
lines.push("export declare function createUploadStream(): Promise<{");
|
|
87
|
+
lines.push(" stream: WritableStream<any>;");
|
|
88
|
+
lines.push(" writableId: number;");
|
|
89
|
+
lines.push("}>;");
|
|
90
|
+
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const createHandler = (exports, generatedTypes) => {
|
|
23
95
|
const exportKeys = Object.keys(exports);
|
|
24
96
|
const iteratorStore = new Map();
|
|
25
97
|
const instanceStore = new Map();
|
|
@@ -280,6 +352,18 @@ export const createHandler = (exports) => {
|
|
|
280
352
|
const wsProtocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
281
353
|
const wsUrl = `${wsProtocol}//${url.host}${url.pathname}`;
|
|
282
354
|
|
|
355
|
+
// Serve TypeScript type definitions
|
|
356
|
+
if (url.searchParams.has("types") || url.pathname.endsWith(".d.ts")) {
|
|
357
|
+
const typeDefinitions = generatedTypes || generateTypeDefinitions(exports, exportKeys);
|
|
358
|
+
return new Response(typeDefinitions, {
|
|
359
|
+
headers: {
|
|
360
|
+
"Content-Type": "application/typescript; charset=utf-8",
|
|
361
|
+
"Access-Control-Allow-Origin": "*",
|
|
362
|
+
"Cache-Control": "no-cache",
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
283
367
|
// Generate named exports
|
|
284
368
|
const namedExports = exportKeys
|
|
285
369
|
.map((key) => `export const ${key} = createProxy([${JSON.stringify(key)}]);`)
|
|
@@ -291,11 +375,15 @@ export const createHandler = (exports) => {
|
|
|
291
375
|
.replace("__DEVALUE_PARSE__", DEVALUE_PARSE)
|
|
292
376
|
.replace("__NAMED_EXPORTS__", namedExports);
|
|
293
377
|
|
|
378
|
+
// Build types URL for X-TypeScript-Types header
|
|
379
|
+
const typesUrl = `${url.protocol}//${url.host}${url.pathname}?types`;
|
|
380
|
+
|
|
294
381
|
return new Response(clientCode, {
|
|
295
382
|
headers: {
|
|
296
383
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
297
384
|
"Access-Control-Allow-Origin": "*",
|
|
298
385
|
"Cache-Control": "no-cache",
|
|
386
|
+
"X-TypeScript-Types": typesUrl,
|
|
299
387
|
},
|
|
300
388
|
});
|
|
301
389
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "export-runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Cloudflare Workers ESM Export Framework Runtime",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -22,12 +22,17 @@
|
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./entry.js"
|
|
24
24
|
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"generate-export-types": "./bin/generate-types.mjs"
|
|
27
|
+
},
|
|
25
28
|
"files": [
|
|
26
29
|
"entry.js",
|
|
27
30
|
"handler.js",
|
|
28
|
-
"client.js"
|
|
31
|
+
"client.js",
|
|
32
|
+
"bin/generate-types.mjs"
|
|
29
33
|
],
|
|
30
34
|
"dependencies": {
|
|
31
|
-
"devalue": "^5.1.1"
|
|
35
|
+
"devalue": "^5.1.1",
|
|
36
|
+
"oxc-parser": "^0.121.0"
|
|
32
37
|
}
|
|
33
38
|
}
|