@prisma/composer-prisma-cloud 0.3.0-dev.5 → 0.3.0-dev.7

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 (64) hide show
  1. package/dist/control.d.mts +1 -1
  2. package/dist/control.mjs +28 -17
  3. package/dist/control.mjs.map +1 -1
  4. package/dist/cron/index.d.mts +33 -26
  5. package/dist/cron/index.d.mts.map +1 -1
  6. package/dist/cron/index.mjs +202 -77
  7. package/dist/cron/index.mjs.map +1 -1
  8. package/dist/cron/scheduler-entrypoint.mjs +133 -114
  9. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  10. package/dist/cron/scheduler-service.mjs +200 -76
  11. package/dist/cron/scheduler-service.mjs.map +1 -1
  12. package/dist/email/email-entrypoint.mjs +162 -126
  13. package/dist/email/email-entrypoint.mjs.map +1 -1
  14. package/dist/email/email-service.mjs +230 -79
  15. package/dist/email/email-service.mjs.map +1 -1
  16. package/dist/email/index.d.mts +33 -29
  17. package/dist/email/index.d.mts.map +1 -1
  18. package/dist/email/index.mjs +236 -84
  19. package/dist/email/index.mjs.map +1 -1
  20. package/dist/email/testing.mjs +112 -89
  21. package/dist/email/testing.mjs.map +1 -1
  22. package/dist/index.d.mts +51 -43
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +47 -51
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/local-target.mjs +1 -1
  27. package/dist/local-target.mjs.map +1 -1
  28. package/dist/{preflight-names-BPL-qsZF-BfoRgbgb.mjs → preflight-names-CpJUd9ZA-BCU-W3cT.mjs} +30 -14
  29. package/dist/{preflight-names-BPL-qsZF-BfoRgbgb.mjs.map → preflight-names-CpJUd9ZA-BCU-W3cT.mjs.map} +1 -1
  30. package/dist/{provisioned-edges-C1wSO3cx-YC5C19f9.mjs → provisioned-edges-Cxy18aoh-CUnaY96r.mjs} +3 -3
  31. package/dist/{provisioned-edges-C1wSO3cx-YC5C19f9.mjs.map → provisioned-edges-Cxy18aoh-CUnaY96r.mjs.map} +1 -1
  32. package/dist/{param-BTzY0ne5-CW6ojZ3E.mjs → secret-BVGxqHxY-DYDfiULl.mjs} +45 -4
  33. package/dist/secret-BVGxqHxY-DYDfiULl.mjs.map +1 -0
  34. package/dist/{serializer-Cx5slrV4-xJfH6EWS.d.mts → serializer-BD_6nkr4-DWn5NNTC.d.mts} +2 -2
  35. package/dist/serializer-BD_6nkr4-DWn5NNTC.d.mts.map +1 -0
  36. package/dist/serializer-sVE4UAmg-CikRngbR.mjs +475 -0
  37. package/dist/serializer-sVE4UAmg-CikRngbR.mjs.map +1 -0
  38. package/dist/storage/index.d.mts +31 -26
  39. package/dist/storage/index.d.mts.map +1 -1
  40. package/dist/storage/index.mjs +215 -89
  41. package/dist/storage/index.mjs.map +1 -1
  42. package/dist/storage/storage-entrypoint.mjs +147 -122
  43. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  44. package/dist/storage/storage-service.mjs +212 -87
  45. package/dist/storage/storage-service.mjs.map +1 -1
  46. package/dist/streams/index.d.mts +29 -22
  47. package/dist/streams/index.d.mts.map +1 -1
  48. package/dist/streams/index.mjs +213 -88
  49. package/dist/streams/index.mjs.map +1 -1
  50. package/dist/streams/streams-entrypoint.mjs +139 -118
  51. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  52. package/dist/streams/streams-service.mjs +212 -87
  53. package/dist/streams/streams-service.mjs.map +1 -1
  54. package/dist/testing.d.mts +19 -5
  55. package/dist/testing.d.mts.map +1 -1
  56. package/dist/testing.mjs +11 -3
  57. package/dist/testing.mjs.map +1 -1
  58. package/package.json +18 -18
  59. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs.map +0 -1
  60. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs +0 -47
  61. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs.map +0 -1
  62. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs +0 -251
  63. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs.map +0 -1
  64. package/dist/serializer-Cx5slrV4-xJfH6EWS.d.mts.map +0 -1
