export-runtime 0.0.4 → 0.0.6
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 +244 -0
- package/client.js +7 -22
- package/entry.js +2 -1
- package/handler.js +101 -93
- package/package.json +9 -3
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseSync } from "oxc-parser";
|
|
4
|
+
import { minifySync } from "oxc-minify";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
// Read wrangler.toml to find user module path
|
|
13
|
+
const wranglerPath = path.join(cwd, "wrangler.toml");
|
|
14
|
+
if (!fs.existsSync(wranglerPath)) {
|
|
15
|
+
console.error("wrangler.toml not found in", cwd);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const wranglerContent = fs.readFileSync(wranglerPath, "utf8");
|
|
19
|
+
const aliasMatch = wranglerContent.match(/"__USER_MODULE__"\s*=\s*"([^"]+)"/);
|
|
20
|
+
if (!aliasMatch) {
|
|
21
|
+
console.error('Could not find __USER_MODULE__ alias in wrangler.toml');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const userModulePath = path.resolve(cwd, aliasMatch[1]);
|
|
26
|
+
if (!fs.existsSync(userModulePath)) {
|
|
27
|
+
console.error("User module not found:", userModulePath);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const source = fs.readFileSync(userModulePath, "utf8");
|
|
32
|
+
const isTS = userModulePath.endsWith(".ts") || userModulePath.endsWith(".tsx");
|
|
33
|
+
const fileName = path.basename(userModulePath);
|
|
34
|
+
const result = parseSync(fileName, source, { sourceType: "module" });
|
|
35
|
+
const program = result.program;
|
|
36
|
+
|
|
37
|
+
// --- Type extraction helpers ---
|
|
38
|
+
|
|
39
|
+
function extractType(node) {
|
|
40
|
+
if (!node) return "any";
|
|
41
|
+
const ta = node.type === "TSTypeAnnotation" ? node.typeAnnotation : node;
|
|
42
|
+
switch (ta.type) {
|
|
43
|
+
case "TSStringKeyword": return "string";
|
|
44
|
+
case "TSNumberKeyword": return "number";
|
|
45
|
+
case "TSBooleanKeyword": return "boolean";
|
|
46
|
+
case "TSVoidKeyword": return "void";
|
|
47
|
+
case "TSAnyKeyword": return "any";
|
|
48
|
+
case "TSNullKeyword": return "null";
|
|
49
|
+
case "TSUndefinedKeyword": return "undefined";
|
|
50
|
+
case "TSNeverKeyword": return "never";
|
|
51
|
+
case "TSUnknownKeyword": return "unknown";
|
|
52
|
+
case "TSBigIntKeyword": return "bigint";
|
|
53
|
+
case "TSSymbolKeyword": return "symbol";
|
|
54
|
+
case "TSObjectKeyword": return "object";
|
|
55
|
+
case "TSArrayType":
|
|
56
|
+
return `${extractType(ta.elementType)}[]`;
|
|
57
|
+
case "TSTupleType":
|
|
58
|
+
return `[${(ta.elementTypes || []).map(e => extractType(e)).join(", ")}]`;
|
|
59
|
+
case "TSUnionType":
|
|
60
|
+
return ta.types.map(t => extractType(t)).join(" | ");
|
|
61
|
+
case "TSIntersectionType":
|
|
62
|
+
return ta.types.map(t => extractType(t)).join(" & ");
|
|
63
|
+
case "TSLiteralType": {
|
|
64
|
+
const lit = ta.literal;
|
|
65
|
+
if (lit.type === "StringLiteral") return JSON.stringify(lit.value);
|
|
66
|
+
if (lit.type === "NumericLiteral") return String(lit.value);
|
|
67
|
+
if (lit.type === "BooleanLiteral") return String(lit.value);
|
|
68
|
+
if (lit.type === "UnaryExpression") return `-${lit.argument.value}`;
|
|
69
|
+
return "any";
|
|
70
|
+
}
|
|
71
|
+
case "TSTypeReference": {
|
|
72
|
+
const name = ta.typeName?.name || ta.typeName?.right?.name || "any";
|
|
73
|
+
const typeArgs = ta.typeArguments || ta.typeParameters;
|
|
74
|
+
if (typeArgs?.params?.length) {
|
|
75
|
+
const args = typeArgs.params.map(p => extractType(p)).join(", ");
|
|
76
|
+
return `${name}<${args}>`;
|
|
77
|
+
}
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
80
|
+
case "TSFunctionType": {
|
|
81
|
+
const params = extractParams(ta.params);
|
|
82
|
+
const ret = ta.returnType ? extractType(ta.returnType) : "any";
|
|
83
|
+
return `(${params.join(", ")}) => ${ret}`;
|
|
84
|
+
}
|
|
85
|
+
case "TSTypeLiteral": {
|
|
86
|
+
const members = (ta.members || []).map(m => {
|
|
87
|
+
if (m.type === "TSPropertySignature") {
|
|
88
|
+
const key = m.key?.name || m.key?.value;
|
|
89
|
+
const type = m.typeAnnotation ? extractType(m.typeAnnotation) : "any";
|
|
90
|
+
const opt = m.optional ? "?" : "";
|
|
91
|
+
return `${key}${opt}: ${type}`;
|
|
92
|
+
}
|
|
93
|
+
return "";
|
|
94
|
+
}).filter(Boolean);
|
|
95
|
+
return `{ ${members.join("; ")} }`;
|
|
96
|
+
}
|
|
97
|
+
case "TSTypeAnnotation":
|
|
98
|
+
return extractType(ta.typeAnnotation);
|
|
99
|
+
default:
|
|
100
|
+
return "any";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractParams(params) {
|
|
105
|
+
return params.map(p => {
|
|
106
|
+
if (p.type === "AssignmentPattern") {
|
|
107
|
+
const name = p.left?.name || "arg";
|
|
108
|
+
const type = p.left?.typeAnnotation ? extractType(p.left.typeAnnotation) : "any";
|
|
109
|
+
return `${name}?: ${type}`;
|
|
110
|
+
}
|
|
111
|
+
const name = p.name || p.argument?.name || "arg";
|
|
112
|
+
const type = p.typeAnnotation ? extractType(p.typeAnnotation) : "any";
|
|
113
|
+
const opt = p.optional ? "?" : "";
|
|
114
|
+
const rest = p.type === "RestElement" ? "..." : "";
|
|
115
|
+
return `${rest}${name}${opt}: ${type}`;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Wrap return type: all functions become async over the network
|
|
120
|
+
function wrapReturnType(returnType, isAsync, isGenerator) {
|
|
121
|
+
if (isGenerator) {
|
|
122
|
+
// async generator → AsyncIterable<YieldType>
|
|
123
|
+
// extract inner type from AsyncGenerator<T> if present
|
|
124
|
+
if (returnType.startsWith("AsyncGenerator")) {
|
|
125
|
+
const inner = returnType.match(/^AsyncGenerator<(.+?)(?:,.*)?>/);
|
|
126
|
+
return `Promise<AsyncIterable<${inner ? inner[1] : "any"}>>`;
|
|
127
|
+
}
|
|
128
|
+
return `Promise<AsyncIterable<${returnType === "any" ? "any" : returnType}>>`;
|
|
129
|
+
}
|
|
130
|
+
// Already Promise<T> → keep as-is
|
|
131
|
+
if (returnType.startsWith("Promise<")) return returnType;
|
|
132
|
+
// ReadableStream<T> → Promise<ReadableStream<T>>
|
|
133
|
+
if (returnType.startsWith("ReadableStream")) return `Promise<${returnType}>`;
|
|
134
|
+
// Wrap in Promise
|
|
135
|
+
return `Promise<${returnType}>`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Generate .d.ts ---
|
|
139
|
+
|
|
140
|
+
const lines = [
|
|
141
|
+
"// Auto-generated type definitions (oxc-parser)",
|
|
142
|
+
"// All functions are async over the network",
|
|
143
|
+
"",
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
for (const node of program.body) {
|
|
147
|
+
if (node.type !== "ExportNamedDeclaration" || !node.declaration) continue;
|
|
148
|
+
const decl = node.declaration;
|
|
149
|
+
|
|
150
|
+
if (decl.type === "FunctionDeclaration") {
|
|
151
|
+
const name = decl.id.name;
|
|
152
|
+
const params = extractParams(decl.params);
|
|
153
|
+
const rawRet = decl.returnType ? extractType(decl.returnType) : "any";
|
|
154
|
+
const ret = wrapReturnType(rawRet, decl.async, decl.generator);
|
|
155
|
+
lines.push(`export declare function ${name}(${params.join(", ")}): ${ret};`);
|
|
156
|
+
lines.push("");
|
|
157
|
+
|
|
158
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
159
|
+
const name = decl.id.name;
|
|
160
|
+
lines.push(`export declare class ${name} {`);
|
|
161
|
+
for (const member of decl.body.body) {
|
|
162
|
+
if (member.type === "MethodDefinition") {
|
|
163
|
+
const mName = member.key.name || member.key.value;
|
|
164
|
+
if (member.kind === "constructor") {
|
|
165
|
+
const params = extractParams(member.value.params);
|
|
166
|
+
lines.push(` constructor(${params.join(", ")});`);
|
|
167
|
+
} else {
|
|
168
|
+
const params = extractParams(member.value.params);
|
|
169
|
+
const rawRet = member.value.returnType ? extractType(member.value.returnType) : "any";
|
|
170
|
+
const ret = wrapReturnType(rawRet, member.value.async, member.value.generator);
|
|
171
|
+
lines.push(` ${mName}(${params.join(", ")}): ${ret};`);
|
|
172
|
+
}
|
|
173
|
+
} else if (member.type === "PropertyDefinition") {
|
|
174
|
+
// Skip private members
|
|
175
|
+
if (member.accessibility === "private") continue;
|
|
176
|
+
const mName = member.key.name;
|
|
177
|
+
const type = member.typeAnnotation ? extractType(member.typeAnnotation) : "any";
|
|
178
|
+
lines.push(` ${mName}: ${type};`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
lines.push(` [Symbol.dispose](): Promise<void>;`);
|
|
182
|
+
lines.push(` "[release]"(): Promise<void>;`);
|
|
183
|
+
lines.push(`}`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
|
|
186
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
187
|
+
for (const d of decl.declarations) {
|
|
188
|
+
const name = d.id.name;
|
|
189
|
+
if (d.init?.type === "ObjectExpression") {
|
|
190
|
+
lines.push(`export declare const ${name}: {`);
|
|
191
|
+
for (const prop of d.init.properties) {
|
|
192
|
+
if (prop.type === "SpreadElement") continue;
|
|
193
|
+
const key = prop.key?.name || prop.key?.value;
|
|
194
|
+
if (prop.value?.type === "FunctionExpression" || prop.value?.type === "ArrowFunctionExpression") {
|
|
195
|
+
const params = extractParams(prop.value.params);
|
|
196
|
+
const rawRet = prop.value.returnType ? extractType(prop.value.returnType) : "any";
|
|
197
|
+
const ret = wrapReturnType(rawRet, prop.value.async, prop.value.generator);
|
|
198
|
+
lines.push(` ${key}(${params.join(", ")}): ${ret};`);
|
|
199
|
+
} else {
|
|
200
|
+
const type = d.id.typeAnnotation ? "any" : "any";
|
|
201
|
+
lines.push(` ${key}: any;`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
lines.push(`};`);
|
|
205
|
+
lines.push("");
|
|
206
|
+
} else {
|
|
207
|
+
const type = d.id.typeAnnotation ? extractType(d.id.typeAnnotation) : "any";
|
|
208
|
+
lines.push(`export declare const ${name}: ${type};`);
|
|
209
|
+
lines.push("");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add createUploadStream helper type
|
|
216
|
+
lines.push("export declare function createUploadStream(): Promise<{");
|
|
217
|
+
lines.push(" stream: WritableStream<any>;");
|
|
218
|
+
lines.push(" writableId: number;");
|
|
219
|
+
lines.push("}>;");
|
|
220
|
+
|
|
221
|
+
const typeDefinitions = lines.join("\n");
|
|
222
|
+
|
|
223
|
+
// --- Minify core module ---
|
|
224
|
+
|
|
225
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
226
|
+
const { CORE_CODE } = await import(path.join(__dirname, "..", "client.js"));
|
|
227
|
+
|
|
228
|
+
// CORE_CODE uses import.meta.url for WS URL — no placeholders needed.
|
|
229
|
+
const minified = minifySync("_core.js", CORE_CODE);
|
|
230
|
+
if (minified.errors?.length) {
|
|
231
|
+
console.error("Minification errors:", minified.errors);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Generate a unique ID per build for cache-busting the core module path
|
|
235
|
+
const coreId = crypto.randomUUID();
|
|
236
|
+
|
|
237
|
+
// Write as a JS module that exports type definitions, minified core, and core ID
|
|
238
|
+
const outPath = path.join(cwd, ".export-types.js");
|
|
239
|
+
fs.writeFileSync(outPath, [
|
|
240
|
+
`export default ${JSON.stringify(typeDefinitions)};`,
|
|
241
|
+
`export const minifiedCore = ${JSON.stringify(minified.code)};`,
|
|
242
|
+
`export const coreId = ${JSON.stringify(coreId)};`,
|
|
243
|
+
].join("\n") + "\n");
|
|
244
|
+
console.log("Generated type definitions + minified core →", outPath);
|
package/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// Core module code - self-contained ES module that derives WS URL from import.meta.url.
|
|
2
|
+
// Served at /_core.js with long cache. Exports createProxy and createUploadStream.
|
|
3
|
+
export const CORE_CODE = `
|
|
3
4
|
const stringify = (value) => {
|
|
4
5
|
const stringified = [];
|
|
5
6
|
const indexes = new Map();
|
|
@@ -79,10 +80,7 @@ const stringify = (value) => {
|
|
|
79
80
|
flatten(value);
|
|
80
81
|
return JSON.stringify(stringified);
|
|
81
82
|
};
|
|
82
|
-
`;
|
|
83
83
|
|
|
84
|
-
// Minimal devalue parse implementation (compatible with sveltejs/devalue)
|
|
85
|
-
export const DEVALUE_PARSE = `
|
|
86
84
|
const UNDEFINED = -1;
|
|
87
85
|
const HOLE = -2;
|
|
88
86
|
const NAN = -3;
|
|
@@ -153,20 +151,16 @@ const parse = (serialized) => {
|
|
|
153
151
|
|
|
154
152
|
return hydrate(0);
|
|
155
153
|
};
|
|
156
|
-
`;
|
|
157
154
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const ws = new WebSocket(__WS_URL__);
|
|
155
|
+
const _u = new URL("./", import.meta.url);
|
|
156
|
+
_u.protocol = _u.protocol === "https:" ? "wss:" : "ws:";
|
|
157
|
+
const ws = new WebSocket(_u.href);
|
|
163
158
|
const pending = new Map();
|
|
164
159
|
let nextId = 1;
|
|
165
160
|
let keepaliveInterval = null;
|
|
166
161
|
|
|
167
162
|
const ready = new Promise((resolve, reject) => {
|
|
168
163
|
ws.onopen = () => {
|
|
169
|
-
// Start keepalive ping every 30 seconds
|
|
170
164
|
keepaliveInterval = setInterval(() => {
|
|
171
165
|
if (ws.readyState === WebSocket.OPEN) {
|
|
172
166
|
ws.send(stringify({ type: "ping", id: 0 }));
|
|
@@ -196,7 +190,6 @@ const sendRequest = async (msg) => {
|
|
|
196
190
|
ws.onmessage = (event) => {
|
|
197
191
|
const msg = parse(event.data);
|
|
198
192
|
|
|
199
|
-
// Ignore pong responses (keepalive)
|
|
200
193
|
if (msg.type === "pong") return;
|
|
201
194
|
|
|
202
195
|
const resolver = pending.get(msg.id);
|
|
@@ -222,7 +215,6 @@ ws.onmessage = (event) => {
|
|
|
222
215
|
};
|
|
223
216
|
resolver.resolve(iteratorProxy);
|
|
224
217
|
} else if (msg.valueType === "readablestream") {
|
|
225
|
-
// Create a ReadableStream proxy that pulls from server
|
|
226
218
|
const streamId = msg.streamId;
|
|
227
219
|
const stream = new ReadableStream({
|
|
228
220
|
async pull(controller) {
|
|
@@ -243,7 +235,6 @@ ws.onmessage = (event) => {
|
|
|
243
235
|
});
|
|
244
236
|
resolver.resolve(stream);
|
|
245
237
|
} else if (msg.valueType === "writablestream") {
|
|
246
|
-
// Create a WritableStream proxy that pushes to server
|
|
247
238
|
const writableId = msg.writableId;
|
|
248
239
|
const stream = new WritableStream({
|
|
249
240
|
async write(chunk) {
|
|
@@ -266,14 +257,12 @@ ws.onmessage = (event) => {
|
|
|
266
257
|
resolver.resolve({ value: msg.value, done: msg.done });
|
|
267
258
|
pending.delete(msg.id);
|
|
268
259
|
} else if (msg.type === "stream-result") {
|
|
269
|
-
// Convert array back to Uint8Array if it was serialized
|
|
270
260
|
const value = Array.isArray(msg.value) ? new Uint8Array(msg.value) : msg.value;
|
|
271
261
|
resolver.resolve({ value, done: msg.done });
|
|
272
262
|
pending.delete(msg.id);
|
|
273
263
|
}
|
|
274
264
|
};
|
|
275
265
|
|
|
276
|
-
// Proxy for remote class instances
|
|
277
266
|
const createInstanceProxy = (instanceId, path = []) => {
|
|
278
267
|
const proxy = new Proxy(function(){}, {
|
|
279
268
|
get(_, prop) {
|
|
@@ -297,8 +286,7 @@ const createInstanceProxy = (instanceId, path = []) => {
|
|
|
297
286
|
return proxy;
|
|
298
287
|
};
|
|
299
288
|
|
|
300
|
-
|
|
301
|
-
const createProxy = (path = []) => new Proxy(function(){}, {
|
|
289
|
+
export const createProxy = (path = []) => new Proxy(function(){}, {
|
|
302
290
|
get(_, prop) {
|
|
303
291
|
if (prop === "then" || prop === Symbol.toStringTag) return undefined;
|
|
304
292
|
return createProxy([...path, prop]);
|
|
@@ -311,7 +299,6 @@ const createProxy = (path = []) => new Proxy(function(){}, {
|
|
|
311
299
|
}
|
|
312
300
|
});
|
|
313
301
|
|
|
314
|
-
// Helper to create a client-side WritableStream that can be passed to server functions
|
|
315
302
|
export const createUploadStream = async () => {
|
|
316
303
|
const result = await sendRequest({ type: "writable-create" });
|
|
317
304
|
const writableId = result.writableId;
|
|
@@ -331,6 +318,4 @@ export const createUploadStream = async () => {
|
|
|
331
318
|
|
|
332
319
|
return { stream, writableId };
|
|
333
320
|
};
|
|
334
|
-
|
|
335
|
-
__NAMED_EXPORTS__
|
|
336
321
|
`;
|
package/entry.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as userExports from "__USER_MODULE__";
|
|
2
|
+
import generatedTypes, { minifiedCore, coreId } from "__GENERATED_TYPES__";
|
|
2
3
|
import { createHandler } from "./handler.js";
|
|
3
4
|
|
|
4
|
-
export default createHandler(userExports);
|
|
5
|
+
export default createHandler(userExports, generatedTypes, minifiedCore, coreId);
|
package/handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stringify, parse } from "devalue";
|
|
2
|
-
import {
|
|
2
|
+
import { CORE_CODE } from "./client.js";
|
|
3
3
|
|
|
4
4
|
const getByPath = (obj, path) => {
|
|
5
5
|
let current = obj;
|
|
@@ -19,70 +19,56 @@ const isReadableStream = (value) =>
|
|
|
19
19
|
const isClass = (fn) =>
|
|
20
20
|
typeof fn === "function" && /^class\s/.test(Function.prototype.toString.call(fn));
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
const generateTypeDefinitions = (exports,
|
|
22
|
+
// Runtime fallback: generate TypeScript type definitions from exports
|
|
23
|
+
const generateTypeDefinitions = (exports, keys) => {
|
|
24
24
|
const lines = [
|
|
25
25
|
"// Auto-generated type definitions",
|
|
26
26
|
"// All functions are async over the network",
|
|
27
27
|
"",
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
for (const name of keys) {
|
|
31
|
+
const value = exports[name];
|
|
31
32
|
if (isClass(value)) {
|
|
32
|
-
// Extract class method names
|
|
33
33
|
const proto = value.prototype;
|
|
34
34
|
const methodNames = Object.getOwnPropertyNames(proto).filter(
|
|
35
35
|
(n) => n !== "constructor" && typeof proto[n] === "function"
|
|
36
36
|
);
|
|
37
|
-
|
|
38
|
-
lines.push(
|
|
39
|
-
lines.push(`${indent} constructor(...args: any[]);`);
|
|
37
|
+
lines.push(`export declare class ${name} {`);
|
|
38
|
+
lines.push(` constructor(...args: any[]);`);
|
|
40
39
|
for (const method of methodNames) {
|
|
41
|
-
lines.push(
|
|
40
|
+
lines.push(` ${method}(...args: any[]): Promise<any>;`);
|
|
42
41
|
}
|
|
43
|
-
lines.push(
|
|
44
|
-
lines.push(
|
|
45
|
-
lines.push(
|
|
42
|
+
lines.push(` [Symbol.dispose](): Promise<void>;`);
|
|
43
|
+
lines.push(` "[release]"(): Promise<void>;`);
|
|
44
|
+
lines.push(`}`);
|
|
46
45
|
} else if (typeof value === "function") {
|
|
47
|
-
// Check if it's an async generator
|
|
48
46
|
const fnStr = Function.prototype.toString.call(value);
|
|
49
47
|
if (fnStr.startsWith("async function*") || fnStr.includes("async *")) {
|
|
50
|
-
lines.push(
|
|
51
|
-
`${indent}export declare function ${name}(...args: any[]): Promise<AsyncIterable<any>>;`
|
|
52
|
-
);
|
|
48
|
+
lines.push(`export declare function ${name}(...args: any[]): Promise<AsyncIterable<any>>;`);
|
|
53
49
|
} else if (fnStr.includes("ReadableStream")) {
|
|
54
|
-
lines.push(
|
|
55
|
-
`${indent}export declare function ${name}(...args: any[]): Promise<ReadableStream<any>>;`
|
|
56
|
-
);
|
|
50
|
+
lines.push(`export declare function ${name}(...args: any[]): Promise<ReadableStream<any>>;`);
|
|
57
51
|
} else {
|
|
58
|
-
lines.push(
|
|
59
|
-
`${indent}export declare function ${name}(...args: any[]): Promise<any>;`
|
|
60
|
-
);
|
|
52
|
+
lines.push(`export declare function ${name}(...args: any[]): Promise<any>;`);
|
|
61
53
|
}
|
|
62
54
|
} else if (typeof value === "object" && value !== null) {
|
|
63
|
-
// Nested object with methods
|
|
64
55
|
const keys = Object.keys(value);
|
|
65
|
-
lines.push(
|
|
56
|
+
lines.push(`export declare const ${name}: {`);
|
|
66
57
|
for (const key of keys) {
|
|
67
58
|
const v = value[key];
|
|
68
59
|
if (typeof v === "function") {
|
|
69
|
-
lines.push(
|
|
60
|
+
lines.push(` ${key}(...args: any[]): Promise<any>;`);
|
|
70
61
|
} else {
|
|
71
|
-
lines.push(
|
|
62
|
+
lines.push(` ${key}: any;`);
|
|
72
63
|
}
|
|
73
64
|
}
|
|
74
|
-
lines.push(
|
|
65
|
+
lines.push(`};`);
|
|
75
66
|
} else {
|
|
76
|
-
lines.push(
|
|
67
|
+
lines.push(`export declare const ${name}: any;`);
|
|
77
68
|
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
for (const key of exportKeys) {
|
|
81
|
-
generateType(exports[key], key);
|
|
82
69
|
lines.push("");
|
|
83
70
|
}
|
|
84
71
|
|
|
85
|
-
// Add createUploadStream helper type
|
|
86
72
|
lines.push("export declare function createUploadStream(): Promise<{");
|
|
87
73
|
lines.push(" stream: WritableStream<any>;");
|
|
88
74
|
lines.push(" writableId: number;");
|
|
@@ -91,7 +77,20 @@ const generateTypeDefinitions = (exports, exportKeys) => {
|
|
|
91
77
|
return lines.join("\n");
|
|
92
78
|
};
|
|
93
79
|
|
|
94
|
-
|
|
80
|
+
|
|
81
|
+
const jsHeaders = (extra = {}) => ({
|
|
82
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
83
|
+
"Access-Control-Allow-Origin": "*",
|
|
84
|
+
...extra,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const tsHeaders = () => ({
|
|
88
|
+
"Content-Type": "application/typescript; charset=utf-8",
|
|
89
|
+
"Access-Control-Allow-Origin": "*",
|
|
90
|
+
"Cache-Control": "no-cache",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const createHandler = (exports, generatedTypes, minifiedCore, coreId) => {
|
|
95
94
|
const exportKeys = Object.keys(exports);
|
|
96
95
|
const iteratorStore = new Map();
|
|
97
96
|
const instanceStore = new Map();
|
|
@@ -105,11 +104,15 @@ export const createHandler = (exports) => {
|
|
|
105
104
|
ws.send(stringify(data));
|
|
106
105
|
};
|
|
107
106
|
|
|
107
|
+
const coreModuleCode = minifiedCore || CORE_CODE;
|
|
108
|
+
const corePath = `/${coreId || crypto.randomUUID()}.js`;
|
|
109
|
+
|
|
108
110
|
return {
|
|
109
111
|
async fetch(request) {
|
|
110
112
|
const url = new URL(request.url);
|
|
111
113
|
const upgradeHeader = request.headers.get("Upgrade");
|
|
112
114
|
|
|
115
|
+
// --- WebSocket upgrade (path-agnostic) ---
|
|
113
116
|
if (upgradeHeader === "websocket") {
|
|
114
117
|
const pair = new WebSocketPair();
|
|
115
118
|
const [client, server] = Object.values(pair);
|
|
@@ -121,14 +124,12 @@ export const createHandler = (exports) => {
|
|
|
121
124
|
const msg = parse(event.data);
|
|
122
125
|
const { type, id, path = [], args = [], iteratorId, instanceId } = msg;
|
|
123
126
|
|
|
124
|
-
// Keepalive ping/pong
|
|
125
127
|
if (type === "ping") {
|
|
126
128
|
send(server, { type: "pong", id });
|
|
127
129
|
return;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
if (type === "construct") {
|
|
131
|
-
// Class instantiation
|
|
132
133
|
try {
|
|
133
134
|
const Ctor = getByPath(exports, path);
|
|
134
135
|
if (!isClass(Ctor)) {
|
|
@@ -148,7 +149,6 @@ export const createHandler = (exports) => {
|
|
|
148
149
|
let thisArg;
|
|
149
150
|
|
|
150
151
|
if (instanceId !== undefined) {
|
|
151
|
-
// Method call on instance
|
|
152
152
|
const instance = instanceStore.get(instanceId);
|
|
153
153
|
if (!instance) {
|
|
154
154
|
send(server, { type: "error", id, error: "Instance not found" });
|
|
@@ -157,7 +157,6 @@ export const createHandler = (exports) => {
|
|
|
157
157
|
target = getByPath(instance, path);
|
|
158
158
|
thisArg = path.length > 1 ? getByPath(instance, path.slice(0, -1)) : instance;
|
|
159
159
|
} else {
|
|
160
|
-
// Regular function call
|
|
161
160
|
target = getByPath(exports, path);
|
|
162
161
|
thisArg = path.length > 1 ? getByPath(exports, path.slice(0, -1)) : undefined;
|
|
163
162
|
}
|
|
@@ -167,7 +166,6 @@ export const createHandler = (exports) => {
|
|
|
167
166
|
return;
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
// Await result to support both sync and async functions
|
|
171
169
|
const result = await target.apply(thisArg, args);
|
|
172
170
|
|
|
173
171
|
if (isReadableStream(result)) {
|
|
@@ -187,7 +185,6 @@ export const createHandler = (exports) => {
|
|
|
187
185
|
send(server, { type: "error", id, error: String(err) });
|
|
188
186
|
}
|
|
189
187
|
} else if (type === "get") {
|
|
190
|
-
// Property access on instance
|
|
191
188
|
try {
|
|
192
189
|
const instance = instanceStore.get(instanceId);
|
|
193
190
|
if (!instance) {
|
|
@@ -204,7 +201,6 @@ export const createHandler = (exports) => {
|
|
|
204
201
|
send(server, { type: "error", id, error: String(err) });
|
|
205
202
|
}
|
|
206
203
|
} else if (type === "set") {
|
|
207
|
-
// Property assignment on instance
|
|
208
204
|
try {
|
|
209
205
|
const instance = instanceStore.get(instanceId);
|
|
210
206
|
if (!instance) {
|
|
@@ -219,7 +215,6 @@ export const createHandler = (exports) => {
|
|
|
219
215
|
send(server, { type: "error", id, error: String(err) });
|
|
220
216
|
}
|
|
221
217
|
} else if (type === "release") {
|
|
222
|
-
// Release instance
|
|
223
218
|
instanceStore.delete(instanceId);
|
|
224
219
|
send(server, { type: "result", id, value: true });
|
|
225
220
|
} else if (type === "iterate-next") {
|
|
@@ -241,7 +236,6 @@ export const createHandler = (exports) => {
|
|
|
241
236
|
iteratorStore.delete(iteratorId);
|
|
242
237
|
send(server, { type: "iterate-result", id, value: undefined, done: true });
|
|
243
238
|
} else if (type === "stream-read") {
|
|
244
|
-
// ReadableStream chunk read
|
|
245
239
|
const { streamId } = msg;
|
|
246
240
|
const entry = streamStore.get(streamId);
|
|
247
241
|
if (!entry) {
|
|
@@ -249,7 +243,6 @@ export const createHandler = (exports) => {
|
|
|
249
243
|
return;
|
|
250
244
|
}
|
|
251
245
|
try {
|
|
252
|
-
// Get or create reader for this stream
|
|
253
246
|
let reader = entry.reader;
|
|
254
247
|
if (!reader) {
|
|
255
248
|
reader = entry.stream.getReader();
|
|
@@ -259,7 +252,6 @@ export const createHandler = (exports) => {
|
|
|
259
252
|
if (done) {
|
|
260
253
|
streamStore.delete(streamId);
|
|
261
254
|
}
|
|
262
|
-
// Convert Uint8Array to array for devalue serialization
|
|
263
255
|
const serializedValue = value instanceof Uint8Array ? Array.from(value) : value;
|
|
264
256
|
send(server, { type: "stream-result", id, value: serializedValue, done: !!done });
|
|
265
257
|
} catch (err) {
|
|
@@ -267,7 +259,6 @@ export const createHandler = (exports) => {
|
|
|
267
259
|
send(server, { type: "error", id, error: String(err) });
|
|
268
260
|
}
|
|
269
261
|
} else if (type === "stream-cancel") {
|
|
270
|
-
// Cancel ReadableStream
|
|
271
262
|
const { streamId } = msg;
|
|
272
263
|
const entry = streamStore.get(streamId);
|
|
273
264
|
if (entry) {
|
|
@@ -282,27 +273,19 @@ export const createHandler = (exports) => {
|
|
|
282
273
|
}
|
|
283
274
|
send(server, { type: "result", id, value: true });
|
|
284
275
|
} else if (type === "writable-create") {
|
|
285
|
-
// Create a WritableStream on server side
|
|
286
276
|
const { targetPath, targetInstanceId } = msg;
|
|
287
277
|
let chunks = [];
|
|
288
278
|
const writableId = nextStreamId++;
|
|
289
279
|
|
|
290
280
|
const writable = new WritableStream({
|
|
291
|
-
write(chunk) {
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
close() {
|
|
295
|
-
// Resolve with all chunks when stream closes
|
|
296
|
-
},
|
|
297
|
-
abort(reason) {
|
|
298
|
-
chunks = [];
|
|
299
|
-
}
|
|
281
|
+
write(chunk) { chunks.push(chunk); },
|
|
282
|
+
close() {},
|
|
283
|
+
abort(reason) { chunks = []; }
|
|
300
284
|
});
|
|
301
285
|
|
|
302
286
|
writableStreamStore.set(writableId, { writable, chunks, targetPath, targetInstanceId });
|
|
303
287
|
send(server, { type: "result", id, writableId, valueType: "writablestream" });
|
|
304
288
|
} else if (type === "writable-write") {
|
|
305
|
-
// Write chunk to WritableStream
|
|
306
289
|
const { writableId, chunk } = msg;
|
|
307
290
|
const entry = writableStreamStore.get(writableId);
|
|
308
291
|
if (!entry) {
|
|
@@ -310,7 +293,6 @@ export const createHandler = (exports) => {
|
|
|
310
293
|
return;
|
|
311
294
|
}
|
|
312
295
|
try {
|
|
313
|
-
// Convert array back to Uint8Array if needed
|
|
314
296
|
const data = Array.isArray(chunk) ? new Uint8Array(chunk) : chunk;
|
|
315
297
|
entry.chunks.push(data);
|
|
316
298
|
send(server, { type: "result", id, value: true });
|
|
@@ -318,7 +300,6 @@ export const createHandler = (exports) => {
|
|
|
318
300
|
send(server, { type: "error", id, error: String(err) });
|
|
319
301
|
}
|
|
320
302
|
} else if (type === "writable-close") {
|
|
321
|
-
// Close WritableStream and return collected chunks
|
|
322
303
|
const { writableId } = msg;
|
|
323
304
|
const entry = writableStreamStore.get(writableId);
|
|
324
305
|
if (!entry) {
|
|
@@ -326,10 +307,8 @@ export const createHandler = (exports) => {
|
|
|
326
307
|
return;
|
|
327
308
|
}
|
|
328
309
|
writableStreamStore.delete(writableId);
|
|
329
|
-
// Return the collected data
|
|
330
310
|
send(server, { type: "result", id, value: entry.chunks });
|
|
331
311
|
} else if (type === "writable-abort") {
|
|
332
|
-
// Abort WritableStream
|
|
333
312
|
const { writableId } = msg;
|
|
334
313
|
writableStreamStore.delete(writableId);
|
|
335
314
|
send(server, { type: "result", id, value: true });
|
|
@@ -349,43 +328,72 @@ export const createHandler = (exports) => {
|
|
|
349
328
|
return new Response(null, { status: 101, webSocket: client });
|
|
350
329
|
}
|
|
351
330
|
|
|
352
|
-
|
|
353
|
-
|
|
331
|
+
// --- HTTP routing ---
|
|
332
|
+
|
|
333
|
+
const fullTypes = generatedTypes || generateTypeDefinitions(exports, exportKeys);
|
|
334
|
+
const pathname = url.pathname;
|
|
335
|
+
|
|
336
|
+
// Serve core module — long-cached, content-independent of deployment URL
|
|
337
|
+
if (pathname === corePath) {
|
|
338
|
+
return new Response(coreModuleCode, {
|
|
339
|
+
headers: jsHeaders({ "Cache-Control": "public, max-age=31536000, immutable" }),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Type definitions
|
|
344
|
+
if (url.searchParams.has("types") || pathname.endsWith(".d.ts")) {
|
|
345
|
+
if (pathname === "/" || pathname.endsWith(".d.ts")) {
|
|
346
|
+
return new Response(fullTypes, { headers: tsHeaders() });
|
|
347
|
+
}
|
|
348
|
+
// Per-export types — re-export from root to avoid duplication
|
|
349
|
+
const name = pathname.slice(1);
|
|
350
|
+
if (exportKeys.includes(name)) {
|
|
351
|
+
const code = `export { ${name} as default, ${name} } from "./?types";`;
|
|
352
|
+
return new Response(code, { headers: tsHeaders() });
|
|
353
|
+
}
|
|
354
|
+
return new Response("// Export not found", { status: 404, headers: tsHeaders() });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
358
|
+
|
|
359
|
+
// Root — re-exports all from ${corePath}
|
|
360
|
+
if (pathname === "/") {
|
|
361
|
+
const namedExports = exportKeys
|
|
362
|
+
.map((key) => `export const ${key} = createProxy([${JSON.stringify(key)}]);`)
|
|
363
|
+
.join("\n");
|
|
364
|
+
const code = [
|
|
365
|
+
`import { createProxy, createUploadStream } from ".${corePath}";`,
|
|
366
|
+
namedExports,
|
|
367
|
+
`export { createUploadStream };`,
|
|
368
|
+
].join("\n");
|
|
369
|
+
|
|
370
|
+
return new Response(code, {
|
|
371
|
+
headers: jsHeaders({
|
|
372
|
+
"Cache-Control": "no-cache",
|
|
373
|
+
"X-TypeScript-Types": `${baseUrl}/?types`,
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
354
377
|
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
// Per-export path — e.g. /greet, /Counter
|
|
379
|
+
const exportName = pathname.slice(1);
|
|
380
|
+
if (exportKeys.includes(exportName)) {
|
|
381
|
+
const code = [
|
|
382
|
+
`import { createProxy } from ".${corePath}";`,
|
|
383
|
+
`const _export = createProxy([${JSON.stringify(exportName)}]);`,
|
|
384
|
+
`export default _export;`,
|
|
385
|
+
`export { _export as ${exportName} };`,
|
|
386
|
+
].join("\n");
|
|
387
|
+
|
|
388
|
+
return new Response(code, {
|
|
389
|
+
headers: jsHeaders({
|
|
362
390
|
"Cache-Control": "no-cache",
|
|
363
|
-
|
|
391
|
+
"X-TypeScript-Types": `${baseUrl}/${exportName}?types`,
|
|
392
|
+
}),
|
|
364
393
|
});
|
|
365
394
|
}
|
|
366
395
|
|
|
367
|
-
|
|
368
|
-
const namedExports = exportKeys
|
|
369
|
-
.map((key) => `export const ${key} = createProxy([${JSON.stringify(key)}]);`)
|
|
370
|
-
.join("\n");
|
|
371
|
-
|
|
372
|
-
const clientCode = CLIENT_CODE
|
|
373
|
-
.replace("__WS_URL__", JSON.stringify(wsUrl))
|
|
374
|
-
.replace("__DEVALUE_STRINGIFY__", DEVALUE_STRINGIFY)
|
|
375
|
-
.replace("__DEVALUE_PARSE__", DEVALUE_PARSE)
|
|
376
|
-
.replace("__NAMED_EXPORTS__", namedExports);
|
|
377
|
-
|
|
378
|
-
// Build types URL for X-TypeScript-Types header
|
|
379
|
-
const typesUrl = `${url.protocol}//${url.host}${url.pathname}?types`;
|
|
380
|
-
|
|
381
|
-
return new Response(clientCode, {
|
|
382
|
-
headers: {
|
|
383
|
-
"Content-Type": "application/javascript; charset=utf-8",
|
|
384
|
-
"Access-Control-Allow-Origin": "*",
|
|
385
|
-
"Cache-Control": "no-cache",
|
|
386
|
-
"X-TypeScript-Types": typesUrl,
|
|
387
|
-
},
|
|
388
|
-
});
|
|
396
|
+
return new Response("Not found", { status: 404 });
|
|
389
397
|
},
|
|
390
398
|
};
|
|
391
399
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "export-runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Cloudflare Workers ESM Export Framework Runtime",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -22,12 +22,18 @@
|
|
|
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-minify": "^0.121.0",
|
|
37
|
+
"oxc-parser": "^0.121.0"
|
|
32
38
|
}
|
|
33
39
|
}
|