@prisma/composer-prisma-cloud 0.3.0-dev.8 → 0.3.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.
Files changed (66) hide show
  1. package/dist/compute-main.mjs +4 -20
  2. package/dist/compute-main.mjs.map +1 -1
  3. package/dist/control.d.mts +1 -1
  4. package/dist/control.mjs +17 -28
  5. package/dist/control.mjs.map +1 -1
  6. package/dist/cron/index.d.mts +26 -33
  7. package/dist/cron/index.d.mts.map +1 -1
  8. package/dist/cron/index.mjs +77 -202
  9. package/dist/cron/index.mjs.map +1 -1
  10. package/dist/cron/scheduler-entrypoint.mjs +114 -133
  11. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  12. package/dist/cron/scheduler-service.mjs +76 -200
  13. package/dist/cron/scheduler-service.mjs.map +1 -1
  14. package/dist/email/email-entrypoint.mjs +126 -162
  15. package/dist/email/email-entrypoint.mjs.map +1 -1
  16. package/dist/email/email-service.mjs +79 -230
  17. package/dist/email/email-service.mjs.map +1 -1
  18. package/dist/email/index.d.mts +29 -33
  19. package/dist/email/index.d.mts.map +1 -1
  20. package/dist/email/index.mjs +84 -236
  21. package/dist/email/index.mjs.map +1 -1
  22. package/dist/email/testing.mjs +89 -112
  23. package/dist/email/testing.mjs.map +1 -1
  24. package/dist/index.d.mts +43 -51
  25. package/dist/index.d.mts.map +1 -1
  26. package/dist/index.mjs +51 -47
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/local-target.mjs +1 -1
  29. package/dist/local-target.mjs.map +1 -1
  30. package/dist/{secret-BVGxqHxY-DYDfiULl.mjs → param-BTzY0ne5-CW6ojZ3E.mjs} +4 -45
  31. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs.map +1 -0
  32. package/dist/{preflight-names-CpJUd9ZA-BCU-W3cT.mjs → preflight-names-BPL-qsZF-BfoRgbgb.mjs} +14 -30
  33. package/dist/{preflight-names-CpJUd9ZA-BCU-W3cT.mjs.map → preflight-names-BPL-qsZF-BfoRgbgb.mjs.map} +1 -1
  34. package/dist/{provisioned-edges-Cxy18aoh-CUnaY96r.mjs → provisioned-edges-C1wSO3cx-YC5C19f9.mjs} +3 -3
  35. package/dist/{provisioned-edges-Cxy18aoh-CUnaY96r.mjs.map → provisioned-edges-C1wSO3cx-YC5C19f9.mjs.map} +1 -1
  36. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs +47 -0
  37. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs.map +1 -0
  38. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs +251 -0
  39. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs.map +1 -0
  40. package/dist/{serializer-BD_6nkr4-DWn5NNTC.d.mts → serializer-Cx5slrV4-xJfH6EWS.d.mts} +2 -2
  41. package/dist/serializer-Cx5slrV4-xJfH6EWS.d.mts.map +1 -0
  42. package/dist/storage/index.d.mts +26 -31
  43. package/dist/storage/index.d.mts.map +1 -1
  44. package/dist/storage/index.mjs +89 -215
  45. package/dist/storage/index.mjs.map +1 -1
  46. package/dist/storage/storage-entrypoint.mjs +122 -147
  47. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  48. package/dist/storage/storage-service.mjs +87 -212
  49. package/dist/storage/storage-service.mjs.map +1 -1
  50. package/dist/streams/index.d.mts +22 -29
  51. package/dist/streams/index.d.mts.map +1 -1
  52. package/dist/streams/index.mjs +88 -213
  53. package/dist/streams/index.mjs.map +1 -1
  54. package/dist/streams/streams-entrypoint.mjs +118 -139
  55. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  56. package/dist/streams/streams-service.mjs +87 -212
  57. package/dist/streams/streams-service.mjs.map +1 -1
  58. package/dist/testing.d.mts +5 -19
  59. package/dist/testing.d.mts.map +1 -1
  60. package/dist/testing.mjs +3 -11
  61. package/dist/testing.mjs.map +1 -1
  62. package/package.json +18 -18
  63. package/dist/secret-BVGxqHxY-DYDfiULl.mjs.map +0 -1
  64. package/dist/serializer-BD_6nkr4-DWn5NNTC.d.mts.map +0 -1
  65. package/dist/serializer-sVE4UAmg-CikRngbR.mjs +0 -475
  66. package/dist/serializer-sVE4UAmg-CikRngbR.mjs.map +0 -1