@@ -508,6 +508,13 @@ 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
+ });
511
518
  /**
512
519
  * **Last-resort escape hatch for unsafe type assertions. Not a sanctioned tool to reach for.**
513
520
  *
@@ -580,11 +587,6 @@ function freezeParams(params) {
580
587
  for (const [name, param] of Object.entries(params)) frozen[name] = Object.freeze({ ...param });
581
588
  return Object.freeze(frozen);
582
589
  }
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
- }
588
590
  /** A frozen shallow copy that keeps the caller's declared type. */
589
591
  function frozenShallowCopy(obj) {
590
592
  return blindCast(Object.freeze({ ...obj }));
@@ -638,7 +640,8 @@ function resource(def) {
638
640
  }
639
641
  /**
640
642
  * Constructs a branded, frozen Service node — declarations only (inputs,
641
- * params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
643
+ * params, the input schema, build adapter, and the ports it exposes). Pure;
644
+ * carries no runtime behavior.
642
645
  */
643
646
  function service$1(def) {
644
647
  requireName(def.name, "service");
@@ -646,8 +649,7 @@ function service$1(def) {
646
649
  requireType(def.type, "service");
647
650
  requireConfigKeySegmentNames(Object.keys(def.inputs), "input", "service");
648
651
  requireConfigKeySegmentNames(Object.keys(def.params), "param", "service");
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.`);
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).");
651
653
  return Object.freeze({
652
654
  [NODE]: true,
653
655
  kind: "service",
@@ -656,7 +658,7 @@ function service$1(def) {
656
658
  type: def.type,
657
659
  inputs: frozenShallowCopy(def.inputs),
658
660
  params: freezeParams(def.params),
659
- secretSlots: freezeSecrets(def.secrets ?? blindCast({})),
661
+ inputSchema: blindCast(def.input),
660
662
  build: Object.freeze({ ...def.build }),
661
663
  expose: def.expose !== void 0 ? frozenShallowCopy(def.expose) : void 0
662
664
  });
@@ -689,11 +691,16 @@ function dependency(def) {
689
691
  * `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
690
692
  * a secret can't leak through an accidental log or serialization.
691
693
  *
692
- * Shape matches the platform's own `secrecy` type (pdp-control-plane). The class
693
- * is nominal enough on its own — no phantom brand.
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).
694
699
  */
695
700
  const REDACTED = "[REDACTED]";
701
+ const SECRET_BOX = blindCast(Symbol.for("prisma:secret-box"));
696
702
  var SecretBox = class {
703
+ [SECRET_BOX] = true;
697
704
  #value;
698
705
  constructor(value) {
699
706
  this.#value = value;
@@ -759,31 +766,6 @@ function hydrateSync(root, config) {
759
766
  }
760
767
  return deps;
761
768
  }
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
- });
787
769
  const liftArray = (data) => Array.isArray(data) ? data : [data];
788
770
  /**
789
771
  * Splits an array into two arrays based on the result of a predicate
@@ -7991,7 +7973,7 @@ function coerceEnvSourcedParam(raw, d, key) {
7991
7973
  /**
7992
7974
  * Boot: read each declared param from env by its key, reverse the param's own
7993
7975
  * serialization (missing/invalid fails loudly), assemble the typed Config.
7994
- * Secrets ride a separate channel (deserializeSecrets), not this one.
7976
+ * The input document rides its own channel (readInput), not this one.
7995
7977
  */
7996
7978
  const deserialize = (node, address) => {
7997
7979
  const service = {};
@@ -8028,40 +8010,84 @@ const stash = (node, config) => {
8028
8010
  process.env[configKey("", d)] = encode(d.owner, value);
8029
8011
  }
8030
8012
  };
8031
- /** The pointer-row key for a secret slot: COMPOSER_<addr>_<slot> (secrets are service-level). */
8032
- const secretKey = (address, slot) => configKey(address, {
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, {
8033
8017
  owner: "service",
8034
- name: slot
8018
+ name: INPUT_KEY_NAME
8035
8019
  });
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
+ }
8036
8032
  /**
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.
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).
8041
8039
  */
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
- };
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
+ }
8054
8080
  /**
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.
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.
8058
8085
  */
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
- }
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;
8065
8091
  };
