export-runtime 0.0.7 → 0.0.9
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 +151 -125
- package/client.js +0 -14
- package/entry.js +2 -2
- package/handler.js +77 -37
- package/package.json +1 -1
- package/rpc.js +28 -34
- package/shared-do.js +2 -6
package/bin/generate-types.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
|
|
|
9
9
|
|
|
10
10
|
const cwd = process.cwd();
|
|
11
11
|
|
|
12
|
-
// Read wrangler.toml to find
|
|
12
|
+
// Read wrangler.toml to find source root
|
|
13
13
|
const wranglerPath = path.join(cwd, "wrangler.toml");
|
|
14
14
|
if (!fs.existsSync(wranglerPath)) {
|
|
15
15
|
console.error("wrangler.toml not found in", cwd);
|
|
@@ -22,17 +22,41 @@ if (!aliasMatch) {
|
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
// Derive source root directory from the alias (e.g., "./src/index.ts" → "./src")
|
|
26
|
+
const aliasTarget = aliasMatch[1];
|
|
27
|
+
// If alias points to the module map, resolve src dir from it; otherwise derive from file
|
|
28
|
+
const srcDir = aliasTarget.includes(".export-module-map")
|
|
29
|
+
? path.resolve(cwd, "src")
|
|
30
|
+
: path.resolve(cwd, path.dirname(aliasTarget.replace(/^\.\//, "")));
|
|
31
|
+
|
|
32
|
+
// --- Discover all source files under srcDir ---
|
|
33
|
+
|
|
34
|
+
const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
35
|
+
|
|
36
|
+
function discoverModules(dir, base = "") {
|
|
37
|
+
const modules = [];
|
|
38
|
+
if (!fs.existsSync(dir)) return modules;
|
|
39
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
40
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
41
|
+
const fullPath = path.join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
modules.push(...discoverModules(fullPath, base ? `${base}/${entry.name}` : entry.name));
|
|
44
|
+
} else if (EXTENSIONS.includes(path.extname(entry.name))) {
|
|
45
|
+
const nameWithoutExt = entry.name.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
46
|
+
const routePath = nameWithoutExt === "index"
|
|
47
|
+
? base // index.ts → directory path ("" for root)
|
|
48
|
+
: (base ? `${base}/${nameWithoutExt}` : nameWithoutExt);
|
|
49
|
+
modules.push({ routePath, filePath: fullPath });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return modules;
|
|
29
53
|
}
|
|
30
54
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
const modules = discoverModules(srcDir);
|
|
56
|
+
if (modules.length === 0) {
|
|
57
|
+
console.error("No source files found in", srcDir);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
36
60
|
|
|
37
61
|
// --- Type extraction helpers ---
|
|
38
62
|
|
|
@@ -52,14 +76,10 @@ function extractType(node) {
|
|
|
52
76
|
case "TSBigIntKeyword": return "bigint";
|
|
53
77
|
case "TSSymbolKeyword": return "symbol";
|
|
54
78
|
case "TSObjectKeyword": return "object";
|
|
55
|
-
case "TSArrayType":
|
|
56
|
-
|
|
57
|
-
case "
|
|
58
|
-
|
|
59
|
-
case "TSUnionType":
|
|
60
|
-
return ta.types.map(t => extractType(t)).join(" | ");
|
|
61
|
-
case "TSIntersectionType":
|
|
62
|
-
return ta.types.map(t => extractType(t)).join(" & ");
|
|
79
|
+
case "TSArrayType": return `${extractType(ta.elementType)}[]`;
|
|
80
|
+
case "TSTupleType": return `[${(ta.elementTypes || []).map(e => extractType(e)).join(", ")}]`;
|
|
81
|
+
case "TSUnionType": return ta.types.map(t => extractType(t)).join(" | ");
|
|
82
|
+
case "TSIntersectionType": return ta.types.map(t => extractType(t)).join(" & ");
|
|
63
83
|
case "TSLiteralType": {
|
|
64
84
|
const lit = ta.literal;
|
|
65
85
|
if (lit.type === "StringLiteral") return JSON.stringify(lit.value);
|
|
@@ -94,10 +114,8 @@ function extractType(node) {
|
|
|
94
114
|
}).filter(Boolean);
|
|
95
115
|
return `{ ${members.join("; ")} }`;
|
|
96
116
|
}
|
|
97
|
-
case "TSTypeAnnotation":
|
|
98
|
-
|
|
99
|
-
default:
|
|
100
|
-
return "any";
|
|
117
|
+
case "TSTypeAnnotation": return extractType(ta.typeAnnotation);
|
|
118
|
+
default: return "any";
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
121
|
|
|
@@ -116,109 +134,110 @@ function extractParams(params) {
|
|
|
116
134
|
});
|
|
117
135
|
}
|
|
118
136
|
|
|
119
|
-
// Wrap return type: all functions become async over the network
|
|
120
137
|
function wrapReturnType(returnType, isAsync, isGenerator) {
|
|
121
138
|
if (isGenerator) {
|
|
122
|
-
// async generator → AsyncIterable<YieldType>
|
|
123
|
-
// extract inner type from AsyncGenerator<T> if present
|
|
124
139
|
if (returnType.startsWith("AsyncGenerator")) {
|
|
125
140
|
const inner = returnType.match(/^AsyncGenerator<(.+?)(?:,.*)?>/);
|
|
126
141
|
return `Promise<AsyncIterable<${inner ? inner[1] : "any"}>>`;
|
|
127
142
|
}
|
|
128
143
|
return `Promise<AsyncIterable<${returnType === "any" ? "any" : returnType}>>`;
|
|
129
144
|
}
|
|
130
|
-
// Already Promise<T> → keep as-is
|
|
131
145
|
if (returnType.startsWith("Promise<")) return returnType;
|
|
132
|
-
// ReadableStream<T> → Promise<ReadableStream<T>>
|
|
133
146
|
if (returnType.startsWith("ReadableStream")) return `Promise<${returnType}>`;
|
|
134
|
-
// Wrap in Promise
|
|
135
147
|
return `Promise<${returnType}>`;
|
|
136
148
|
}
|
|
137
149
|
|
|
138
|
-
// ---
|
|
150
|
+
// --- Extract types and export names from a single file ---
|
|
139
151
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
""
|
|
144
|
-
|
|
152
|
+
function extractFileTypes(filePath) {
|
|
153
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
154
|
+
const fileName = path.basename(filePath);
|
|
155
|
+
const result = parseSync(fileName, source, { sourceType: "module" });
|
|
156
|
+
const program = result.program;
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
const lines = [];
|
|
159
|
+
const exportNames = [];
|
|
160
|
+
|
|
161
|
+
for (const node of program.body) {
|
|
162
|
+
if (node.type !== "ExportNamedDeclaration" || !node.declaration) continue;
|
|
163
|
+
const decl = node.declaration;
|
|
164
|
+
|
|
165
|
+
if (decl.type === "FunctionDeclaration") {
|
|
166
|
+
const name = decl.id.name;
|
|
167
|
+
exportNames.push(name);
|
|
168
|
+
const params = extractParams(decl.params);
|
|
169
|
+
const rawRet = decl.returnType ? extractType(decl.returnType) : "any";
|
|
170
|
+
const ret = wrapReturnType(rawRet, decl.async, decl.generator);
|
|
171
|
+
lines.push(`export declare function ${name}(${params.join(", ")}): ${ret};`);
|
|
172
|
+
lines.push("");
|
|
173
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
174
|
+
const name = decl.id.name;
|
|
175
|
+
exportNames.push(name);
|
|
176
|
+
lines.push(`export declare class ${name} {`);
|
|
177
|
+
for (const member of decl.body.body) {
|
|
178
|
+
if (member.type === "MethodDefinition") {
|
|
179
|
+
const mName = member.key.name || member.key.value;
|
|
180
|
+
if (member.kind === "constructor") {
|
|
181
|
+
lines.push(` constructor(${extractParams(member.value.params).join(", ")});`);
|
|
182
|
+
} else {
|
|
183
|
+
const params = extractParams(member.value.params);
|
|
184
|
+
const rawRet = member.value.returnType ? extractType(member.value.returnType) : "any";
|
|
185
|
+
const ret = wrapReturnType(rawRet, member.value.async, member.value.generator);
|
|
186
|
+
lines.push(` ${mName}(${params.join(", ")}): ${ret};`);
|
|
187
|
+
}
|
|
188
|
+
} else if (member.type === "PropertyDefinition") {
|
|
189
|
+
if (member.accessibility === "private") continue;
|
|
190
|
+
const mName = member.key.name;
|
|
191
|
+
const type = member.typeAnnotation ? extractType(member.typeAnnotation) : "any";
|
|
192
|
+
lines.push(` ${mName}: ${type};`);
|
|
172
193
|
}
|
|
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
194
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
lines.push(` ${key}: any;`);
|
|
195
|
+
lines.push(` [Symbol.dispose](): Promise<void>;`);
|
|
196
|
+
lines.push(` "[release]"(): Promise<void>;`);
|
|
197
|
+
lines.push(`}`);
|
|
198
|
+
lines.push("");
|
|
199
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
200
|
+
for (const d of decl.declarations) {
|
|
201
|
+
const name = d.id.name;
|
|
202
|
+
exportNames.push(name);
|
|
203
|
+
if (d.init?.type === "ObjectExpression") {
|
|
204
|
+
lines.push(`export declare const ${name}: {`);
|
|
205
|
+
for (const prop of d.init.properties) {
|
|
206
|
+
if (prop.type === "SpreadElement") continue;
|
|
207
|
+
const key = prop.key?.name || prop.key?.value;
|
|
208
|
+
if (prop.value?.type === "FunctionExpression" || prop.value?.type === "ArrowFunctionExpression") {
|
|
209
|
+
const params = extractParams(prop.value.params);
|
|
210
|
+
const rawRet = prop.value.returnType ? extractType(prop.value.returnType) : "any";
|
|
211
|
+
const ret = wrapReturnType(rawRet, prop.value.async, prop.value.generator);
|
|
212
|
+
lines.push(` ${key}(${params.join(", ")}): ${ret};`);
|
|
213
|
+
} else {
|
|
214
|
+
lines.push(` ${key}: any;`);
|
|
215
|
+
}
|
|
202
216
|
}
|
|
217
|
+
lines.push(`};`);
|
|
218
|
+
lines.push("");
|
|
219
|
+
} else {
|
|
220
|
+
const type = d.id.typeAnnotation ? extractType(d.id.typeAnnotation) : "any";
|
|
221
|
+
lines.push(`export declare const ${name}: ${type};`);
|
|
222
|
+
lines.push("");
|
|
203
223
|
}
|
|
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
224
|
}
|
|
211
225
|
}
|
|
212
226
|
}
|
|
227
|
+
|
|
228
|
+
return { types: lines.join("\n"), exportNames };
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
lines.push("}>;");
|
|
231
|
+
// --- Process all modules ---
|
|
232
|
+
|
|
233
|
+
const typesMap = {}; // routePath → type definition string
|
|
234
|
+
const exportsMap = {}; // routePath → export names array
|
|
220
235
|
|
|
221
|
-
const
|
|
236
|
+
for (const mod of modules) {
|
|
237
|
+
const { types, exportNames } = extractFileTypes(mod.filePath);
|
|
238
|
+
typesMap[mod.routePath] = types;
|
|
239
|
+
exportsMap[mod.routePath] = exportNames;
|
|
240
|
+
}
|
|
222
241
|
|
|
223
242
|
// --- Minify core modules ---
|
|
224
243
|
|
|
@@ -226,42 +245,39 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
226
245
|
const { CORE_CODE, SHARED_CORE_CODE } = await import(path.join(__dirname, "..", "client.js"));
|
|
227
246
|
|
|
228
247
|
const minified = minifySync("_core.js", CORE_CODE);
|
|
229
|
-
if (minified.errors?.length)
|
|
230
|
-
console.error("Minification errors (core):", minified.errors);
|
|
231
|
-
}
|
|
248
|
+
if (minified.errors?.length) console.error("Minification errors (core):", minified.errors);
|
|
232
249
|
|
|
233
250
|
const minifiedShared = minifySync("_core-shared.js", SHARED_CORE_CODE);
|
|
234
|
-
if (minifiedShared.errors?.length)
|
|
235
|
-
console.error("Minification errors (shared core):", minifiedShared.errors);
|
|
236
|
-
}
|
|
251
|
+
if (minifiedShared.errors?.length) console.error("Minification errors (shared core):", minifiedShared.errors);
|
|
237
252
|
|
|
238
|
-
// Generate a unique ID per build for cache-busting the core module path
|
|
239
253
|
const coreId = crypto.randomUUID();
|
|
240
254
|
|
|
241
|
-
// Write
|
|
255
|
+
// --- Write .export-types.js ---
|
|
256
|
+
|
|
242
257
|
const outPath = path.join(cwd, ".export-types.js");
|
|
243
258
|
fs.writeFileSync(outPath, [
|
|
244
|
-
`export default ${JSON.stringify(
|
|
259
|
+
`export default ${JSON.stringify(typesMap)};`,
|
|
245
260
|
`export const minifiedCore = ${JSON.stringify(minified.code)};`,
|
|
246
261
|
`export const minifiedSharedCore = ${JSON.stringify(minifiedShared.code)};`,
|
|
247
262
|
`export const coreId = ${JSON.stringify(coreId)};`,
|
|
248
263
|
].join("\n") + "\n");
|
|
249
264
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
// --- Write .export-module-map.js ---
|
|
266
|
+
|
|
267
|
+
const moduleMapPath = path.join(cwd, ".export-module-map.js");
|
|
268
|
+
const relSrcDir = path.relative(cwd, srcDir);
|
|
269
|
+
const mapLines = [];
|
|
270
|
+
modules.forEach((mod, i) => {
|
|
271
|
+
const relFile = "./" + path.relative(cwd, mod.filePath).replace(/\\/g, "/");
|
|
272
|
+
mapLines.push(`import * as _${i} from ${JSON.stringify(relFile)};`);
|
|
273
|
+
});
|
|
274
|
+
mapLines.push(`export default { ${modules.map((mod, i) => `${JSON.stringify(mod.routePath)}: _${i}`).join(", ")} };`);
|
|
275
|
+
fs.writeFileSync(moduleMapPath, mapLines.join("\n") + "\n");
|
|
276
|
+
|
|
277
|
+
// --- Write .export-shared.js ---
|
|
262
278
|
|
|
263
279
|
const sharedModulePath = path.join(cwd, ".export-shared.js");
|
|
264
|
-
const
|
|
280
|
+
const sharedLines = [
|
|
265
281
|
`import { env } from "cloudflare:workers";`,
|
|
266
282
|
``,
|
|
267
283
|
`const getStub = (room = "default") =>`,
|
|
@@ -298,10 +314,20 @@ const sharedModuleLines = [
|
|
|
298
314
|
` });`,
|
|
299
315
|
``,
|
|
300
316
|
`const _stub = getStub();`,
|
|
301
|
-
...exportNames.map(n => `export const ${n} = createSharedProxy(_stub, [${JSON.stringify(n)}]);`),
|
|
302
|
-
`export { getStub };`,
|
|
303
317
|
];
|
|
304
|
-
|
|
318
|
+
// Generate proxies for all modules' exports, prefixed with route
|
|
319
|
+
for (const mod of modules) {
|
|
320
|
+
const names = exportsMap[mod.routePath] || [];
|
|
321
|
+
for (const n of names) {
|
|
322
|
+
const proxyPath = mod.routePath ? `[${JSON.stringify(mod.routePath)}, ${JSON.stringify(n)}]` : `[${JSON.stringify("")}, ${JSON.stringify(n)}]`;
|
|
323
|
+
const exportAlias = mod.routePath ? `${mod.routePath.replace(/\//g, "_")}_${n}` : n;
|
|
324
|
+
sharedLines.push(`export const ${exportAlias} = createSharedProxy(_stub, ${proxyPath});`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
sharedLines.push(`export { getStub };`);
|
|
328
|
+
fs.writeFileSync(sharedModulePath, sharedLines.join("\n") + "\n");
|
|
305
329
|
|
|
330
|
+
console.log(`Discovered ${modules.length} module(s): ${modules.map(m => m.routePath || "/").join(", ")}`);
|
|
306
331
|
console.log("Generated type definitions + minified core →", outPath);
|
|
332
|
+
console.log("Generated module map →", moduleMapPath);
|
|
307
333
|
console.log("Generated shared import module →", sharedModulePath);
|
package/client.js
CHANGED
|
@@ -135,14 +135,6 @@ const sendRequest = async (msg) => {
|
|
|
135
135
|
});
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
const makeWritable = (writableId) => new WritableStream({
|
|
139
|
-
async write(chunk) {
|
|
140
|
-
await sendRequest({ type: "writable-write", writableId, chunk: chunk instanceof Uint8Array ? Array.from(chunk) : chunk });
|
|
141
|
-
},
|
|
142
|
-
async close() { await sendRequest({ type: "writable-close", writableId }); },
|
|
143
|
-
async abort() { await sendRequest({ type: "writable-abort", writableId }); }
|
|
144
|
-
});
|
|
145
|
-
|
|
146
138
|
ws.onmessage = (event) => {
|
|
147
139
|
const msg = parse(event.data);
|
|
148
140
|
const resolver = pending.get(msg.id);
|
|
@@ -166,7 +158,6 @@ ws.onmessage = (event) => {
|
|
|
166
158
|
},
|
|
167
159
|
cancel: () => sendRequest({ type: "stream-cancel", streamId: msg.streamId })
|
|
168
160
|
}));
|
|
169
|
-
else if (msg.valueType === "writablestream") resolver.resolve(makeWritable(msg.writableId));
|
|
170
161
|
else resolver.resolve(msg.value);
|
|
171
162
|
} else if (msg.type === "iterate-result") {
|
|
172
163
|
resolver.resolve({ value: msg.value, done: msg.done });
|
|
@@ -199,11 +190,6 @@ export const createProxy = (path = []) => new Proxy(function(){}, {
|
|
|
199
190
|
async apply(_, __, args) { return sendRequest({ type: "call", path, args }); },
|
|
200
191
|
construct(_, args) { return sendRequest({ type: "construct", path, args }); }
|
|
201
192
|
});
|
|
202
|
-
|
|
203
|
-
export const createUploadStream = async () => {
|
|
204
|
-
const { writableId } = await sendRequest({ type: "writable-create" });
|
|
205
|
-
return { stream: makeWritable(writableId), writableId };
|
|
206
|
-
};
|
|
207
193
|
`;
|
|
208
194
|
|
|
209
195
|
export const CORE_CODE = CORE_TEMPLATE.replace("__WS_SUFFIX__", "./");
|
package/entry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import moduleMap from "__USER_MODULE__";
|
|
2
2
|
import generatedTypes, { minifiedCore, minifiedSharedCore, coreId } from "__GENERATED_TYPES__";
|
|
3
3
|
import { createHandler } from "./handler.js";
|
|
4
4
|
export { SharedExportDO } from "./shared-do.js";
|
|
5
5
|
|
|
6
|
-
export default createHandler(
|
|
6
|
+
export default createHandler(moduleMap, generatedTypes, minifiedCore, coreId, minifiedSharedCore);
|
package/handler.js
CHANGED
|
@@ -13,28 +13,61 @@ const jsResponse = (body, extra = {}) =>
|
|
|
13
13
|
const tsResponse = (body, status = 200) =>
|
|
14
14
|
new Response(body, { status, headers: { "Content-Type": TS, ...CORS, "Cache-Control": "no-cache" } });
|
|
15
15
|
|
|
16
|
-
export const createHandler = (
|
|
17
|
-
|
|
16
|
+
export const createHandler = (moduleMap, generatedTypes, minifiedCore, coreId, minifiedSharedCore) => {
|
|
17
|
+
// moduleMap: { routePath: moduleNamespace, ... }
|
|
18
|
+
const moduleRoutes = Object.keys(moduleMap); // e.g. ["", "greet", "utils/math"]
|
|
19
|
+
const moduleExportKeys = {};
|
|
20
|
+
for (const [route, mod] of Object.entries(moduleMap)) {
|
|
21
|
+
moduleExportKeys[route] = Object.keys(mod);
|
|
22
|
+
}
|
|
18
23
|
|
|
19
24
|
const coreModuleCode = minifiedCore || CORE_CODE;
|
|
20
25
|
const sharedCoreModuleCode = minifiedSharedCore || SHARED_CORE_CODE;
|
|
21
26
|
const corePath = `/${coreId || crypto.randomUUID()}.js`;
|
|
22
27
|
const sharedCorePath = corePath.replace(".js", "-shared.js");
|
|
23
28
|
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
.join("\n");
|
|
29
|
+
// Resolve a URL pathname to { route, exportName } or null
|
|
30
|
+
const resolveRoute = (pathname) => {
|
|
31
|
+
const p = pathname === "/" ? "" : pathname.slice(1);
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// Exact module match: /greet → route "greet", /utils/math → route "utils/math"
|
|
34
|
+
if (moduleRoutes.includes(p)) {
|
|
35
|
+
return { route: p, exportName: null };
|
|
36
|
+
}
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
// Try parent as module, last segment as export: /greet/foo → route "greet", export "foo"
|
|
39
|
+
const lastSlash = p.lastIndexOf("/");
|
|
40
|
+
if (lastSlash > 0) {
|
|
41
|
+
const parentRoute = p.slice(0, lastSlash);
|
|
42
|
+
const name = p.slice(lastSlash + 1);
|
|
43
|
+
if (moduleRoutes.includes(parentRoute) && moduleExportKeys[parentRoute]?.includes(name)) {
|
|
44
|
+
return { route: parentRoute, exportName: name };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Root module export: /greet → route "", export "greet" (only if no module named "greet")
|
|
49
|
+
if (moduleExportKeys[""]?.includes(p) && !p.includes("/")) {
|
|
50
|
+
return { route: "", exportName: p };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const buildIndexModule = (cpath, route) => {
|
|
57
|
+
const keys = moduleExportKeys[route] || [];
|
|
58
|
+
const namedExports = keys
|
|
59
|
+
.map((key) => `export const ${key} = createProxy([${JSON.stringify(route)}, ${JSON.stringify(key)}]);`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
return `import { createProxy } from ".${cpath}";\n${namedExports}`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const buildExportModule = (cpath, route, name) =>
|
|
65
|
+
`import { createProxy } from ".${cpath}";\n` +
|
|
66
|
+
`const _export = createProxy([${JSON.stringify(route)}, ${JSON.stringify(name)}]);\n` +
|
|
67
|
+
`export default _export;\nexport { _export as ${name} };`;
|
|
34
68
|
|
|
35
|
-
// Dispatch a parsed devalue message to an RPC dispatcher
|
|
36
69
|
const dispatchMessage = async (dispatcher, msg) => {
|
|
37
|
-
const { type, path = [], args = [], instanceId, iteratorId, streamId
|
|
70
|
+
const { type, path = [], args = [], instanceId, iteratorId, streamId } = msg;
|
|
38
71
|
switch (type) {
|
|
39
72
|
case "ping": return { type: "pong" };
|
|
40
73
|
case "call":
|
|
@@ -49,10 +82,6 @@ export const createHandler = (exports, generatedTypes, minifiedCore, coreId, min
|
|
|
49
82
|
case "iterate-return": return dispatcher.rpcIterateReturn(iteratorId);
|
|
50
83
|
case "stream-read": return dispatcher.rpcStreamRead(streamId);
|
|
51
84
|
case "stream-cancel": return dispatcher.rpcStreamCancel(streamId);
|
|
52
|
-
case "writable-create": return dispatcher.rpcWritableCreate();
|
|
53
|
-
case "writable-write": return dispatcher.rpcWritableWrite(writableId, chunk);
|
|
54
|
-
case "writable-close": return dispatcher.rpcWritableClose(writableId);
|
|
55
|
-
case "writable-abort": return dispatcher.rpcWritableAbort(writableId);
|
|
56
85
|
}
|
|
57
86
|
};
|
|
58
87
|
|
|
@@ -87,7 +116,7 @@ export const createHandler = (exports, generatedTypes, minifiedCore, coreId, min
|
|
|
87
116
|
const stub = env.SHARED_EXPORT.get(env.SHARED_EXPORT.idFromName(room));
|
|
88
117
|
wireWebSocket(server, stub);
|
|
89
118
|
} else {
|
|
90
|
-
const dispatcher = createRpcDispatcher(
|
|
119
|
+
const dispatcher = createRpcDispatcher(moduleMap);
|
|
91
120
|
wireWebSocket(server, dispatcher, () => dispatcher.clearAll());
|
|
92
121
|
}
|
|
93
122
|
|
|
@@ -97,41 +126,52 @@ export const createHandler = (exports, generatedTypes, minifiedCore, coreId, min
|
|
|
97
126
|
// --- HTTP routing ---
|
|
98
127
|
const pathname = url.pathname;
|
|
99
128
|
|
|
100
|
-
// Core modules
|
|
129
|
+
// Core modules
|
|
101
130
|
if (pathname === corePath) return jsResponse(coreModuleCode, { "Cache-Control": IMMUTABLE });
|
|
102
131
|
if (pathname === sharedCorePath) return jsResponse(sharedCoreModuleCode, { "Cache-Control": IMMUTABLE });
|
|
103
132
|
|
|
104
133
|
// Type definitions
|
|
105
134
|
if (url.searchParams.has("types")) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
const p = pathname === "/" ? "" : pathname.slice(1);
|
|
136
|
+
// Module types
|
|
137
|
+
if (generatedTypes?.[p] !== undefined) {
|
|
138
|
+
return tsResponse(generatedTypes[p]);
|
|
139
|
+
}
|
|
140
|
+
// Per-export re-export
|
|
141
|
+
const resolved = resolveRoute(pathname);
|
|
142
|
+
if (resolved?.exportName) {
|
|
143
|
+
const routeTypesPath = resolved.route ? `./${resolved.route}?types` : "./?types";
|
|
144
|
+
const code = `export { ${resolved.exportName} as default, ${resolved.exportName} } from "${routeTypesPath}";`;
|
|
145
|
+
return tsResponse(code);
|
|
146
|
+
}
|
|
147
|
+
return tsResponse("// Not found", 404);
|
|
148
|
+
}
|
|
149
|
+
if (pathname.endsWith(".d.ts")) {
|
|
150
|
+
return tsResponse(generatedTypes?.[""] || "");
|
|
111
151
|
}
|
|
112
|
-
if (pathname.endsWith(".d.ts")) return tsResponse(generatedTypes || "");
|
|
113
152
|
|
|
114
153
|
const baseUrl = `${url.protocol}//${url.host}`;
|
|
115
154
|
const cpath = isShared ? sharedCorePath : corePath;
|
|
116
155
|
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"X-TypeScript-Types": `${baseUrl}/?types`,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
156
|
+
const resolved = resolveRoute(pathname);
|
|
157
|
+
if (!resolved) return new Response("Not found", { status: 404 });
|
|
158
|
+
|
|
159
|
+
const { route, exportName } = resolved;
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return jsResponse(buildExportModule(cpath, exportName), {
|
|
161
|
+
if (exportName) {
|
|
162
|
+
// Per-export module
|
|
163
|
+
return jsResponse(buildExportModule(cpath, route, exportName), {
|
|
129
164
|
"Cache-Control": "no-cache",
|
|
130
|
-
"X-TypeScript-Types": `${baseUrl}
|
|
165
|
+
"X-TypeScript-Types": `${baseUrl}${pathname}?types`,
|
|
131
166
|
});
|
|
132
167
|
}
|
|
133
168
|
|
|
134
|
-
|
|
169
|
+
// Module index
|
|
170
|
+
const typesPath = route ? `${baseUrl}/${route}?types` : `${baseUrl}/?types`;
|
|
171
|
+
return jsResponse(buildIndexModule(cpath, route), {
|
|
172
|
+
"Cache-Control": "no-cache",
|
|
173
|
+
"X-TypeScript-Types": typesPath,
|
|
174
|
+
});
|
|
135
175
|
},
|
|
136
176
|
};
|
|
137
177
|
};
|
package/package.json
CHANGED
package/rpc.js
CHANGED
|
@@ -19,14 +19,24 @@ export const isClass = (fn) =>
|
|
|
19
19
|
export const RPC_METHODS = [
|
|
20
20
|
"rpcCall", "rpcConstruct", "rpcInstanceCall", "rpcGet", "rpcSet", "rpcRelease",
|
|
21
21
|
"rpcIterateNext", "rpcIterateReturn", "rpcStreamRead", "rpcStreamCancel",
|
|
22
|
-
"rpcWritableCreate", "rpcWritableWrite", "rpcWritableClose", "rpcWritableAbort",
|
|
23
22
|
];
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
// moduleMap: { routePath: moduleNamespace, ... } or a single namespace (backward compat)
|
|
25
|
+
export function createRpcDispatcher(moduleMap) {
|
|
26
|
+
// Normalize: if moduleMap has no "" key and looks like a namespace, wrap it
|
|
27
|
+
const isMap = typeof moduleMap === "object" && !moduleMap.__esModule && "" in moduleMap;
|
|
28
|
+
const resolveModule = (route) => {
|
|
29
|
+
if (isMap) {
|
|
30
|
+
const mod = moduleMap[route];
|
|
31
|
+
if (!mod) throw new Error(`Module not found: ${route}`);
|
|
32
|
+
return mod;
|
|
33
|
+
}
|
|
34
|
+
return moduleMap; // single namespace fallback
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
const instances = new Map();
|
|
27
38
|
const iterators = new Map();
|
|
28
39
|
const streams = new Map();
|
|
29
|
-
const writables = new Map();
|
|
30
40
|
let nextId = 1;
|
|
31
41
|
|
|
32
42
|
const requireInstance = (id) => {
|
|
@@ -52,19 +62,29 @@ export function createRpcDispatcher(exports) {
|
|
|
52
62
|
return { type: "result", value: result };
|
|
53
63
|
};
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
// path = [route, ...exportPath] — route selects the module, exportPath walks its exports
|
|
66
|
+
const splitPath = (path) => {
|
|
67
|
+
const [route, ...rest] = path;
|
|
68
|
+
return { exports: resolveModule(route), exportPath: rest };
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const callTarget = async (obj, path, args, isRoot = false) => {
|
|
56
72
|
const target = getByPath(obj, path);
|
|
57
|
-
const thisArg = path.length > 1 ? getByPath(obj, path.slice(0, -1)) : (
|
|
73
|
+
const thisArg = path.length > 1 ? getByPath(obj, path.slice(0, -1)) : (isRoot ? undefined : obj);
|
|
58
74
|
if (typeof target !== "function") throw new Error(`${path.join(".")} is not a function`);
|
|
59
75
|
return wrapResult(await target.apply(thisArg, args), path);
|
|
60
76
|
};
|
|
61
77
|
|
|
62
78
|
return {
|
|
63
|
-
rpcCall
|
|
79
|
+
rpcCall(path, args = []) {
|
|
80
|
+
const { exports, exportPath } = splitPath(path);
|
|
81
|
+
return callTarget(exports, exportPath, args, true);
|
|
82
|
+
},
|
|
64
83
|
|
|
65
84
|
async rpcConstruct(path, args = []) {
|
|
66
|
-
const
|
|
67
|
-
|
|
85
|
+
const { exports, exportPath } = splitPath(path);
|
|
86
|
+
const Ctor = getByPath(exports, exportPath);
|
|
87
|
+
if (!isClass(Ctor)) throw new Error(`${exportPath.join(".")} is not a class`);
|
|
68
88
|
const id = nextId++;
|
|
69
89
|
instances.set(id, new Ctor(...args));
|
|
70
90
|
return { type: "result", instanceId: id, valueType: "instance" };
|
|
@@ -126,36 +146,10 @@ export function createRpcDispatcher(exports) {
|
|
|
126
146
|
return { type: "result", value: true };
|
|
127
147
|
},
|
|
128
148
|
|
|
129
|
-
async rpcWritableCreate() {
|
|
130
|
-
const id = nextId++;
|
|
131
|
-
writables.set(id, { chunks: [] });
|
|
132
|
-
return { type: "result", writableId: id, valueType: "writablestream" };
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
async rpcWritableWrite(writableId, chunk) {
|
|
136
|
-
const entry = writables.get(writableId);
|
|
137
|
-
if (!entry) throw new Error("WritableStream not found");
|
|
138
|
-
entry.chunks.push(Array.isArray(chunk) ? new Uint8Array(chunk) : chunk);
|
|
139
|
-
return { type: "result", value: true };
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
async rpcWritableClose(writableId) {
|
|
143
|
-
const entry = writables.get(writableId);
|
|
144
|
-
if (!entry) throw new Error("WritableStream not found");
|
|
145
|
-
writables.delete(writableId);
|
|
146
|
-
return { type: "result", value: entry.chunks };
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
async rpcWritableAbort(writableId) {
|
|
150
|
-
writables.delete(writableId);
|
|
151
|
-
return { type: "result", value: true };
|
|
152
|
-
},
|
|
153
|
-
|
|
154
149
|
clearAll() {
|
|
155
150
|
instances.clear();
|
|
156
151
|
iterators.clear();
|
|
157
152
|
streams.clear();
|
|
158
|
-
writables.clear();
|
|
159
153
|
},
|
|
160
154
|
};
|
|
161
155
|
}
|
package/shared-do.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { DurableObject } from "cloudflare:workers";
|
|
2
|
-
import
|
|
2
|
+
import moduleMap from "__USER_MODULE__";
|
|
3
3
|
import { createRpcDispatcher } from "./rpc.js";
|
|
4
4
|
|
|
5
5
|
export class SharedExportDO extends DurableObject {
|
|
6
6
|
#d;
|
|
7
7
|
constructor(ctx, env) {
|
|
8
8
|
super(ctx, env);
|
|
9
|
-
this.#d = createRpcDispatcher(
|
|
9
|
+
this.#d = createRpcDispatcher(moduleMap);
|
|
10
10
|
}
|
|
11
11
|
rpcCall(p, a) { return this.#d.rpcCall(p, a); }
|
|
12
12
|
rpcConstruct(p, a) { return this.#d.rpcConstruct(p, a); }
|
|
@@ -18,8 +18,4 @@ export class SharedExportDO extends DurableObject {
|
|
|
18
18
|
rpcIterateReturn(i) { return this.#d.rpcIterateReturn(i); }
|
|
19
19
|
rpcStreamRead(s) { return this.#d.rpcStreamRead(s); }
|
|
20
20
|
rpcStreamCancel(s) { return this.#d.rpcStreamCancel(s); }
|
|
21
|
-
rpcWritableCreate() { return this.#d.rpcWritableCreate(); }
|
|
22
|
-
rpcWritableWrite(w, c) { return this.#d.rpcWritableWrite(w, c); }
|
|
23
|
-
rpcWritableClose(w) { return this.#d.rpcWritableClose(w); }
|
|
24
|
-
rpcWritableAbort(w) { return this.#d.rpcWritableAbort(w); }
|
|
25
21
|
}
|