sproutboat 0.7.0 → 0.8.0

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/src/wrap.ts CHANGED
@@ -13,6 +13,23 @@
13
13
  * imported — callers do `readFile(preludePath, "utf8")`. */
14
14
  export const preludePath = new URL("./native-fetch-prelude.js", import.meta.url);
15
15
 
16
+ /**
17
+ * #15 — the two transports the prelude can be built with.
18
+ *
19
+ * Both define `__sbCall(reqJson) -> replyJson` and nothing else; every binding
20
+ * shim above that line is identical, which is what lets one conformance suite
21
+ * hold both honest. `broker` talks to the per-deployment broker over loopback
22
+ * (deployed, dev, phase-0 standalone); `embedded` compiles SQLite into the
23
+ * sprout and needs no second process at all.
24
+ */
25
+ export type Transport = "broker" | "embedded";
26
+ export const transportPath = (transport: Transport): URL =>
27
+ new URL(transport === "embedded" ? "./transport-embedded.js" : "./transport-broker.js", import.meta.url);
28
+
29
+ /** Where the prelude expects its transport spliced in. */
30
+ export const TRANSPORT_MARKER =
31
+ "// TRANSPORT: wrap.ts splices one of transport-broker.js / transport-embedded.js here.";
32
+
16
33
  // The server honours $PORT at runtime (patches/porffor-render.patch); this baked
17
34
  // value is only a fallback for a directly-run binary.
18
35
  const DEFAULT_PORT = 8080;
@@ -44,6 +61,9 @@ export type Bindings = {
44
61
  queues: string[];
45
62
  analytics: string[];
46
63
  do: Array<{ binding: string; className: string }>;
64
+ /** #48 — worker-to-worker: binding name -> the project it calls. The hostname
65
+ * it resolves to is a runtime input, not part of the artifact. */
66
+ services: Array<{ binding: string; service: string }>;
47
67
  crons: string[];
48
68
  /** Static-asset binding name for `env.<NAME>.fetch(request)`; `""` when assets are edge-only. */
49
69
  assets: string;
@@ -58,6 +78,7 @@ export const EMPTY_BINDINGS: Bindings = {
58
78
  queues: [],
59
79
  analytics: [],
60
80
  do: [],
81
+ services: [],
61
82
  crons: [],
62
83
  assets: "",
63
84
  };
@@ -72,6 +93,7 @@ function hasBindings(b: Bindings): boolean {
72
93
  b.queues.length > 0 ||
73
94
  b.analytics.length > 0 ||
74
95
  b.do.length > 0 ||
96
+ b.services.length > 0 ||
75
97
  b.assets !== ""
76
98
  );
77
99
  }
@@ -142,6 +164,9 @@ export function wrapNativeFetchHandler(
142
164
  bindings: Bindings = EMPTY_BINDINGS,
143
165
  port: number = DEFAULT_PORT,
144
166
  compatibilityDate: string = BASELINE_COMPATIBILITY_DATE,
167
+ appName: string = "app",
168
+ /** #15 — assets baked into the module for a binary that has no files beside it. */
169
+ assets?: { manifest: unknown; files: Record<string, string> },
145
170
  ): string {
146
171
  const neutralised = neutraliseExports(source);
147
172
  if (neutralised === null || !/\bfetch\s*\(/.test(source)) {
@@ -151,16 +176,26 @@ export function wrapNativeFetchHandler(
151
176
  const env = `const env = ${JSON.stringify(vars)};\nglobalThis.env = env;\n`;
152
177
  // Baked, not a binding: the date belongs to the artifact, and a handler must
153
178
  // not be able to change the semantics it was compiled against at runtime.
154
- const compat = `globalThis.__sbCompat = ${JSON.stringify(compatibilityDate)};\n`;
179
+ const compat =
180
+ `globalThis.__sbCompat = ${JSON.stringify(compatibilityDate)};\n` +
181
+ // #15 — the embedded transport derives its default data directory from this.
182
+ `globalThis.__sbAppName = ${JSON.stringify(appName)};\n` +
183
+ // #15 — and enforces the outbound allowlist itself, with no broker to do it.
184
+ `globalThis.__sbOutbound = ${JSON.stringify(bindings.outbound)};\n` +
185
+ (assets ? `globalThis.__sbAssets = ${JSON.stringify(assets)};\n` : "");
155
186
  const wire = hasBindings(bindings) ? `__sbInstallBindings(env, ${JSON.stringify(bindings)});\n` : "";
156
187
  const registerDO = bindings.do.length
157
188
  ? `__sbRegisterDO({ ${bindings.do.map((d) => `${d.className}: ${d.className}`).join(", ")} });\n`
158
189
  : "";
190
+ // Cron / queue / alarm timers, for a transport that has no broker to deliver
191
+ // them. The broker transport defines this as a no-op, so the emitted module
192
+ // is the same either way.
193
+ const triggers = hasBindings(bindings) ? `__sbStartLocalTriggers(__sbHandlers, ${JSON.stringify(bindings)});\n` : "";
159
194
 
160
195
  return (
161
196
  `${prelude}\n${compat}${env}${wire}` +
162
197
  `${neutralised}\n` +
163
- `${registerDO}` +
198
+ `${registerDO}${triggers}` +
164
199
  `export default {\n port: ${port},\n fetch(request) { return __sbEntry(__sbHandlers, request); }\n};\n`
165
200
  );
166
201
  }
@@ -200,6 +235,14 @@ export function readBindingsFromEnv(): Bindings {
200
235
  const parsed: VarsJson = JSON.parse(raw);
201
236
  if (!isVarsObject(parsed)) throw new Error("SPROUTBOAT_BINDINGS_JSON must be a JSON object");
202
237
  const strings = (v: VarsJson): string[] => (Array.isArray(v) ? v.filter(isVarsString) : []);
238
+ const services: Array<{ binding: string; service: string }> = [];
239
+ if (Array.isArray(parsed.services)) {
240
+ for (const entry of parsed.services) {
241
+ if (isVarsObject(entry) && isVarsString(entry.binding) && isVarsString(entry.service)) {
242
+ services.push({ binding: entry.binding, service: entry.service });
243
+ }
244
+ }
245
+ }
203
246
  const dos: Array<{ binding: string; className: string }> = [];
204
247
  if (Array.isArray(parsed.do)) {
205
248
  for (const entry of parsed.do) {
@@ -217,6 +260,7 @@ export function readBindingsFromEnv(): Bindings {
217
260
  queues: strings(parsed.queues),
218
261
  analytics: strings(parsed.analytics),
219
262
  do: dos,
263
+ services,
220
264
  crons: strings(parsed.crons),
221
265
  assets: isVarsString(parsed.assets) ? parsed.assets : "",
222
266
  };