8066
8092
  /**
8067
8093
  * Boot: for each reserved provider param, read its address-scoped row through
@@ -8244,20 +8270,24 @@ Object.freeze({
8244
8270
  });
8245
8271
  const reservedParams = { port: number$1({ default: 3e3 }) };
8246
8272
  /**
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.
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.
8251
8277
  *
8252
8278
  * · run(address, boot) — the process controller: deserialize the platform
8253
8279
  * environment (keyed off `address`, the extension's ONE env read) into a
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).
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).
8259
8286
  * · origin() — this service's platform-assigned public origin, read from the
8260
8287
  * 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.
8261
8291
  *
8262
8292
  * The underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
8263
8293
  * the control-plane registry key `prisma-composer deploy` resolves through the
@@ -8267,8 +8297,7 @@ const reservedParams = { port: number$1({ default: 3e3 }) };
8267
8297
  var ComputeService = class {
8268
8298
  #resolved;
8269
8299
  #loadedDeps;
8270
- #loadedParams;
8271
- #loadedSecrets;
8300
+ #loadedInput;
8272
8301
  #origin;
8273
8302
  constructor(node) {
8274
8303
  Object.assign(this, node);
@@ -8281,7 +8310,7 @@ var ComputeService = class {
8281
8310
  const config = deserialize(this, address);
8282
8311
  stash(this, config);
8283
8312
  stashProviderParams(RESERVED_PROVIDER_PARAMS, address);
8284
- stashSecrets(this, address);
8313
+ stashInput(this, address);
8285
8314
  const port = config.service["port"];
8286
8315
  if (typeof port === "number") process.env["PORT"] = String(port);
8287
8316
  return boot();
@@ -8290,13 +8319,9 @@ var ComputeService = class {
8290
8319
  if (this.#loadedDeps === void 0) this.#loadedDeps = blindCast(hydrateSync(this, this.#processConfig()));
8291
8320
  return this.#loadedDeps;
8292
8321
  }
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;
8322
+ input() {
8323
+ if (this.#loadedInput === void 0) this.#loadedInput = blindCast(readInput(this, ""));
8324
+ return this.#loadedInput;
8300
8325
  }
8301
8326
  /** This service's platform-assigned public origin — read from the stash
8302
8327
  * run() populates and memoized per process. Throws if called before run()
@@ -8305,26 +8330,24 @@ var ComputeService = class {
8305
8330
  this.#origin ??= readOrigin();
8306
8331
  return this.#origin;
8307
8332
  }
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
+ }
8308
8341
  };
8309
8342
  const compute = (def) => {
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
- });
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.`);
8321
8344
  const instance = new ComputeService(service$1({
8322
8345
  name: def.name,
8323
8346
  extension: "@prisma/composer-prisma-cloud",
8324
8347
  type: "compute",
8325
8348
  inputs: def.deps,
8326
- params,
8327
- ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
8349
+ params: reservedParams,
8350
+ ...def.input !== void 0 ? { input: def.input } : {},
8328
8351
  build: def.build,
8329
8352
  ...def.expose !== void 0 ? { expose: def.expose } : {}
8330
8353
  }));
@@ -8392,21 +8415,21 @@ function s3Credentials(opts) {
8392
8415
  /**
8393
8416
  * The storage service authoring factory — a `compute` service routed to the
8394
8417
  * `s3-store` lowering instead of `compute`'s. It is exactly `compute`'s
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 }`.
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 }`.
8403
8426
  *
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.
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.
8410
8433
  */
8411
8434
  function s3StoreService(def) {
8412
8435
  const instance = new ComputeService({
@@ -8429,21 +8452,22 @@ const s3Contract = Object.freeze({
8429
8452
  /**
8430
8453
  * The storage service node (like cron's `scheduler.ts` + `scheduler-service.ts`
8431
8454
  * combined): `storageService` builds the `s3-store` service — a Postgres `db`
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: [] })`.
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()`.
8438
8461
  */
8439
- function storageService(opts) {
8462
+ const storageInputSchema = type({ bucket: "string" });
8463
+ function storageService() {
8440
8464
  return s3StoreService({
8441
8465
  name: "storage",
8442
8466
  deps: {
8443
8467
  db: postgres(),
8444
8468
  credentials: s3Credentials()
8445
8469
  },
8446
- params: { bucket: string$1({ default: opts.bucket }) },
8470
+ input: storageInputSchema,
8447
8471
  build: nodeBuild({
8448
8472
  module: new URL("./storage-service.mjs", import.meta.url).href,
8449
8473
  entry: "./storage-entrypoint.mjs"
@@ -8451,10 +8475,11 @@ function storageService(opts) {
8451
8475
  expose: { store: s3Contract }
8452
8476
  });
8453
8477
  }
8454
- storageService({ bucket: "storage" });
8455
- const service = storageService({ bucket: "storage" });
8478
+ storageService();
8479
+ const service = storageService();
8456
8480
  const { db, credentials } = service.load();
8457
- const { bucket, port } = service.config();
8481
+ const { bucket } = service.input();
8482
+ const port = service.port();
8458
8483
  startStorageServer({
8459
8484
  store: await createPgStore(db.url),
8460
8485
  credentials,