@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
@@ -1,4 +1,11 @@
1
1
  //#region ../../1-prisma-cloud/2-shared-modules/cron/dist/scheduler-entrypoint.mjs
2
+ const nodeBuild = (opts) => ({
3
+ extension: "@prisma/composer/node",
4
+ type: "node",
5
+ module: opts.module,
6
+ entry: opts.entry,
7
+ ...opts.dir === void 0 ? {} : { dir: opts.dir }
8
+ });
2
9
  /**
3
10
  * **Last-resort escape hatch for unsafe type assertions. Not a sanctioned tool to reach for.**
4
11
  *
@@ -80,18 +87,14 @@ function freezeParams(params) {
80
87
  for (const [name, param] of Object.entries(params)) frozen[name] = Object.freeze({ ...param });
81
88
  return Object.freeze(frozen);
82
89
  }
83
- function freezeSecrets(secrets) {
84
- const frozen = {};
85
- for (const [name, need] of Object.entries(secrets)) frozen[name] = Object.freeze({ ...need });
86
- return blindCast(Object.freeze(frozen));
87
- }
88
90
  /** A frozen shallow copy that keeps the caller's declared type. */
89
91
  function frozenShallowCopy(obj) {
90
92
  return blindCast(Object.freeze({ ...obj }));
91
93
  }
92
94
  /**
93
95
  * Constructs a branded, frozen Service node — declarations only (inputs,
94
- * params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
96
+ * params, the input schema, build adapter, and the ports it exposes). Pure;
97
+ * carries no runtime behavior.
95
98
  */
