@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
@@ -508,13 +508,6 @@ function startStorageServer(opts) {
508
508
  stop: () => server.stop(true)
509
509
  };
510
510
  }
511
- const nodeBuild = (opts) => ({
512
- extension: "@prisma/composer/node",
513
- type: "node",
514
- module: opts.module,
515
- entry: opts.entry,
516
- ...opts.dir === void 0 ? {} : { dir: opts.dir }
517
- });
518
511
  /**
519
512
  * **Last-resort escape hatch for unsafe type assertions. Not a sanctioned tool to reach for.**
520
513
  *
@@ -587,6 +580,11 @@ function freezeParams(params) {
587
580
  for (const [name, param] of Object.entries(params)) frozen[name] = Object.freeze({ ...param });
588
581
  return Object.freeze(frozen);
589
582
  }
583
+ function freezeSecrets(secrets) {
584
+ const frozen = {};
585
+ for (const [name, need] of Object.entries(secrets)) frozen[name] = Object.freeze({ ...need });
586
+ return blindCast(Object.freeze(frozen));
587
+ }
590
588
  /** A frozen shallow copy that keeps the caller's declared type. */
591
589
  function frozenShallowCopy(obj) {
592
590
  return blindCast(Object.freeze({ ...obj }));
@@ -640,8 +638,7 @@ function resource(def) {
640
638
  }
641
639
  /**
642
640
  * Constructs a branded, frozen Service node — declarations only (inputs,
643
- * params, the input schema, build adapter, and the ports it exposes). Pure;
644
- * carries no runtime behavior.
641
+ * params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
645
642
  */
646
643
  function service$1(def) {
647
644
  requireName(def.name, "service");
@@ -649,7 +646,8 @@ function service$1(def) {
649
646
  requireType(def.type, "service");
650
647
  requireConfigKeySegmentNames(Object.keys(def.inputs), "input", "service");
651
648
  requireConfigKeySegmentNames(Object.keys(def.params), "param", "service");
652
- 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).");
649
+ requireConfigKeySegmentNames(Object.keys(def.secrets ?? {}), "secret", "service");
650
+ 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.`);
653
651
  return Object.freeze({
654
652
  [NODE]: true,
655
653
  kind: "service",
@@ -658,7 +656,7 @@ function service$1(def) {
658
656
  type: def.type,
659
657
  inputs: frozenShallowCopy(def.inputs),
660
658
  params: freezeParams(def.params),
661
- inputSchema: blindCast(def.input),
659
+ secretSlots: freezeSecrets(def.secrets ?? blindCast({})),
662
660
  build: Object.freeze({ ...def.build }),
663
661
  expose: def.expose !== void 0 ? frozenShallowCopy(def.expose) : void 0
664
662
  });
@@ -691,16 +689,11 @@ function dependency(def) {
691
689
  * `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
692
690
  * a secret can't leak through an accidental log or serialization.
693
691
  *
694
- * Every box carries a brand property keyed by the registered symbol
695
- * `Symbol.for('prisma:secret-box')`, which is what `isSecretString` checks. A
696
- * symbol key is invisible to `JSON.stringify` and `Object.keys`, so the box's
697
- * public shape still matches the platform's own `secrecy` type
698
- * (pdp-control-plane).
692
+ * Shape matches the platform's own `secrecy` type (pdp-control-plane). The class
693
+ * is nominal enough on its own — no phantom brand.
699
694
  */
700
695
  const REDACTED = "[REDACTED]";
701
- const SECRET_BOX = blindCast(Symbol.for("prisma:secret-box"));
702
696
  var SecretBox = class {
703
- [SECRET_BOX] = true;
704
697
  #value;
705
698
  constructor(value) {
706
699
  this.#value = value;
@@ -766,6 +759,31 @@ function hydrateSync(root, config) {
766
759
  }
767
760
  return deps;
768
761
  }
762
+ /**
763
+ * Wraps each of a service's resolved secret values in a redacting `SecretBox`
764
+ * — what the node's `secrets()` accessor returns (ADR-0021, sibling to
765
+ * `load()`/`config()`). The RESOLUTION of a secret's value (the boot
766
+ * double-lookup that reads the platform var the pointer names) is the target
767
+ * pack's job; core is handed the already-resolved strings and only boxes them,
768
+ * so a secret is redacted by TYPE from here on. A declared slot missing from
769
+ * `values` is a target contract violation, named loudly.
770
+ */
771
+ function hydrateSecrets(root, values) {
772
+ const boxed = {};
773
+ for (const slot of Object.keys(root.secretSlots)) {
774
+ const value = values[slot];
775
+ if (value === void 0) throw new Error(`secret slot "${slot}" has no resolved value — the target must resolve every declared secret before hydrateSecrets().`);
776
+ boxed[slot] = new SecretBox(value);
777
+ }
778
+ return blindCast(boxed);
779
+ }
780
+ const nodeBuild = (opts) => ({
781
+ extension: "@prisma/composer/node",
782
+ type: "node",
783
+ module: opts.module,
784
+ entry: opts.entry,
785
+ ...opts.dir === void 0 ? {} : { dir: opts.dir }
786
+ });
769
787
  const liftArray = (data) => Array.isArray(data) ? data : [data];
770
788
  /**
771
789
  * Splits an array into two arrays based on the result of a predicate
@@ -7973,7 +7991,7 @@ function coerceEnvSourcedParam(raw, d, key) {
7973
7991
  /**
7974
7992
  * Boot: read each declared param from env by its key, reverse the param's own
7975
7993
  * serialization (missing/invalid fails loudly), assemble the typed Config.
7976
- * The input document rides its own channel (readInput), not this one.
7994
+ * Secrets ride a separate channel (deserializeSecrets), not this one.
7977
7995
  */
7978
7996
  const deserialize = (node, address) => {
7979
7997
  const service = {};
@@ -8010,84 +8028,40 @@ const stash = (node, config) => {
8010
8028
  process.env[configKey("", d)] = encode(d.owner, value);
8011
8029
  }
8012
8030
  };
8013
- /** The input document's own row name; its full key is COMPOSER_<addr>_INPUT. */
8014
- const INPUT_KEY_NAME = "INPUT";
8015
- /** The input document row's key for a service address. */
8016
- const inputKey = (address) => configKey(address, {
8031
+ /** The pointer-row key for a secret slot: COMPOSER_<addr>_<slot> (secrets are service-level). */
8032
+ const secretKey = (address, slot) => configKey(address, {
8017
8033
  owner: "service",
8018
- name: INPUT_KEY_NAME
8034
+ name: slot
8019
8035
  });
8020
- const SECRET_MARKER = "$secret";
8021
- const ESCAPED_KEY = /^\$\$+secret$/;
8022
- /** True for the framework's secret pointer: `{ "$secret": "<PLATFORM_VAR>" }` and nothing else. */
8023
- function isSecretPointer(value) {
8024
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && SECRET_MARKER in value && typeof value[SECRET_MARKER] === "string";
8025
- }
8026
- /** A plain data object (not an array, not a class instance) — the only object shape a binding/document may nest. */
8027
- function isPlainObject(value) {
8028
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
8029
- const proto = Object.getPrototypeOf(value);
8030
- return proto === Object.prototype || proto === null;
8031
- }
8032
8036
  /**
8033
- * Boot: read the one input document row (address-free after run()'s
8034
- * re-stash), swap each `$secret` pointer for a redacting box over the named
8035
- * platform var, unescape reserved keys, validate with the service's own
8036
- * schema, and return the typed object. A missing row is a loud failure naming
8037
- * the env var — a deployed environment always writes it; a local harness must
8038
- * supply it (set the row to the serialized input document).
8037
+ * Boot: resolve every secret slot to its value by double-lookup read the
8038
+ * pointer key (the platform NAME), then read that platform var. A missing
8039
+ * pointer or a missing/empty platform value is a loud failure naming both keys.
8040
+ * Returns a plain Record for core's `hydrateSecrets` to box.
8039
8041
  */
8040
- function readInput(node, address) {
8041
- const schema = node.inputSchema;
8042
- 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).`);
8043
- const key = inputKey(address);
8044
- const raw = process.env[key];
8045
- 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).`);
8046
- let parsed;
8047
- try {
8048
- parsed = JSON.parse(raw);
8049
- } catch (cause) {
8050
- const message = cause instanceof Error ? cause.message : String(cause);
8051
- 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).`);
8052
- }
8053
- const hydrated = hydrateInputDocument(parsed, key);
8054
- try {
8055
- return standardValidateSync(schema, hydrated);
8056
- } catch (cause) {
8057
- const message = cause instanceof Error ? cause.message : String(cause);
8058
- throw new Error(`invalid input document (env ${key}): ${message}`);
8059
- }
8060
- }
8061
- /** Reverses the document encoding: `$secret` pointers become redacting boxes over the named platform vars; escaped reserved keys drop one "$". */
8062
- function hydrateInputDocument(value, key) {
8063
- if (isSecretPointer(value)) {
8064
- const name = value[SECRET_MARKER];
8065
- const secret = process.env[name];
8066
- if (secret === void 0 || secret === "") throw new Error(`secret input is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
8067
- return new SecretBox(secret);
8068
- }
8069
- if (Array.isArray(value)) return value.map((member) => hydrateInputDocument(member, key));
8070
- if (isPlainObject(value)) {
8071
- const out = {};
8072
- for (const [k, member] of Object.entries(value)) {
8073
- const read = ESCAPED_KEY.test(k) ? k.slice(1) : k;
8074
- out[read] = hydrateInputDocument(member, key);
8075
- }
8076
- return out;
8077
- }
8078
- return value;
8079
- }
8042
+ const deserializeSecrets = (node, address) => {
8043
+ const values = {};
8044
+ for (const slot of Object.keys(node.secretSlots)) {
8045
+ const key = secretKey(address, slot);
8046
+ const name = process.env[key];
8047
+ if (name === void 0 || name === "") throw new Error(`missing secret pointer for slot "${slot}" (env ${key}) — the deploy did not write it.`);
8048
+ const value = process.env[name];
8049
+ if (value === void 0 || value === "") throw new Error(`secret "${slot}" is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
8050
+ values[slot] = value;
8051
+ }
8052
+ return values;
8053
+ };
8080
8054
  /**
8081
- * run()'s setup step for the input document: re-emit the row under its
8082
- * address-free key, so the address-free `readInput` reads identically. The
8083
- * document carries pointers, never values — the values stay only in their
8084
- * platform vars.
8055
+ * run()'s setup step for secrets: re-emit each slot's pointer NAME under its
8056
+ * address-free key, so the address-free `deserializeSecrets` double-looks-up
8057
+ * identically. Never the value — the value stays only in the platform var.
8085
8058
  */
8086
- const stashInput = (node, address) => {
8087
- if (node.inputSchema === void 0) return;
8088
- const raw = process.env[inputKey(address)];
8089
- if (raw === void 0) return;
8090
- process.env[inputKey("")] = raw;
8059
+ const stashSecrets = (node, address) => {
8060
+ for (const slot of Object.keys(node.secretSlots)) {
8061
+ const name = process.env[secretKey(address, slot)];
8062
+ if (name === void 0) continue;
8063
+ process.env[secretKey("", slot)] = name;
8064
+ }
8091
8065
  };
8092
8066
  /**
8093
8067
  * Boot: for each reserved provider param, read its address-scoped row through
@@ -8270,24 +8244,20 @@ Object.freeze({
8270
8244
  });
8271
8245
  const reservedParams = { port: number$1({ default: 3e3 }) };
8272
8246
  /**
8273
- * A Prisma Compute service — declarations only (deps + one input schema +
8274
- * build + the ports it exposes), no descriptor. The reserved `port` param
8275
- * rides its own channel (ADR-0042 leaves it untouched); a dependency named
8276
- * like it fails at authoring.
8247
+ * A Prisma Compute service — declarations only (deps + params + build + the
8248
+ * ports it exposes), no descriptor. `params` merges with the reserved
8249
+ * `ReservedParams` (`port`); a user param whose name collides with a reserved
8250
+ * one fails at authoring, the same way a colliding dependency name does.
8277
8251
  *
8278
8252
  * · run(address, boot) — the process controller: deserialize the platform
8279
8253
  * environment (keyed off `address`, the extension's ONE env read) into a
8280
- * typed Config, re-emit it (and the input document row) under address-free
8281
- * process-local stash keys, then call boot() to start the app's entry.
8282
- * · load() / input() — called from inside the app's entry: read the stash;
8283
- * load() hydrates + memoizes the deps, input() parses the input document,
8284
- * replaces each `$secret` pointer with a redacting box over the named
8285
- * platform var, validates with the declared schema, and memoizes (ADR-0042).
8254
+ * typed Config, re-emit it under address-free process-local stash keys,
8255
+ * then call boot() to start the app's entry.
8256
+ * · load() / config() — called from inside the app's entry: read the stash;
8257
+ * load() hydrates + memoizes the deps, config() returns the typed params.
8258
+ * Separate accessors so a dep and a param never share a namespace (ADR-0021).
8286
8259
  * · origin() — this service's platform-assigned public origin, read from the
8287
8260
  * stash `run()` populates; memoized per process.
8288
- * · port() — this service's resolved reserved port (the value `run()` routes
8289
- * to and exports as PORT), read from that same stash via #processConfig();
8290
- * defaults to 3000 when the platform binds none.
8291
8261
  *
8292
8262
  * The underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
8293
8263
  * the control-plane registry key `prisma-composer deploy` resolves through the
@@ -8297,7 +8267,8 @@ const reservedParams = { port: number$1({ default: 3e3 }) };
8297
8267
  var ComputeService = class {
8298
8268
  #resolved;
8299
8269
  #loadedDeps;
8300
- #loadedInput;
8270
+ #loadedParams;
8271
+ #loadedSecrets;
8301
8272
  #origin;
8302
8273
  constructor(node) {
8303
8274
  Object.assign(this, node);
@@ -8310,7 +8281,7 @@ var ComputeService = class {
8310
8281
  const config = deserialize(this, address);
8311
8282
  stash(this, config);
8312
8283
  stashProviderParams(RESERVED_PROVIDER_PARAMS, address);
8313
- stashInput(this, address);
8284
+ stashSecrets(this, address);
8314
8285
  const port = config.service["port"];
8315
8286
  if (typeof port === "number") process.env["PORT"] = String(port);
8316
8287
  return boot();
@@ -8319,9 +8290,13 @@ var ComputeService = class {
8319
8290
  if (this.#loadedDeps === void 0) this.#loadedDeps = blindCast(hydrateSync(this, this.#processConfig()));
8320
8291
  return this.#loadedDeps;
8321
8292
  }
8322
- input() {
8323
- if (this.#loadedInput === void 0) this.#loadedInput = blindCast(readInput(this, ""));
8324
- return this.#loadedInput;
8293
+ config() {
8294
+ if (this.#loadedParams === void 0) this.#loadedParams = blindCast(this.#processConfig().service);
8295
+ return this.#loadedParams;
8296
+ }
8297
+ secrets() {
8298
+ if (this.#loadedSecrets === void 0) this.#loadedSecrets = blindCast(hydrateSecrets(this, deserializeSecrets(this, "")));
8299
+ return this.#loadedSecrets;
8325
8300
  }
8326
8301
  /** This service's platform-assigned public origin — read from the stash
8327
8302
  * run() populates and memoized per process. Throws if called before run()
@@ -8330,24 +8305,26 @@ var ComputeService = class {
8330
8305
  this.#origin ??= readOrigin();
8331
8306
  return this.#origin;
8332
8307
  }
8333
- /** This service's resolved reserved port — the value run() routes to and
8334
- * exports as PORT — read the same way load() reads the stash. Defaults to
8335
- * 3000 when the platform binds none. */
8336
- port() {
8337
- const port = this.#processConfig().service["port"];
8338
- 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.`);
8339
- return port;
8340
- }
8341
8308
  };
8342
8309
  const compute = (def) => {
8343
- 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.`);
8310
+ const userParams = def.params ?? blindCast({});
8311
+ for (const reserved of Object.keys(reservedParams)) {
8312
+ if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
8313
+ if (reserved in userParams) throw new Error(`compute(): param "${reserved}" collides with the reserved service param of the same name — rename the param.`);
8314
+ }
8315
+ 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.`);
8316
+ 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.`);
8317
+ const params = blindCast({
8318
+ ...userParams,
8319
+ ...reservedParams
8320
+ });
8344
8321
  const instance = new ComputeService(service$1({
8345
8322
  name: def.name,
8346
8323
  extension: "@prisma/composer-prisma-cloud",
8347
8324
  type: "compute",
8348
8325
  inputs: def.deps,
8349
- params: reservedParams,
8350
- ...def.input !== void 0 ? { input: def.input } : {},
8326
+ params,
8327
+ ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
8351
8328
  build: def.build,
8352
8329
  ...def.expose !== void 0 ? { expose: def.expose } : {}
8353
8330
  }));
@@ -8415,21 +8392,21 @@ function s3Credentials(opts) {
8415
8392
  /**
8416
8393
  * The storage service authoring factory — a `compute` service routed to the
8417
8394
  * `s3-store` lowering instead of `compute`'s. It is exactly `compute`'s
8418
- * runnable (run/load/input, deps, input schema, build, expose) with the
8419
- * routing `type` overridden to `'s3-store'`: nothing at runtime keys off
8420
- * `type` (the serializer keys off the deployment address and each param's
8421
- * owner/name, and `load`/`input` off deps/the input schema), so only the
8422
- * deploy-time descriptor lookup sees the override and routes to the
8423
- * extended-output lowering (§ 5). The return type is compute's exactly
8424
- * (including the reserved `port` param). The storage module (D4b) calls this
8425
- * with its `db`/`credentials` deps and `expose: { store: s3Contract }`.
8395
+ * runnable (run/load/config, deps, params, build, expose) with the routing
8396
+ * `type` overridden to `'s3-store'`: nothing at runtime keys off `type` (the
8397
+ * serializer keys off the deployment address and each param's owner/name, and
8398
+ * `load`/`config` off deps/params), so only the deploy-time descriptor lookup
8399
+ * sees the override and routes to the extended-output lowering (§ 5). The
8400
+ * return type is compute's exactly (including the reserved `port` param). The
8401
+ * storage module (D4b) calls this with its `db`/`credentials` deps, a `bucket`
8402
+ * param, and `expose: { store: s3Contract }`.
8426
8403
  *
8427
- * `compute()`'s result is a `ComputeService` instance — its run/load/input/
8428
- * origin methods live on the class prototype, not as the instance's own
8429
- * properties, so a plain object spread (`{ ...node }`) would silently drop
8430
- * them. Building a fresh `ComputeService` from the same data fields (which
8431
- * `{ ...node }` DOES copy — they're the node's own enumerable properties)
8432
- * with `type` overridden keeps every method intact.
8404
+ * `compute()`'s result is a `ComputeService` instance — its run/load/config/
8405
+ * secrets/origin methods live on the class prototype, not as the instance's
8406
+ * own properties, so a plain object spread (`{ ...node }`) would silently
8407
+ * drop them. Building a fresh `ComputeService` from the same data fields
8408
+ * (which `{ ...node }` DOES copy — they're the node's own enumerable
8409
+ * properties) with `type` overridden keeps every method intact.
8433
8410
  */
8434
8411
  function s3StoreService(def) {
8435
8412
  const instance = new ComputeService({
@@ -8452,22 +8429,21 @@ const s3Contract = Object.freeze({
8452
8429
  /**
8453
8430
  * The storage service node (like cron's `scheduler.ts` + `scheduler-service.ts`
8454
8431
  * combined): `storageService` builds the `s3-store` service — a Postgres `db`
8455
- * dependency, a minted `credentials` dependency, a `bucket` input key
8456
- * (bound at provision by `storage()`, ADR-0042), and the `store` port exposing
8457
- * `s3Contract`. The deploy bootstrap runs the default-exported bare node
8458
- * (`main.run(address, boot)`); the real bucket comes from the stashed input
8459
- * document at runtime — exactly like `scheduler-service.ts` default-exports a
8460
- * schedule-free `cronScheduler()`.
8432
+ * dependency, a minted `credentials` dependency, a `bucket` param, and the
8433
+ * `store` port exposing `s3Contract`. The deploy bootstrap runs the
8434
+ * default-exported bare node (`main.run(address, boot)`); the real bucket comes
8435
+ * from serialized config at runtime, so the default's `bucket` is only a
8436
+ * placeholder — exactly like `scheduler-service.ts` default-exports
8437
+ * `cronScheduler({ jobs: [] })`.
8461
8438
  */
8462
- const storageInputSchema = type({ bucket: "string" });
8463
- function storageService() {
8439
+ function storageService(opts) {
8464
8440
  return s3StoreService({
8465
8441
  name: "storage",
8466
8442
  deps: {
8467
8443
  db: postgres(),
8468
8444
  credentials: s3Credentials()
8469
8445
  },
8470
- input: storageInputSchema,
8446
+ params: { bucket: string$1({ default: opts.bucket }) },
8471
8447
  build: nodeBuild({
8472
8448
  module: new URL("./storage-service.mjs", import.meta.url).href,
8473
8449
  entry: "./storage-entrypoint.mjs"
@@ -8475,11 +8451,10 @@ function storageService() {
8475
8451
  expose: { store: s3Contract }
8476
8452
  });
8477
8453
  }
8478
- storageService();
8479
- const service = storageService();
8454
+ storageService({ bucket: "storage" });
8455
+ const service = storageService({ bucket: "storage" });
8480
8456
  const { db, credentials } = service.load();
8481
- const { bucket } = service.input();
8482
- const port = service.port();
8457
+ const { bucket, port } = service.config();
8483
8458
  startStorageServer({
8484
8459
  store: await createPgStore(db.url),
8485
8460
  credentials,