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.
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
9
9
 
10
10
  const cwd = process.cwd();
11
11
 
12
- // Read wrangler.toml to find user module path
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
- const userModulePath = path.resolve(cwd, aliasMatch[1]);
26
- if (!fs.existsSync(userModulePath)) {
27
- console.error("User module not found:", userModulePath);
28
- process.exit(1);
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 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;
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
- 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(" & ");
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
- return extractType(ta.typeAnnotation);
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
- // --- Generate .d.ts ---
150
+ // --- Extract types and export names from a single file ---
139
151
 
140
- const lines = [
141
- "// Auto-generated type definitions (oxc-parser)",
142
- "// All functions are async over the network",
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
- 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};`);
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
- 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;`);
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
- // 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("}>;");
231
+ // --- Process all modules ---
232
+
233
+ const typesMap = {}; // routePath → type definition string
234
+ const exportsMap = {}; // routePath → export names array
220
235
 
221
- const typeDefinitions = lines.join("\n");
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 as a JS module
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(typeDefinitions)};`,
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
- // Generate Worker-side shared import module (.export-shared.js)
251
- const exportNames = [];
252
- for (const node of program.body) {
253
- if (node.type !== "ExportNamedDeclaration" || !node.declaration) continue;
254
- const decl = node.declaration;
255
- if (decl.id?.name) exportNames.push(decl.id.name);
256
- else if (decl.declarations) {
257
- for (const d of decl.declarations) {
258
- if (d.id?.name) exportNames.push(d.id.name);
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 sharedModuleLines = [
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
- fs.writeFileSync(sharedModulePath, sharedModuleLines.join("\n") + "\n");
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 * as userExports from "__USER_MODULE__";
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(userExports, generatedTypes, minifiedCore, coreId, minifiedSharedCore);
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 = (exports, generatedTypes, minifiedCore, coreId, minifiedSharedCore) => {
17
- const exportKeys = Object.keys(exports);
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
- // Pre-generate the named exports string (same for shared and normal, only import source differs)
25
- const namedExportsCode = exportKeys
26
- .map((key) => `export const ${key} = createProxy([${JSON.stringify(key)}]);`)
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
- const buildIndexModule = (cpath) =>
30
- `import { createProxy, createUploadStream } from ".${cpath}";\n${namedExportsCode}\nexport { createUploadStream };`;
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
- const buildExportModule = (cpath, name) =>
33
- `import { createProxy } from ".${cpath}";\nconst _export = createProxy([${JSON.stringify(name)}]);\nexport default _export;\nexport { _export as ${name} };`;
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, writableId, chunk } = msg;
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(exports);
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 (cached immutably)
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
- if (pathname === "/") return tsResponse(generatedTypes || "");
107
- const name = pathname.slice(1);
108
- return exportKeys.includes(name)
109
- ? tsResponse(`export { ${name} as default, ${name} } from "./?types";`)
110
- : tsResponse("// Export not found", 404);
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
- // Root module
118
- if (pathname === "/") {
119
- return jsResponse(buildIndexModule(cpath), {
120
- "Cache-Control": "no-cache",
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
- // Per-export module
126
- const exportName = pathname.slice(1);
127
- if (exportKeys.includes(exportName)) {
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}/${exportName}?types`,
165
+ "X-TypeScript-Types": `${baseUrl}${pathname}?types`,
131
166
  });
132
167
  }
133
168
 
134
- return new Response("Not found", { status: 404 });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "export-runtime",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Cloudflare Workers ESM Export Framework Runtime",
5
5
  "keywords": [
6
6
  "cloudflare",
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
- export function createRpcDispatcher(exports) {
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
- const callTarget = async (obj, path, args) => {
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)) : (obj === exports ? undefined : obj);
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: (path, args = []) => callTarget(exports, path, args),
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 Ctor = getByPath(exports, path);
67
- if (!isClass(Ctor)) throw new Error(`${path.join(".")} is not a class`);
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 * as userExports from "__USER_MODULE__";
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(userExports);
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
  }