@sproutboat/runtime 0.13.3 → 0.13.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sproutboat/runtime
2
2
 
3
+ ## 0.13.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 5bf4ea6: The handler bundle runs in its own function scope inside the generated module (part of baronunread/sproutboat#238). It used to be spliced into the prelude's top level, and Porffor resolves top-level names module-wide, so a bundle with its own top-level `const URL = ...` (uuid exports one) replaced the `URL` class the prelude extends and the binary died at startup with `Cannot get property of null`. Static imports from a direct-ESM handler stay at module top level.
8
+
3
9
  ## 0.13.3
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/runtime",
3
- "version": "0.13.3",
3
+ "version": "0.13.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
package/src/wrap.ts CHANGED
@@ -228,11 +228,21 @@ export function wrapNativeFetchHandler(
228
228
  ? `__sbStartLocalTriggers(__sbHandlers, ${JSON.stringify(bindings)});\n`
229
229
  : "";
230
230
 
231
+ // baronunread/sproutboat#238: the handler runs in its own function scope.
232
+ // Spliced in at the prelude's top level, a bundle's own top-level
233
+ // `const URL = ...` (uuid exports one) replaced the URL class for the whole
234
+ // module, prelude included, and the binary died at startup. Porffor resolves
235
+ // a top-level name module-wide, so a block is not enough; a function is.
236
+ // Static imports (a direct-ESM handler, no bundler in front) are only legal
237
+ // at module top level, so they stay outside the function.
238
+ const importPattern = /^import\s(?:[\s\S]*?\sfrom\s*)?["'][^"'\n]+["'];?[ \t]*$/gm;
239
+ const imports = (neutralised.match(importPattern) ?? []).join("\n");
240
+ const body = neutralised.replace(importPattern, "");
231
241
  return (
232
242
  `${prelude}\n${nativeTransferAbi}${compat}${env}${versionMetadataLine}${wire}` +
233
- `${neutralised}\n` +
234
- `${registerDO}${triggers}` +
235
- `export default {\n port: ${port},\n fetch(request) { return __sbEntry(__sbHandlers, request); }\n};\n`
243
+ `${imports ? `${imports}\n` : ""}const __sbUserHandlers = (() => {\n${body}\n` +
244
+ `${registerDO}${triggers}return __sbHandlers;\n})();\n` +
245
+ `export default {\n port: ${port},\n fetch(request) { return __sbEntry(__sbUserHandlers, request); }\n};\n`
236
246
  );
237
247
  }
238
248
 
@@ -12,7 +12,10 @@ import { EMPTY_BINDINGS, preludePath, TRANSPORT_MARKER, transportPath, wrapNativ
12
12
  // two hot paths (KV, D1) and a non-ASCII SQLite error message.
13
13
  const text = "/café/日本/😀/x";
14
14
 
15
+ // #238: the handler's own top-level \`URL\` (uuid exports one) must not replace
16
+ // the URL class the prelude extends; the handler runs in its own scope.
15
17
  const handler = `
18
+ const URL = "shadowed";
16
19
  export default {
17
20
  async fetch() {
18
21
  await env.KV.put("k", ${JSON.stringify(text)});
@@ -22,7 +25,7 @@ export default {
22
25
  const d1 = (await env.DB.prepare("SELECT v FROM t").first()).v;
23
26
  let error = "";
24
27
  try { await env.DB.prepare("SELECT * FROM tabellà").first(); } catch (e) { error = String(e.message); }
25
- return Response.json({ kv, kvLength: kv.length, d1, d1Length: d1.length, error });
28
+ return Response.json({ kv, kvLength: kv.length, d1, d1Length: d1.length, error, url: URL });
26
29
  },
27
30
  };
28
31
  `;
@@ -96,13 +99,21 @@ try {
96
99
  assert.equal(response.status, 200);
97
100
  // SAFETY: the handler above is the only producer of this body and always
98
101
  // returns exactly these five fields; deepEqual below checks every one.
99
- const body = (await response.json()) as { kv: string; kvLength: number; d1: string; d1Length: number; error: string };
102
+ const body = (await response.json()) as {
103
+ kv: string;
104
+ kvLength: number;
105
+ d1: string;
106
+ d1Length: number;
107
+ error: string;
108
+ url: string;
109
+ };
100
110
  assert.deepEqual(
101
111
  { kv: body.kv, kvLength: body.kvLength, d1: body.d1, d1Length: body.d1Length },
102
112
  { kv: text, kvLength: text.length, d1: text, d1Length: text.length },
103
113
  );
104
114
  assert.match(body.error, /tabellà/);
105
- console.log("Non-ASCII KV, D1 and error text round-trip through the broker");
115
+ assert.equal(body.url, "shadowed");
116
+ console.log("Non-ASCII KV, D1 and error text round-trip through the broker; a handler-level URL stays scoped");
106
117
  } finally {
107
118
  if (server) {
108
119
  server.kill(9);