@@ -130,6 +130,11 @@ function freezeParams(params) {
130
130
  for (const [name, param] of Object.entries(params)) frozen[name] = Object.freeze({ ...param });
131
131
  return Object.freeze(frozen);
132
132
  }
133
+ function freezeSecrets(secrets) {
134
+ const frozen = {};
135
+ for (const [name, need] of Object.entries(secrets)) frozen[name] = Object.freeze({ ...need });
136
+ return blindCast(Object.freeze(frozen));
137
+ }
133
138
  /** A frozen shallow copy that keeps the caller's declared type. */
134
139
  function frozenShallowCopy(obj) {
135
140
  return blindCast(Object.freeze({ ...obj }));
@@ -183,16 +188,16 @@ function resource(def) {
183
188
  }
184
189
  /**
185
190
  * Constructs a branded, frozen Service node — declarations only (inputs,
186
- * params, the input schema, build adapter, and the ports it exposes). Pure;
187
- * carries no runtime behavior.
191
+ * params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
188
192
  */
189
- function service(def) {
193
+ function service$1(def) {
190
194
  requireName(def.name, "service");
191
195
  requireExtension(def.extension, "service");
192
196
  requireType(def.type, "service");
193
197
  requireConfigKeySegmentNames(Object.keys(def.inputs), "input", "service");
194
198
  requireConfigKeySegmentNames(Object.keys(def.params), "param", "service");
195
- if (def.input !== void 0 && typeof def.input["~standard"]?.validate !== "function") throw new Error("service() `input` must be a Standard Schema (an object with a \"~standard\".validate function) — see https://standardschema.dev (ADR-0042).");
199
+ requireConfigKeySegmentNames(Object.keys(def.secrets ?? {}), "secret", "service");
200
+ for (const slot of Object.keys(def.secrets ?? {})) if (Object.hasOwn(def.params, slot)) throw new Error(`service() secret slot "${slot}" collides with a param of the same name — a secret slot and a service param derive the same config key (COMPOSER_<addr>_${slot.toUpperCase()}); rename one.`);
196
201
  return Object.freeze({
197
202
  [NODE]: true,
198
203
  kind: "service",
@@ -201,7 +206,7 @@ function service(def) {
201
206
  type: def.type,
202
207
  inputs: frozenShallowCopy(def.inputs),
203
208
  params: freezeParams(def.params),
204
- inputSchema: blindCast(def.input),
209
+ secretSlots: freezeSecrets(def.secrets ?? blindCast({})),
205
210
  build: Object.freeze({ ...def.build }),
206
211
  expose: def.expose !== void 0 ? frozenShallowCopy(def.expose) : void 0
207
212
  });
@@ -234,16 +239,11 @@ function dependency(def) {
234
239
  * `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
235
240
  * a secret can't leak through an accidental log or serialization.
236
241
  *
237
- * Every box carries a brand property keyed by the registered symbol
238
- * `Symbol.for('prisma:secret-box')`, which is what `isSecretString` checks. A
239
- * symbol key is invisible to `JSON.stringify` and `Object.keys`, so the box's
240
- * public shape still matches the platform's own `secrecy` type
241
- * (pdp-control-plane).
242
+ * Shape matches the platform's own `secrecy` type (pdp-control-plane). The class
243
+ * is nominal enough on its own — no phantom brand.
242
244
  */
243
245
  const REDACTED = "[REDACTED]";
244
- const SECRET_BOX = blindCast(Symbol.for("prisma:secret-box"));
245
246
  var SecretBox = class {
246
- [SECRET_BOX] = true;
247
247
  #value;
248
248
  constructor(value) {
249
249
  this.#value = value;
@@ -309,6 +309,24 @@ function hydrateSync(root, config) {
309
309
  }
310
310
  return deps;
311
311
  }
312
+ /**
313
+ * Wraps each of a service's resolved secret values in a redacting `SecretBox`
314
+ * — what the node's `secrets()` accessor returns (ADR-0021, sibling to
315
+ * `load()`/`config()`). The RESOLUTION of a secret's value (the boot
316
+ * double-lookup that reads the platform var the pointer names) is the target
317
+ * pack's job; core is handed the already-resolved strings and only boxes them,
318
+ * so a secret is redacted by TYPE from here on. A declared slot missing from
319
+ * `values` is a target contract violation, named loudly.
320
+ */
321
+ function hydrateSecrets(root, values) {
322
+ const boxed = {};
323
+ for (const slot of Object.keys(root.secretSlots)) {
324
+ const value = values[slot];
325
+ if (value === void 0) throw new Error(`secret slot "${slot}" has no resolved value — the target must resolve every declared secret before hydrateSecrets().`);
326
+ boxed[slot] = new SecretBox(value);
327
+ }
328
+ return blindCast(boxed);
329
+ }
312
330
  const liftArray = (data) => Array.isArray(data) ? data : [data];
313
331
  /**
314
332
  * Splits an array into two arrays based on the result of a predicate
@@ -7516,7 +7534,7 @@ function coerceEnvSourcedParam(raw, d, key) {
7516
7534
  /**
7517
7535
  * Boot: read each declared param from env by its key, reverse the param's own
7518
7536
  * serialization (missing/invalid fails loudly), assemble the typed Config.
7519
- * The input document rides its own channel (readInput), not this one.
7537
+ * Secrets ride a separate channel (deserializeSecrets), not this one.
7520
7538
  */
7521
7539
  const deserialize = (node, address) => {
7522
7540
  const service = {};
@@ -7553,84 +7571,40 @@ const stash = (node, config) => {
7553
7571
  process.env[configKey("", d)] = encode(d.owner, value);
7554
7572
  }
7555
7573
  };
7556
- /** The input document's own row name; its full key is COMPOSER_<addr>_INPUT. */
7557
- const INPUT_KEY_NAME = "INPUT";
7558
- /** The input document row's key for a service address. */
7559
- const inputKey = (address) => configKey(address, {
7574
+ /** The pointer-row key for a secret slot: COMPOSER_<addr>_<slot> (secrets are service-level). */
7575
+ const secretKey = (address, slot) => configKey(address, {
7560
7576
  owner: "service",
7561
- name: INPUT_KEY_NAME
7577
+ name: slot
7562
7578
  });
7563
- const SECRET_MARKER = "$secret";
7564
- const ESCAPED_KEY = /^\$\$+secret$/;
7565
- /** True for the framework's secret pointer: `{ "$secret": "<PLATFORM_VAR>" }` and nothing else. */
7566
- function isSecretPointer(value) {
7567
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && SECRET_MARKER in value && typeof value[SECRET_MARKER] === "string";
7568
- }
7569
- /** A plain data object (not an array, not a class instance) — the only object shape a binding/document may nest. */
7570
- function isPlainObject$7(value) {
7571
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
7572
- const proto = Object.getPrototypeOf(value);
7573
- return proto === Object.prototype || proto === null;
7574
- }
7575
7579
  /**
7576
- * Boot: read the one input document row (address-free after run()'s
7577
- * re-stash), swap each `$secret` pointer for a redacting box over the named
7578
- * platform var, unescape reserved keys, validate with the service's own
7579
- * schema, and return the typed object. A missing row is a loud failure naming
7580
- * the env var — a deployed environment always writes it; a local harness must
7581
- * supply it (set the row to the serialized input document).
7580
+ * Boot: resolve every secret slot to its value by double-lookup read the
7581
+ * pointer key (the platform NAME), then read that platform var. A missing
7582
+ * pointer or a missing/empty platform value is a loud failure naming both keys.
7583
+ * Returns a plain Record for core's `hydrateSecrets` to box.
7582
7584
  */
7583
- function readInput(node, address) {
7584
- const schema = node.inputSchema;
7585
- if (schema === void 0) throw new Error(`input() called on service "${node.name}", which declares no input schema — declare \`input: <schema>\` on compute() to use it (ADR-0042).`);
7586
- const key = inputKey(address);
7587
- const raw = process.env[key];
7588
- if (raw === void 0 || raw === "") throw new Error(`this service's input is not available (env ${key} is unset) — a deployed environment writes it automatically; a local harness must supply it like any other config value (set ${key} to the serialized input document).`);
7589
- let parsed;
7590
- try {
7591
- parsed = JSON.parse(raw);
7592
- } catch (cause) {
7593
- const message = cause instanceof Error ? cause.message : String(cause);
7594
- throw new Error(`this service's input document is not valid JSON (env ${key}): ${message} — a deployed environment writes it automatically; a local harness must supply the serialized input document (set ${key} to it).`);
7595
- }
7596
- const hydrated = hydrateInputDocument(parsed, key);
7597
- try {
7598
- return standardValidateSync(schema, hydrated);
7599
- } catch (cause) {
7600
- const message = cause instanceof Error ? cause.message : String(cause);
7601
- throw new Error(`invalid input document (env ${key}): ${message}`);
7585
+ const deserializeSecrets = (node, address) => {
7586
+ const values = {};
7587
+ for (const slot of Object.keys(node.secretSlots)) {
7588
+ const key = secretKey(address, slot);
7589
+ const name = process.env[key];
7590
+ if (name === void 0 || name === "") throw new Error(`missing secret pointer for slot "${slot}" (env ${key}) — the deploy did not write it.`);
7591
+ const value = process.env[name];
7592
+ if (value === void 0 || value === "") throw new Error(`secret "${slot}" is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
7593
+ values[slot] = value;
7602
7594
  }
7603
- }
7604
- /** Reverses the document encoding: `$secret` pointers become redacting boxes over the named platform vars; escaped reserved keys drop one "$". */
7605
- function hydrateInputDocument(value, key) {
7606
- if (isSecretPointer(value)) {
7607
- const name = value[SECRET_MARKER];
7608
- const secret = process.env[name];
7609
- if (secret === void 0 || secret === "") throw new Error(`secret input is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
7610
- return new SecretBox(secret);
7611
- }
7612
- if (Array.isArray(value)) return value.map((member) => hydrateInputDocument(member, key));
7613
- if (isPlainObject$7(value)) {
7614
- const out = {};
7615
- for (const [k, member] of Object.entries(value)) {
7616
- const read = ESCAPED_KEY.test(k) ? k.slice(1) : k;
7617
- out[read] = hydrateInputDocument(member, key);
7618
- }
7619
- return out;
7620
- }
7621
- return value;
7622
- }
7595
+ return values;
7596
+ };
7623
7597
  /**
7624
- * run()'s setup step for the input document: re-emit the row under its
7625
- * address-free key, so the address-free `readInput` reads identically. The
7626
- * document carries pointers, never values — the values stay only in their
7627
- * platform vars.
7598
+ * run()'s setup step for secrets: re-emit each slot's pointer NAME under its
7599
+ * address-free key, so the address-free `deserializeSecrets` double-looks-up
7600
+ * identically. Never the value — the value stays only in the platform var.
7628
7601
  */
7629
- const stashInput = (node, address) => {
7630
- if (node.inputSchema === void 0) return;
7631
- const raw = process.env[inputKey(address)];
7632
- if (raw === void 0) return;
7633
- process.env[inputKey("")] = raw;
7602
+ const stashSecrets = (node, address) => {
7603
+ for (const slot of Object.keys(node.secretSlots)) {
7604
+ const name = process.env[secretKey(address, slot)];
7605
+ if (name === void 0) continue;
7606
+ process.env[secretKey("", slot)] = name;
7607
+ }
7634
7608
  };
7635
7609
  /**
7636
7610
  * Boot: for each reserved provider param, read its address-scoped row through
@@ -7822,24 +7796,20 @@ Object.freeze({
7822
7796
  });
7823
7797
  const reservedParams = { port: number$1({ default: 3e3 }) };
7824
7798
  /**
7825
- * A Prisma Compute service — declarations only (deps + one input schema +
7826
- * build + the ports it exposes), no descriptor. The reserved `port` param
7827
- * rides its own channel (ADR-0042 leaves it untouched); a dependency named
7828
- * like it fails at authoring.
7799
+ * A Prisma Compute service — declarations only (deps + params + build + the
7800
+ * ports it exposes), no descriptor. `params` merges with the reserved
7801
+ * `ReservedParams` (`port`); a user param whose name collides with a reserved
7802
+ * one fails at authoring, the same way a colliding dependency name does.
7829
7803
  *
7830
7804
  * · run(address, boot) — the process controller: deserialize the platform
7831
7805
  * environment (keyed off `address`, the extension's ONE env read) into a
7832
- * typed Config, re-emit it (and the input document row) under address-free
7833
- * process-local stash keys, then call boot() to start the app's entry.
7834
- * · load() / input() — called from inside the app's entry: read the stash;
7835
- * load() hydrates + memoizes the deps, input() parses the input document,
7836
- * replaces each `$secret` pointer with a redacting box over the named
7837
- * platform var, validates with the declared schema, and memoizes (ADR-0042).
7806
+ * typed Config, re-emit it under address-free process-local stash keys,
7807
+ * then call boot() to start the app's entry.
7808
+ * · load() / config() — called from inside the app's entry: read the stash;
7809
+ * load() hydrates + memoizes the deps, config() returns the typed params.
7810
+ * Separate accessors so a dep and a param never share a namespace (ADR-0021).
7838
7811
  * · origin() — this service's platform-assigned public origin, read from the
7839
7812
  * stash `run()` populates; memoized per process.
7840
- * · port() — this service's resolved reserved port (the value `run()` routes
7841
- * to and exports as PORT), read from that same stash via #processConfig();
7842
- * defaults to 3000 when the platform binds none.
7843
7813
  *
7844
7814
  * The underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
7845
7815
  * the control-plane registry key `prisma-composer deploy` resolves through the
@@ -7849,7 +7819,8 @@ const reservedParams = { port: number$1({ default: 3e3 }) };
7849
7819
  var ComputeService = class {
7850
7820
  #resolved;
7851
7821
  #loadedDeps;
7852
- #loadedInput;
7822
+ #loadedParams;
7823
+ #loadedSecrets;
7853
7824
  #origin;
7854
7825
  constructor(node) {
7855
7826
  Object.assign(this, node);
@@ -7862,7 +7833,7 @@ var ComputeService = class {
7862
7833
  const config = deserialize(this, address);
7863
7834
  stash(this, config);
7864
7835
  stashProviderParams(RESERVED_PROVIDER_PARAMS, address);
7865
- stashInput(this, address);
7836
+ stashSecrets(this, address);
7866
7837
  const port = config.service["port"];
7867
7838
  if (typeof port === "number") process.env["PORT"] = String(port);
7868
7839
  return boot();
@@ -7871,9 +7842,13 @@ var ComputeService = class {
7871
7842
  if (this.#loadedDeps === void 0) this.#loadedDeps = blindCast(hydrateSync(this, this.#processConfig()));
7872
7843
  return this.#loadedDeps;
7873
7844
  }
7874
- input() {
7875
- if (this.#loadedInput === void 0) this.#loadedInput = blindCast(readInput(this, ""));
7876
- return this.#loadedInput;
7845
+ config() {
7846
+ if (this.#loadedParams === void 0) this.#loadedParams = blindCast(this.#processConfig().service);
7847
+ return this.#loadedParams;
7848
+ }
7849
+ secrets() {
7850
+ if (this.#loadedSecrets === void 0) this.#loadedSecrets = blindCast(hydrateSecrets(this, deserializeSecrets(this, "")));
7851
+ return this.#loadedSecrets;
7877
7852
  }
7878
7853
  /** This service's platform-assigned public origin — read from the stash
7879
7854
  * run() populates and memoized per process. Throws if called before run()
@@ -7882,24 +7857,26 @@ var ComputeService = class {
7882
7857
  this.#origin ??= readOrigin();
7883
7858
  return this.#origin;
7884
7859
  }
7885
- /** This service's resolved reserved port — the value run() routes to and
7886
- * exports as PORT — read the same way load() reads the stash. Defaults to
7887
- * 3000 when the platform binds none. */
7888
- port() {
7889
- const port = this.#processConfig().service["port"];
7890
- if (typeof port !== "number") throw new Error(`service "${this.name}" resolved a non-numeric port — the reserved port param is number({ default: 3000 }), so a stashed config always carries a number here.`);
7891
- return port;
7892
- }
7893
7860
  };
7894
7861
  const compute = (def) => {
7895
- for (const reserved of Object.keys(reservedParams)) if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
7896
- const instance = new ComputeService(service({
7862
+ const userParams = def.params ?? blindCast({});
7863
+ for (const reserved of Object.keys(reservedParams)) {
7864
+ if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
7865
+ if (reserved in userParams) throw new Error(`compute(): param "${reserved}" collides with the reserved service param of the same name — rename the param.`);
7866
+ }
7867
+ for (const name of Object.keys(userParams)) if (name.toUpperCase() === "ORIGIN") throw new Error(`compute(): param "${name}" collides with the framework-written origin row — rename the param.`);
7868
+ for (const name of Object.keys(def.secrets ?? {})) if (name.toUpperCase() === "ORIGIN") throw new Error(`compute(): secret "${name}" collides with the framework-written origin row — rename the secret.`);
7869
+ const params = blindCast({
7870
+ ...userParams,
7871
+ ...reservedParams
7872
+ });
7873
+ const instance = new ComputeService(service$1({
7897
7874
  name: def.name,
7898
7875
  extension: "@prisma/composer-prisma-cloud",
7899
7876
  type: "compute",
7900
7877
  inputs: def.deps,
7901
- params: reservedParams,
7902
- ...def.input !== void 0 ? { input: def.input } : {},
7878
+ params,
7879
+ ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
7903
7880
  build: def.build,
7904
7881
  ...def.expose !== void 0 ? { expose: def.expose } : {}
7905
7882
  }));
@@ -7967,21 +7944,21 @@ function s3Credentials(opts) {
7967
7944
  /**
7968
7945
  * The storage service authoring factory — a `compute` service routed to the
7969
7946
  * `s3-store` lowering instead of `compute`'s. It is exactly `compute`'s
7970
- * runnable (run/load/input, deps, input schema, build, expose) with the
7971
- * routing `type` overridden to `'s3-store'`: nothing at runtime keys off
7972
- * `type` (the serializer keys off the deployment address and each param's
7973
- * owner/name, and `load`/`input` off deps/the input schema), so only the
7974
- * deploy-time descriptor lookup sees the override and routes to the
7975
- * extended-output lowering (§ 5). The return type is compute's exactly
7976
- * (including the reserved `port` param). The storage module (D4b) calls this
7977
- * with its `db`/`credentials` deps and `expose: { store: s3Contract }`.
7947
+ * runnable (run/load/config, deps, params, build, expose) with the routing
7948
+ * `type` overridden to `'s3-store'`: nothing at runtime keys off `type` (the
7949
+ * serializer keys off the deployment address and each param's owner/name, and
7950
+ * `load`/`config` off deps/params), so only the deploy-time descriptor lookup
7951
+ * sees the override and routes to the extended-output lowering (§ 5). The
7952
+ * return type is compute's exactly (including the reserved `port` param). The
7953
+ * storage module (D4b) calls this with its `db`/`credentials` deps, a `bucket`
7954
+ * param, and `expose: { store: s3Contract }`.
7978
7955
  *
7979
- * `compute()`'s result is a `ComputeService` instance — its run/load/input/
7980
- * origin methods live on the class prototype, not as the instance's own
7981
- * properties, so a plain object spread (`{ ...node }`) would silently drop
7982
- * them. Building a fresh `ComputeService` from the same data fields (which
7983
- * `{ ...node }` DOES copy — they're the node's own enumerable properties)
7984
- * with `type` overridden keeps every method intact.
7956
+ * `compute()`'s result is a `ComputeService` instance — its run/load/config/
7957
+ * secrets/origin methods live on the class prototype, not as the instance's
7958
+ * own properties, so a plain object spread (`{ ...node }`) would silently
7959
+ * drop them. Building a fresh `ComputeService` from the same data fields
7960
+ * (which `{ ...node }` DOES copy — they're the node's own enumerable
7961
+ * properties) with `type` overridden keeps every method intact.
7985
7962
  */
7986
7963
  function s3StoreService(def) {
7987
7964
  const instance = new ComputeService({
@@ -8030,22 +8007,21 @@ function s3() {
8030
8007
  /**
8031
8008
  * The storage service node (like cron's `scheduler.ts` + `scheduler-service.ts`
8032
8009
  * combined): `storageService` builds the `s3-store` service — a Postgres `db`
8033
- * dependency, a minted `credentials` dependency, a `bucket` input key
8034
- * (bound at provision by `storage()`, ADR-0042), and the `store` port exposing
8035
- * `s3Contract`. The deploy bootstrap runs the default-exported bare node
8036
- * (`main.run(address, boot)`); the real bucket comes from the stashed input
8037
- * document at runtime — exactly like `scheduler-service.ts` default-exports a
8038
- * schedule-free `cronScheduler()`.
8010
+ * dependency, a minted `credentials` dependency, a `bucket` param, and the
8011
+ * `store` port exposing `s3Contract`. The deploy bootstrap runs the
8012
+ * default-exported bare node (`main.run(address, boot)`); the real bucket comes
8013
+ * from serialized config at runtime, so the default's `bucket` is only a
8014
+ * placeholder — exactly like `scheduler-service.ts` default-exports
8015
+ * `cronScheduler({ jobs: [] })`.
8039
8016
  */
8040
- const storageInputSchema = type({ bucket: "string" });
8041
- function storageService() {
8017
+ function storageService(opts) {
8042
8018
  return s3StoreService({
8043
8019
  name: "storage",
8044
8020
  deps: {
8045
8021
  db: postgres(),
8046
8022
  credentials: s3Credentials()
8047
8023
  },
8048
- input: storageInputSchema,
8024
+ params: { bucket: string$1({ default: opts.bucket }) },
8049
8025
  build: nodeBuild({
8050
8026
  module: new URL("./storage-service.mjs", import.meta.url).href,
8051
8027
  entry: "./storage-entrypoint.mjs"
@@ -8053,7 +8029,7 @@ function storageService() {
8053
8029
  expose: { store: s3Contract }
8054
8030
  });
8055
8031
  }
8056
- storageService();
8032
+ storageService({ bucket: "storage" });
8057
8033
  var require_reusify = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8058
8034
  function reusify(Constructor) {
8059
8035
  var head = new Constructor();
@@ -48197,7 +48173,9 @@ var init_package_entry = __esmMin((async () => {
48197
48173
  process.argv = ensureComputeArgv(process.argv);
48198
48174
  await init_server().then(() => server_exports);
48199
48175
  }));
48200
- const { store } = streamsService().load();
48176
+ const service = streamsService();
48177
+ const { store } = service.load();
48178
+ const { port } = service.config();
48201
48179
  const raw = process.env[STREAMS_API_KEY_ENV];
48202
48180
  if (raw === void 0 || raw === "") throw new Error("streams: no bearer key was provisioned for this module — nothing declares a durableStreams() dependency on it, so the key that authenticates its API was never minted. Wire a consumer to the module's `streams` port, or remove the module.");
48203
48181
  let parsed;
@@ -48209,6 +48187,7 @@ try {
48209
48187
  if (typeof parsed !== "string" || parsed.length === 0) throw new Error("streams: the provisioned bearer key is not a valid JSON-encoded string — the deploy wrote something this entrypoint cannot read back. Redeploy to re-mint the key.");
48210
48188
  const apiKey = parsed;
48211
48189
  process.env["API_KEY"] = apiKey;
48190
+ process.env["PORT"] = String(port);
48212
48191
  process.env["DS_HOST"] ??= "0.0.0.0";
48213
48192
  process.env["DS_ROOT"] ??= "/tmp/ds-data";
48214
48193
  process.env["DS_SEGMENT_MAX_INTERVAL_MS"] ??= "5000";