export-runtime 0.0.8 → 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 +150 -120
- package/entry.js +2 -2
- package/handler.js +76 -32
- package/package.json +1 -1
- package/rpc.js +28 -6
- package/shared-do.js +2 -2
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,105 +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
|
|
|
231
|
+
// --- Process all modules ---
|
|
215
232
|
|
|
233
|
+
const typesMap = {}; // routePath → type definition string
|
|
234
|
+
const exportsMap = {}; // routePath → export names array
|
|
216
235
|
|
|
217
|
-
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
|
+
}
|
|
218
241
|
|
|
219
242
|
// --- Minify core modules ---
|
|
220
243
|
|
|
@@ -222,42 +245,39 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
222
245
|
const { CORE_CODE, SHARED_CORE_CODE } = await import(path.join(__dirname, "..", "client.js"));
|
|
223
246
|
|
|
224
247
|
const minified = minifySync("_core.js", CORE_CODE);
|
|
225
|
-
if (minified.errors?.length)
|
|
226
|
-
console.error("Minification errors (core):", minified.errors);
|
|
227
|
-
}
|
|
248
|
+
if (minified.errors?.length) console.error("Minification errors (core):", minified.errors);
|
|
228
249
|
|
|
229
250
|
const minifiedShared = minifySync("_core-shared.js", SHARED_CORE_CODE);
|
|
230
|
-
if (minifiedShared.errors?.length)
|
|
231
|
-
console.error("Minification errors (shared core):", minifiedShared.errors);
|
|
232
|
-
}
|
|
251
|
+
if (minifiedShared.errors?.length) console.error("Minification errors (shared core):", minifiedShared.errors);
|
|
233
252
|
|
|
234
|
-
// Generate a unique ID per build for cache-busting the core module path
|
|
235
253
|
const coreId = crypto.randomUUID();
|
|
236
254
|
|
|
237
|
-
// Write
|
|
255
|
+
// --- Write .export-types.js ---
|
|
256
|
+
|
|
238
257
|
const outPath = path.join(cwd, ".export-types.js");
|
|
239
258
|
fs.writeFileSync(outPath, [
|
|
240
|
-
`export default ${JSON.stringify(
|
|
259
|
+
`export default ${JSON.stringify(typesMap)};`,
|
|
241
260
|
`export const minifiedCore = ${JSON.stringify(minified.code)};`,
|
|
242
261
|
`export const minifiedSharedCore = ${JSON.stringify(minifiedShared.code)};`,
|
|
243
262
|
`export const coreId = ${JSON.stringify(coreId)};`,
|
|
244
263
|
].join("\n") + "\n");
|
|
245
264
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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 ---
|
|
258
278
|
|
|
259
279
|
const sharedModulePath = path.join(cwd, ".export-shared.js");
|
|
260
|
-
const
|
|
280
|
+
const sharedLines = [
|
|
261
281
|
`import { env } from "cloudflare:workers";`,
|
|
262
282
|
``,
|
|
263
283
|
`const getStub = (room = "default") =>`,
|
|
@@ -294,10 +314,20 @@ const sharedModuleLines = [
|
|
|
294
314
|
` });`,
|
|
295
315
|
``,
|
|
296
316
|
`const _stub = getStub();`,
|
|
297
|
-
...exportNames.map(n => `export const ${n} = createSharedProxy(_stub, [${JSON.stringify(n)}]);`),
|
|
298
|
-
`export { getStub };`,
|
|
299
317
|
];
|
|
300
|
-
|
|
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");
|
|
301
329
|
|
|
330
|
+
console.log(`Discovered ${modules.length} module(s): ${modules.map(m => m.routePath || "/").join(", ")}`);
|
|
302
331
|
console.log("Generated type definitions + minified core →", outPath);
|
|
332
|
+
console.log("Generated module map →", moduleMapPath);
|
|
303
333
|
console.log("Generated shared import module →", sharedModulePath);
|
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,26 +13,59 @@ 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
70
|
const { type, path = [], args = [], instanceId, iteratorId, streamId } = msg;
|
|
38
71
|
switch (type) {
|
|
@@ -83,7 +116,7 @@ export const createHandler = (exports, generatedTypes, minifiedCore, coreId, min
|
|
|
83
116
|
const stub = env.SHARED_EXPORT.get(env.SHARED_EXPORT.idFromName(room));
|
|
84
117
|
wireWebSocket(server, stub);
|
|
85
118
|
} else {
|
|
86
|
-
const dispatcher = createRpcDispatcher(
|
|
119
|
+
const dispatcher = createRpcDispatcher(moduleMap);
|
|
87
120
|
wireWebSocket(server, dispatcher, () => dispatcher.clearAll());
|
|
88
121
|
}
|
|
89
122
|
|
|
@@ -93,41 +126,52 @@ export const createHandler = (exports, generatedTypes, minifiedCore, coreId, min
|
|
|
93
126
|
// --- HTTP routing ---
|
|
94
127
|
const pathname = url.pathname;
|
|
95
128
|
|
|
96
|
-
// Core modules
|
|
129
|
+
// Core modules
|
|
97
130
|
if (pathname === corePath) return jsResponse(coreModuleCode, { "Cache-Control": IMMUTABLE });
|
|
98
131
|
if (pathname === sharedCorePath) return jsResponse(sharedCoreModuleCode, { "Cache-Control": IMMUTABLE });
|
|
99
132
|
|
|
100
133
|
// Type definitions
|
|
101
134
|
if (url.searchParams.has("types")) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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?.[""] || "");
|
|
107
151
|
}
|
|
108
|
-
if (pathname.endsWith(".d.ts")) return tsResponse(generatedTypes || "");
|
|
109
152
|
|
|
110
153
|
const baseUrl = `${url.protocol}//${url.host}`;
|
|
111
154
|
const cpath = isShared ? sharedCorePath : corePath;
|
|
112
155
|
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"X-TypeScript-Types": `${baseUrl}/?types`,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
156
|
+
const resolved = resolveRoute(pathname);
|
|
157
|
+
if (!resolved) return new Response("Not found", { status: 404 });
|
|
158
|
+
|
|
159
|
+
const { route, exportName } = resolved;
|
|
120
160
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return jsResponse(buildExportModule(cpath, exportName), {
|
|
161
|
+
if (exportName) {
|
|
162
|
+
// Per-export module
|
|
163
|
+
return jsResponse(buildExportModule(cpath, route, exportName), {
|
|
125
164
|
"Cache-Control": "no-cache",
|
|
126
|
-
"X-TypeScript-Types": `${baseUrl}
|
|
165
|
+
"X-TypeScript-Types": `${baseUrl}${pathname}?types`,
|
|
127
166
|
});
|
|
128
167
|
}
|
|
129
168
|
|
|
130
|
-
|
|
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
|
+
});
|
|
131
175
|
},
|
|
132
176
|
};
|
|
133
177
|
};
|
package/package.json
CHANGED
package/rpc.js
CHANGED
|
@@ -21,7 +21,19 @@ export const RPC_METHODS = [
|
|
|
21
21
|
"rpcIterateNext", "rpcIterateReturn", "rpcStreamRead", "rpcStreamCancel",
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
|
|
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
|
+
|
|
25
37
|
const instances = new Map();
|
|
26
38
|
const iterators = new Map();
|
|
27
39
|
const streams = new Map();
|
|
@@ -50,19 +62,29 @@ export function createRpcDispatcher(exports) {
|
|
|
50
62
|
return { type: "result", value: result };
|
|
51
63
|
};
|
|
52
64
|
|
|
53
|
-
|
|
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) => {
|
|
54
72
|
const target = getByPath(obj, path);
|
|
55
|
-
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);
|
|
56
74
|
if (typeof target !== "function") throw new Error(`${path.join(".")} is not a function`);
|
|
57
75
|
return wrapResult(await target.apply(thisArg, args), path);
|
|
58
76
|
};
|
|
59
77
|
|
|
60
78
|
return {
|
|
61
|
-
rpcCall
|
|
79
|
+
rpcCall(path, args = []) {
|
|
80
|
+
const { exports, exportPath } = splitPath(path);
|
|
81
|
+
return callTarget(exports, exportPath, args, true);
|
|
82
|
+
},
|
|
62
83
|
|
|
63
84
|
async rpcConstruct(path, args = []) {
|
|
64
|
-
const
|
|
65
|
-
|
|
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`);
|
|
66
88
|
const id = nextId++;
|
|
67
89
|
instances.set(id, new Ctor(...args));
|
|
68
90
|
return { type: "result", instanceId: id, valueType: "instance" };
|
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); }
|