96
99
  function service$1(def) {
97
100
  requireName(def.name, "service");
@@ -99,8 +102,7 @@ function service$1(def) {
99
102
  requireType(def.type, "service");
100
103
  requireConfigKeySegmentNames(Object.keys(def.inputs), "input", "service");
101
104
  requireConfigKeySegmentNames(Object.keys(def.params), "param", "service");
102
- requireConfigKeySegmentNames(Object.keys(def.secrets ?? {}), "secret", "service");
103
- 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.`);
105
+ 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).");
104
106
  return Object.freeze({
105
107
  [NODE]: true,
106
108
  kind: "service",
@@ -109,7 +111,7 @@ function service$1(def) {
109
111
  type: def.type,
110
112
  inputs: frozenShallowCopy(def.inputs),
111
113
  params: freezeParams(def.params),
112
- secretSlots: freezeSecrets(def.secrets ?? blindCast({})),
114
+ inputSchema: blindCast(def.input),
113
115
  build: Object.freeze({ ...def.build }),
114
116
  expose: def.expose !== void 0 ? frozenShallowCopy(def.expose) : void 0
115
117
  });
@@ -142,11 +144,16 @@ function dependency(def) {
142
144
  * `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
143
145
  * a secret can't leak through an accidental log or serialization.
144
146
  *
145
- * Shape matches the platform's own `secrecy` type (pdp-control-plane). The class
146
- * is nominal enough on its own — no phantom brand.
147
+ * Every box carries a brand property keyed by the registered symbol
148
+ * `Symbol.for('prisma:secret-box')`, which is what `isSecretString` checks. A
149
+ * symbol key is invisible to `JSON.stringify` and `Object.keys`, so the box's
150
+ * public shape still matches the platform's own `secrecy` type
151
+ * (pdp-control-plane).
147
152
  */
148
153
  const REDACTED = "[REDACTED]";
154
+ const SECRET_BOX = blindCast(Symbol.for("prisma:secret-box"));
149
155
  var SecretBox = class {
156
+ [SECRET_BOX] = true;
150
157
  #value;
151
158
  constructor(value) {
152
159
  this.#value = value;
@@ -196,10 +203,6 @@ function string$1(opts = {}) {
196
203
  function number$1(opts = {}) {
197
204
  return withFacets(numberSchema, opts);
198
205
  }
199
- /** A param over any caller-supplied Standard Schema — a structured `jobs`, say. */
200
- function param(schema, opts = {}) {
201
- return withFacets(schema, opts);
202
- }
203
206
  /**
204
207
  * Synchronous hydrate — what the node's `load()` uses so
205
208
  * `const { db } = service.load()` reads without `await`. Requires every
@@ -216,31 +219,6 @@ function hydrateSync(root, config) {
216
219
  }
217
220
  return deps;
218
221
  }
219
- /**
220
- * Wraps each of a service's resolved secret values in a redacting `SecretBox`
221
- * — what the node's `secrets()` accessor returns (ADR-0021, sibling to
222
- * `load()`/`config()`). The RESOLUTION of a secret's value (the boot
223
- * double-lookup that reads the platform var the pointer names) is the target
224
- * pack's job; core is handed the already-resolved strings and only boxes them,
225
- * so a secret is redacted by TYPE from here on. A declared slot missing from
226
- * `values` is a target contract violation, named loudly.
227
- */
228
- function hydrateSecrets(root, values) {
229
- const boxed = {};
230
- for (const slot of Object.keys(root.secretSlots)) {
231
- const value = values[slot];
232
- if (value === void 0) throw new Error(`secret slot "${slot}" has no resolved value — the target must resolve every declared secret before hydrateSecrets().`);
233
- boxed[slot] = new SecretBox(value);
234
- }
235
- return blindCast(boxed);
236
- }
237
- const nodeBuild = (opts) => ({
238
- extension: "@prisma/composer/node",
239
- type: "node",
240
- module: opts.module,
241
- entry: opts.entry,
242
- ...opts.dir === void 0 ? {} : { dir: opts.dir }
243
- });
244
222
  const liftArray = (data) => Array.isArray(data) ? data : [data];
245
223
  /**
246
224
  * Splits an array into two arrays based on the result of a predicate
@@ -7448,7 +7426,7 @@ function coerceEnvSourcedParam(raw, d, key) {
7448
7426
  /**
7449
7427
  * Boot: read each declared param from env by its key, reverse the param's own
7450
7428
  * serialization (missing/invalid fails loudly), assemble the typed Config.
7451
- * Secrets ride a separate channel (deserializeSecrets), not this one.
7429
+ * The input document rides its own channel (readInput), not this one.
7452
7430
  */
7453
7431
  const deserialize = (node, address) => {
7454
7432
  const service = {};
@@ -7485,40 +7463,84 @@ const stash = (node, config) => {
7485
7463
  process.env[configKey("", d)] = encode(d.owner, value);
7486
7464
  }
7487
7465
  };
7488
- /** The pointer-row key for a secret slot: COMPOSER_<addr>_<slot> (secrets are service-level). */
7489
- const secretKey = (address, slot) => configKey(address, {
7466
+ /** The input document's own row name; its full key is COMPOSER_<addr>_INPUT. */
7467
+ const INPUT_KEY_NAME = "INPUT";
7468
+ /** The input document row's key for a service address. */
7469
+ const inputKey = (address) => configKey(address, {
7490
7470
  owner: "service",
7491
- name: slot
7471
+ name: INPUT_KEY_NAME
7492
7472
  });
7473
+ const SECRET_MARKER = "$secret";
7474
+ const ESCAPED_KEY = /^\$\$+secret$/;
7475
+ /** True for the framework's secret pointer: `{ "$secret": "<PLATFORM_VAR>" }` and nothing else. */
7476
+ function isSecretPointer(value) {
7477
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && SECRET_MARKER in value && typeof value[SECRET_MARKER] === "string";
7478
+ }
7479
+ /** A plain data object (not an array, not a class instance) — the only object shape a binding/document may nest. */
7480
+ function isPlainObject(value) {
7481
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
7482
+ const proto = Object.getPrototypeOf(value);
7483
+ return proto === Object.prototype || proto === null;
7484
+ }
7493
7485
  /**
7494
- * Boot: resolve every secret slot to its value by double-lookup read the
7495
- * pointer key (the platform NAME), then read that platform var. A missing
7496
- * pointer or a missing/empty platform value is a loud failure naming both keys.
7497
- * Returns a plain Record for core's `hydrateSecrets` to box.
7486
+ * Boot: read the one input document row (address-free after run()'s
7487
+ * re-stash), swap each `$secret` pointer for a redacting box over the named
7488
+ * platform var, unescape reserved keys, validate with the service's own
7489
+ * schema, and return the typed object. A missing row is a loud failure naming
7490
+ * the env var — a deployed environment always writes it; a local harness must
7491
+ * supply it (set the row to the serialized input document).
7498
7492
  */
7499
- const deserializeSecrets = (node, address) => {
7500
- const values = {};
7501
- for (const slot of Object.keys(node.secretSlots)) {
7502
- const key = secretKey(address, slot);
7503
- const name = process.env[key];
7504
- if (name === void 0 || name === "") throw new Error(`missing secret pointer for slot "${slot}" (env ${key}) — the deploy did not write it.`);
7505
- const value = process.env[name];
7506
- if (value === void 0 || value === "") throw new Error(`secret "${slot}" is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
7507
- values[slot] = value;
7508
- }
7509
- return values;
7510
- };
7493
+ function readInput(node, address) {
7494
+ const schema = node.inputSchema;
7495
+ 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).`);
7496
+ const key = inputKey(address);
7497
+ const raw = process.env[key];
7498
+ 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).`);
7499
+ let parsed;
7500
+ try {
7501
+ parsed = JSON.parse(raw);
7502
+ } catch (cause) {
7503
+ const message = cause instanceof Error ? cause.message : String(cause);
7504
+ 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).`);
7505
+ }
7506
+ const hydrated = hydrateInputDocument(parsed, key);
7507
+ try {
7508
+ return standardValidateSync(schema, hydrated);
7509
+ } catch (cause) {
7510
+ const message = cause instanceof Error ? cause.message : String(cause);
7511
+ throw new Error(`invalid input document (env ${key}): ${message}`);
7512
+ }
7513
+ }
7514
+ /** Reverses the document encoding: `$secret` pointers become redacting boxes over the named platform vars; escaped reserved keys drop one "$". */
7515
+ function hydrateInputDocument(value, key) {
7516
+ if (isSecretPointer(value)) {
7517
+ const name = value[SECRET_MARKER];
7518
+ const secret = process.env[name];
7519
+ if (secret === void 0 || secret === "") throw new Error(`secret input is not provisioned (env ${key} → ${name}): the platform var "${name}" is unset or empty.`);
7520
+ return new SecretBox(secret);
7521
+ }
7522
+ if (Array.isArray(value)) return value.map((member) => hydrateInputDocument(member, key));
7523
+ if (isPlainObject(value)) {
7524
+ const out = {};
7525
+ for (const [k, member] of Object.entries(value)) {
7526
+ const read = ESCAPED_KEY.test(k) ? k.slice(1) : k;
7527
+ out[read] = hydrateInputDocument(member, key);
7528
+ }
7529
+ return out;
7530
+ }
7531
+ return value;
7532
+ }
7511
7533
  /**
7512
- * run()'s setup step for secrets: re-emit each slot's pointer NAME under its
7513
- * address-free key, so the address-free `deserializeSecrets` double-looks-up
7514
- * identically. Never the value — the value stays only in the platform var.
7534
+ * run()'s setup step for the input document: re-emit the row under its
7535
+ * address-free key, so the address-free `readInput` reads identically. The
7536
+ * document carries pointers, never values — the values stay only in their
7537
+ * platform vars.
7515
7538
  */
7516
- const stashSecrets = (node, address) => {
7517
- for (const slot of Object.keys(node.secretSlots)) {
7518
- const name = process.env[secretKey(address, slot)];
7519
- if (name === void 0) continue;
7520
- process.env[secretKey("", slot)] = name;
7521
- }
7539
+ const stashInput = (node, address) => {
7540
+ if (node.inputSchema === void 0) return;
7541
+ const raw = process.env[inputKey(address)];
7542
+ if (raw === void 0) return;
7543
+ process.env[inputKey("")] = raw;
7522
7544
  };
7523
7545
  /**
7524
7546
  * Boot: for each reserved provider param, read its address-scoped row through
@@ -7808,20 +7830,24 @@ Object.freeze({
7808
7830
  });
7809
7831
  const reservedParams = { port: number$1({ default: 3e3 }) };
7810
7832
  /**
7811
- * A Prisma Compute service — declarations only (deps + params + build + the
7812
- * ports it exposes), no descriptor. `params` merges with the reserved
7813
- * `ReservedParams` (`port`); a user param whose name collides with a reserved
7814
- * one fails at authoring, the same way a colliding dependency name does.
7833
+ * A Prisma Compute service — declarations only (deps + one input schema +
7834
+ * build + the ports it exposes), no descriptor. The reserved `port` param
7835
+ * rides its own channel (ADR-0042 leaves it untouched); a dependency named
7836
+ * like it fails at authoring.
7815
7837
  *
7816
7838
  * · run(address, boot) — the process controller: deserialize the platform
7817
7839
  * environment (keyed off `address`, the extension's ONE env read) into a
7818
- * typed Config, re-emit it under address-free process-local stash keys,
7819
- * then call boot() to start the app's entry.
7820
- * · load() / config() — called from inside the app's entry: read the stash;
7821
- * load() hydrates + memoizes the deps, config() returns the typed params.
7822
- * Separate accessors so a dep and a param never share a namespace (ADR-0021).
7840
+ * typed Config, re-emit it (and the input document row) under address-free
7841
+ * process-local stash keys, then call boot() to start the app's entry.
7842
+ * · load() / input() — called from inside the app's entry: read the stash;
7843
+ * load() hydrates + memoizes the deps, input() parses the input document,
7844
+ * replaces each `$secret` pointer with a redacting box over the named
7845
+ * platform var, validates with the declared schema, and memoizes (ADR-0042).
7823
7846
  * · origin() — this service's platform-assigned public origin, read from the
7824
7847
  * stash `run()` populates; memoized per process.
7848
+ * · port() — this service's resolved reserved port (the value `run()` routes
7849
+ * to and exports as PORT), read from that same stash via #processConfig();
7850
+ * defaults to 3000 when the platform binds none.
7825
7851
  *
7826
7852
  * The underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
7827
7853
  * the control-plane registry key `prisma-composer deploy` resolves through the
@@ -7831,8 +7857,7 @@ const reservedParams = { port: number$1({ default: 3e3 }) };
7831
7857
  var ComputeService = class {
7832
7858
  #resolved;
7833
7859
  #loadedDeps;
7834
- #loadedParams;
7835
- #loadedSecrets;
7860
+ #loadedInput;
7836
7861
  #origin;
7837
7862
  constructor(node) {
7838
7863
  Object.assign(this, node);
@@ -7845,7 +7870,7 @@ var ComputeService = class {
7845
7870
  const config = deserialize(this, address);
7846
7871
  stash(this, config);
7847
7872
  stashProviderParams(RESERVED_PROVIDER_PARAMS, address);
7848
- stashSecrets(this, address);
7873
+ stashInput(this, address);
7849
7874
  const port = config.service["port"];
7850
7875
  if (typeof port === "number") process.env["PORT"] = String(port);
7851
7876
  return boot();
@@ -7854,13 +7879,9 @@ var ComputeService = class {
7854
7879
  if (this.#loadedDeps === void 0) this.#loadedDeps = blindCast(hydrateSync(this, this.#processConfig()));
7855
7880
  return this.#loadedDeps;
7856
7881
  }
7857
- config() {
7858
- if (this.#loadedParams === void 0) this.#loadedParams = blindCast(this.#processConfig().service);
7859
- return this.#loadedParams;
7860
- }
7861
- secrets() {
7862
- if (this.#loadedSecrets === void 0) this.#loadedSecrets = blindCast(hydrateSecrets(this, deserializeSecrets(this, "")));
7863
- return this.#loadedSecrets;
7882
+ input() {
7883
+ if (this.#loadedInput === void 0) this.#loadedInput = blindCast(readInput(this, ""));
7884
+ return this.#loadedInput;
7864
7885
  }
7865
7886
  /** This service's platform-assigned public origin — read from the stash
7866
7887
  * run() populates and memoized per process. Throws if called before run()
@@ -7869,26 +7890,24 @@ var ComputeService = class {
7869
7890
  this.#origin ??= readOrigin();
7870
7891
  return this.#origin;
7871
7892
  }
7893
+ /** This service's resolved reserved port — the value run() routes to and
7894
+ * exports as PORT — read the same way load() reads the stash. Defaults to
7895
+ * 3000 when the platform binds none. */
7896
+ port() {
7897
+ const port = this.#processConfig().service["port"];
7898
+ 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.`);
7899
+ return port;
7900
+ }
7872
7901
  };
7873
7902
  const compute = (def) => {
7874
- const userParams = def.params ?? blindCast({});
7875
- for (const reserved of Object.keys(reservedParams)) {
7876
- if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
7877
- if (reserved in userParams) throw new Error(`compute(): param "${reserved}" collides with the reserved service param of the same name — rename the param.`);
7878
- }
7879
- 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.`);
7880
- 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.`);
7881
- const params = blindCast({
7882
- ...userParams,
7883
- ...reservedParams
7884
- });
7903
+ 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.`);
7885
7904
  const instance = new ComputeService(service$1({
7886
7905
  name: def.name,
7887
7906
  extension: "@prisma/composer-prisma-cloud",
7888
7907
  type: "compute",
7889
7908
  inputs: def.deps,
7890
- params,
7891
- ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
7909
+ params: reservedParams,
7910
+ ...def.input !== void 0 ? { input: def.input } : {},
7892
7911
  build: def.build,
7893
7912
  ...def.expose !== void 0 ? { expose: def.expose } : {}
7894
7913
  }));
@@ -7939,25 +7958,25 @@ function parseEvery(s) {
7939
7958
  }
7940
7959
  /**
7941
7960
  * The reusable scheduler node and its firing logic. `cronScheduler` builds a
7942
- * `compute()` whose `jobs` param default is the app's schedule and whose only
7943
- * dependency is `trigger(jobId)`; nothing else about it varies per app.
7944
- * `runScheduler` is the pure, injectable firing loop the entrypoint (and its
7945
- * tests) drive.
7961
+ * `compute()` whose one input is the `jobs` schedule (bound at provision by
7962
+ * `cron()`, ADR-0042) and whose only dependency is `trigger(jobId)`; nothing
7963
+ * else about it varies per app. `runScheduler` is the pure, injectable firing
7964
+ * loop the entrypoint (and its tests) drive.
7946
7965
  */
7947
- const scheduleSchema = type({
7966
+ const cronInputSchema = type({ jobs: type({
7948
7967
  jobId: "string",
7949
7968
  every: "string"
7950
- }).array();
7969
+ }).array() });
7951
7970
  /**
7952
- * The always-on scheduler service. `schedule` sets only the `jobs` param's
7953
- * default the value the deploy serializes into config; the scheduler
7954
- * itself is job-agnostic.
7971
+ * The always-on scheduler service. Job-agnostic: the schedule arrives as the
7972
+ * `jobs` key of the input binding `cron()` supplies at provision, and the
7973
+ * entrypoint reads it back through `input()`.
7955
7974
  */
7956
- function cronScheduler(schedule) {
7975
+ function cronScheduler() {
7957
7976
  return compute({
7958
7977
  name: "scheduler",
7959
7978
  deps: { trigger: rpc(triggerContract) },
7960
- params: { jobs: param(scheduleSchema, { default: [...schedule.jobs] }) },
7979
+ input: cronInputSchema,
7961
7980
  build: nodeBuild({
7962
7981
  module: new URL("./scheduler-service.mjs", import.meta.url).href,
7963
7982
  entry: "./scheduler-entrypoint.mjs"
@@ -7978,9 +7997,9 @@ function runScheduler(opts) {
7978
7997
  });
7979
7998
  }, parseEvery(job.every));
7980
7999
  }
7981
- const service = cronScheduler({ jobs: [] });
8000
+ const service = cronScheduler();
7982
8001
  const { trigger } = service.load();
7983
- const { jobs } = service.config();
8002
+ const { jobs } = service.input();
7984
8003
  runScheduler({
7985
8004
  jobs,
7986
8005
  call: (jobId) => trigger.trigger({ jobId })