@prisma/composer-prisma-cloud 0.1.0 → 0.2.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 (47) hide show
  1. package/dist/control.d.mts +52 -38
  2. package/dist/control.mjs +1050 -326
  3. package/dist/control.mjs.map +1 -1
  4. package/dist/cron/index.d.mts +9 -6
  5. package/dist/cron/index.mjs +114 -5
  6. package/dist/cron/index.mjs.map +1 -1
  7. package/dist/cron/scheduler-entrypoint.mjs +281 -111
  8. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  9. package/dist/cron/scheduler-service.mjs +114 -5
  10. package/dist/cron/scheduler-service.mjs.map +1 -1
  11. package/dist/index.d.mts +118 -23
  12. package/dist/index.mjs +44 -2
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/{prisma-next-COrwlg3N.mjs → prisma-next-DlU01kXJ-2sP5mQb6.mjs} +3 -3
  15. package/dist/prisma-next-DlU01kXJ-2sP5mQb6.mjs.map +1 -0
  16. package/dist/prisma-next.d.mts +1 -2
  17. package/dist/prisma-next.mjs +1 -1
  18. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs +151 -0
  19. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs.map +1 -0
  20. package/dist/{serializer-C2CsA7xm-29Eg2Tjl.mjs → serializer-CX4VYdf_-KKGoAxfx.mjs} +61 -7
  21. package/dist/serializer-CX4VYdf_-KKGoAxfx.mjs.map +1 -0
  22. package/dist/serializer-Cx5slrV4-xJfH6EWS.d.mts +36 -0
  23. package/dist/storage/index.d.mts +5 -16
  24. package/dist/storage/index.mjs +114 -3
  25. package/dist/storage/index.mjs.map +1 -1
  26. package/dist/storage/storage-entrypoint.mjs +7242 -12
  27. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  28. package/dist/storage/storage-service.mjs +114 -3
  29. package/dist/storage/storage-service.mjs.map +1 -1
  30. package/dist/storage/testing.d.mts +7 -4
  31. package/dist/storage/testing.mjs.map +1 -1
  32. package/dist/streams/index.d.mts +250 -0
  33. package/dist/streams/index.mjs +4058 -0
  34. package/dist/streams/index.mjs.map +1 -0
  35. package/dist/streams/streams-entrypoint.mjs +48095 -0
  36. package/dist/streams/streams-entrypoint.mjs.map +1 -0
  37. package/dist/streams/streams-service.mjs +811 -0
  38. package/dist/streams/streams-service.mjs.map +1 -0
  39. package/dist/streams/testing.d.mts +33 -0
  40. package/dist/streams/testing.mjs +31335 -0
  41. package/dist/streams/testing.mjs.map +1 -0
  42. package/dist/testing.d.mts +1 -2
  43. package/dist/testing.mjs +1 -1
  44. package/dist/testing.mjs.map +1 -1
  45. package/package.json +27 -22
  46. package/dist/prisma-next-COrwlg3N.mjs.map +0 -1
  47. package/dist/serializer-C2CsA7xm-29Eg2Tjl.mjs.map +0 -1
@@ -42,6 +42,15 @@ function blindCast(input) {
42
42
  * data objects. A node's `extension` + `type` form its deploy-time registry key (ADR-0017).
43
43
  */
44
44
  const NODE = Symbol.for("prisma:node");
45
+ const PROVISION_NEED = blindCast(Symbol.for("prisma:provision-need"));
46
+ /** Builds an opaque provisioning need — the declaring package's own brand plus whatever payload its provisioner reads back. */
47
+ function provisionNeed(brand, payload) {
48
+ return blindCast(Object.freeze({
49
+ [PROVISION_NEED]: true,
50
+ brand,
51
+ payload
52
+ }));
53
+ }
45
54
  function requireType(type, factory) {
46
55
  if (typeof type !== "string" || type.length === 0) throw new Error(`${factory}() requires a non-empty node type.`);
47
56
  }
@@ -87,7 +96,7 @@ function service$1(def) {
87
96
  requireNoUnderscoreNames(Object.keys(def.inputs), "input", "service");
88
97
  requireNoUnderscoreNames(Object.keys(def.params), "param", "service");
89
98
  requireNoUnderscoreNames(Object.keys(def.secrets ?? {}), "secret", "service");
90
- 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 (COMPOSE_<addr>_${slot.toUpperCase()}); rename one.`);
99
+ 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.`);
91
100
  return Object.freeze({
92
101
  [NODE]: true,
93
102
  kind: "service",
@@ -171,7 +180,8 @@ function withFacets(schema, opts) {
171
180
  return {
172
181
  schema,
173
182
  ...opts.optional !== void 0 ? { optional: opts.optional } : {},
174
- ...opts.default !== void 0 ? { default: opts.default } : {}
183
+ ...opts.default !== void 0 ? { default: opts.default } : {},
184
+ ...opts.provision !== void 0 ? { provision: opts.provision } : {}
175
185
  };
176
186
  }
177
187
  /** A string-valued param. */
@@ -224,7 +234,8 @@ const nodeBuild = (opts) => ({
224
234
  extension: "@prisma/composer/node",
225
235
  type: "node",
226
236
  module: opts.module,
227
- entry: opts.entry
237
+ entry: opts.entry,
238
+ ...opts.dir === void 0 ? {} : { dir: opts.dir }
228
239
  });
229
240
  /**
230
241
  * Walks a node's own params, then each dependency input's connection params —
@@ -254,7 +265,7 @@ const configKey = (address, d) => {
254
265
  const segments = address.split(".").filter((s) => s.length > 0);
255
266
  const owner = d.owner === "service" ? [] : [d.owner.input];
256
267
  return [
257
- "COMPOSE",
268
+ "COMPOSER",
258
269
  ...segments,
259
270
  ...owner,
260
271
  d.name
@@ -273,12 +284,18 @@ function encode(owner, value) {
273
284
  function decode(owner, raw) {
274
285
  return owner === "service" ? JSON.parse(raw) : raw;
275
286
  }
287
+ const PARAM_POINTER_PREFIX = "@composer-param-pointer:";
288
+ /** True iff `raw` is a param pointer row (as opposed to a JSON-encoded literal). */
289
+ const isParamPointerRow = (raw) => raw.startsWith(PARAM_POINTER_PREFIX);
290
+ /** Reverses `encodeParamPointer`: the platform var NAME a pointer row points to. */
291
+ const decodeParamPointer = (raw) => raw.slice(24);
276
292
  function coerce(raw, d, key) {
277
293
  if (!(raw !== void 0 && raw !== "")) {
278
294
  if (d.param.default !== void 0) return d.param.default;
279
295
  if (d.param.optional === true) return void 0;
280
296
  throw new Error(`missing required config param "${d.name}" (env ${key})`);
281
297
  }
298
+ if (d.owner === "service" && isParamPointerRow(raw)) return coerceEnvSourcedParam(raw, d, key);
282
299
  try {
283
300
  return standardValidateSync(d.param.schema, decode(d.owner, raw));
284
301
  } catch (cause) {
@@ -287,6 +304,26 @@ function coerce(raw, d, key) {
287
304
  }
288
305
  }
289
306
  /**
307
+ * Boot resolution for an env-sourced param: double-lookup (pointer → platform
308
+ * var), then the param's own schema on the raw string — no JSON decode, and
309
+ * no redaction (it's config, not a secret). An UNSET platform var is a loud
310
+ * boot failure naming both the param and the platform var; an EMPTY string is
311
+ * not special-cased here — it reaches the schema like any other value, so it
312
+ * passes iff the schema accepts it (deliberately unlike a literal param's own
313
+ * ""-means-absent rule, and unlike a secret's non-empty requirement).
314
+ */
315
+ function coerceEnvSourcedParam(raw, d, key) {
316
+ const platformVar = decodeParamPointer(raw);
317
+ const value = process.env[platformVar];
318
+ if (value === void 0) throw new Error(`env-sourced config param "${d.name}" (env ${key} → ${platformVar}) is unset: the platform variable "${platformVar}" was not injected — the deploy did not provision it.`);
319
+ try {
320
+ return standardValidateSync(d.param.schema, value);
321
+ } catch (cause) {
322
+ const message = cause instanceof Error ? cause.message : String(cause);
323
+ throw new Error(`invalid value for env-sourced config param "${d.name}" (env ${key} → ${platformVar}): ${message}`);
324
+ }
325
+ }
326
+ /**
290
327
  * Boot: read each declared param from env by its key, reverse the param's own
291
328
  * serialization (missing/invalid fails loudly), assemble the typed Config.
292
329
  * Secrets ride a separate channel (deserializeSecrets), not this one.
@@ -326,7 +363,7 @@ const stash = (node, config) => {
326
363
  process.env[configKey("", d)] = encode(d.owner, value);
327
364
  }
328
365
  };
329
- /** The pointer-row key for a secret slot: COMPOSE_<addr>_<slot> (secrets are service-level). */
366
+ /** The pointer-row key for a secret slot: COMPOSER_<addr>_<slot> (secrets are service-level). */
330
367
  const secretKey = (address, slot) => configKey(address, {
331
368
  owner: "service",
332
369
  name: slot
@@ -361,6 +398,32 @@ const stashSecrets = (node, address) => {
361
398
  process.env[secretKey("", slot)] = name;
362
399
  }
363
400
  };
401
+ /**
402
+ * Boot: for each reserved provider param, read its address-scoped row through
403
+ * the same `coerce` a declared param uses (JSON-decode, schema-validate), and
404
+ * re-emit it address-free — `stash`'s counterpart for this separate
405
+ * declaration space. A param is declared optional here unconditionally: an
406
+ * absent row means "never provisioned" (local dev, tests, a provider with no
407
+ * registered value for this deploy), never a boot failure, so nothing is
408
+ * stashed and the runtime reader that owns this slot falls back to its own
409
+ * pass-through behavior.
410
+ */
411
+ function stashProviderParams(entries, address) {
412
+ for (const entry of entries) {
413
+ const d = {
414
+ owner: "service",
415
+ name: entry.name,
416
+ param: {
417
+ schema: entry.schema,
418
+ optional: true
419
+ }
420
+ };
421
+ const key = configKey(address, d);
422
+ const value = coerce(process.env[key], d, key);
423
+ if (value === void 0) continue;
424
+ process.env[configKey("", d)] = encode("service", value);
425
+ }
426
+ }
364
427
  /** Synchronous Standard Schema validation — see the matching note in core's `config.ts`. */
365
428
  function standardValidateSync(schema, value) {
366
429
  const result = schema["~standard"].validate(value);
@@ -368,101 +431,20 @@ function standardValidateSync(schema, value) {
368
431
  if (result.issues !== void 0) throw new Error(`config param validation failed: ${result.issues.map((issue) => issue.message).join("; ")}`);
369
432
  return result.value;
370
433
  }
371
- const reservedParams = { port: number$1({ default: 3e3 }) };
372
- /**
373
- * A Prisma Compute service — declarations only (deps + params + build + the
374
- * ports it exposes), no descriptor. `params` merges with the reserved
375
- * `ReservedParams` (`port`); a user param whose name collides with a reserved
376
- * one fails at authoring, the same way a colliding dependency name does.
377
- * Returns the extension's runnable/loadable node:
378
- * · run(address, boot) — the process controller: deserialize the platform
379
- * environment (keyed off `address`, the extension's ONE env read) into a
380
- * typed Config, re-emit it under address-free process-local stash keys,
381
- * then call boot() to start the app's entry.
382
- * · load() / config() — called from inside the app's entry: read the stash;
383
- * load() hydrates + memoizes the deps, config() returns the typed params.
384
- * Separate accessors so a dep and a param never share a namespace (ADR-0021).
385
- *
386
- * `service()`'s underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
387
- * the control-plane registry key `prisma-composer deploy` resolves through the
388
- * app's `prisma-composer.config.ts` (ADR-0017). This module loads nothing at
389
- * deploy time; nodes are pure data.
390
- */
391
- const compute = (def) => {
392
- const userParams = def.params ?? blindCast({});
393
- for (const reserved of Object.keys(reservedParams)) {
394
- if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
395
- if (reserved in userParams) throw new Error(`compute(): param "${reserved}" collides with the reserved service param of the same name — rename the param.`);
396
- }
397
- const params = blindCast({
398
- ...userParams,
399
- ...reservedParams
400
- });
401
- const node = service$1({
402
- name: def.name,
403
- extension: "@prisma/composer-prisma-cloud",
404
- type: "compute",
405
- inputs: def.deps,
406
- params,
407
- ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
408
- build: def.build,
409
- ...def.expose !== void 0 ? { expose: def.expose } : {}
410
- });
411
- let resolved;
412
- let loadedDeps;
413
- let loadedParams;
414
- let loadedSecrets;
415
- function processConfig() {
416
- if (resolved === void 0) resolved = deserialize(node, "");
417
- return resolved;
418
- }
419
- const runnable = {
420
- ...node,
421
- async run(address, boot) {
422
- const config = deserialize(node, address);
423
- stash(node, config);
424
- stashSecrets(node, address);
425
- const port = config.service["port"];
426
- if (typeof port === "number") process.env["PORT"] = String(port);
427
- return boot();
428
- },
429
- load() {
430
- if (loadedDeps === void 0) loadedDeps = blindCast(hydrateSync(node, processConfig()));
431
- return loadedDeps;
432
- },
433
- config() {
434
- if (loadedParams === void 0) loadedParams = blindCast(processConfig().service);
435
- return loadedParams;
436
- },
437
- secrets() {
438
- if (loadedSecrets === void 0) loadedSecrets = blindCast(hydrateSecrets(node, deserializeSecrets(node, "")));
439
- return loadedSecrets;
440
- }
441
- };
442
- return Object.freeze(blindCast(runnable));
443
- };
444
- Object.freeze({
445
- kind: "postgres",
446
- __cmp: { url: "" },
447
- satisfies: (required) => required.kind === "postgres"
448
- });
449
- Object.freeze({
450
- kind: "credentials",
451
- __cmp: {
452
- accessKeyId: "",
453
- secretAccessKey: ""
454
- },
455
- satisfies: (required) => required.kind === "credentials"
456
- });
457
- async function standardValidate(schema, value) {
458
- const result = await schema["~standard"].validate(value);
459
- if (result.issues !== void 0) throw new Error(`Schema validation failed: ${result.issues.map((issue) => issue.message).join("; ")}`);
460
- return result.value;
434
+ /** Bounded jittered backoff for retrying a dropped call. `maxRetries` is retries after the first attempt. */
435
+ const RETRY = {
436
+ initialDelayMs: 250,
437
+ multiplier: 2,
438
+ maxDelayMs: 5e3,
439
+ maxRetries: 5
440
+ };
441
+ const IDEMPOTENCY_KEY_HEADER$1 = "Idempotency-Key";
442
+ function sleep(ms) {
443
+ return new Promise((resolve) => setTimeout(resolve, ms));
461
444
  }
462
- /** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */
463
- function methodUrl(base, method) {
464
- const normalizedBase = base.endsWith("/") ? base : `${base}/`;
465
- return new URL(`rpc/${method}`, normalizedBase).toString();
445
+ /** Whether a non-OK response is safe to retry: 429 or any 5xx, never another 4xx. */
446
+ function isRetryableStatus(status) {
447
+ return status === 429 || status >= 500;
466
448
  }
467
449
  /** The server's `{ error }` body, if the response has one — undefined otherwise. */
468
450
  async function errorDetail(res) {
@@ -473,20 +455,56 @@ async function errorDetail(res) {
473
455
  return;
474
456
  }
475
457
  }
458
+ /** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */
459
+ function methodUrl(base, method) {
460
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
461
+ return new URL(`rpc/${method}`, normalizedBase).toString();
462
+ }
463
+ /**
464
+ * Sends one call over `send`, retrying a thrown error, 429, or 5xx with
465
+ * full-jitter backoff. `buildRequest` runs per attempt but carries the same
466
+ * idempotency key each time — only the transport call repeats, not the key.
467
+ */
468
+ async function callWithRetry(send, buildRequest, method) {
469
+ let delay = RETRY.initialDelayMs;
470
+ let retries = 0;
471
+ for (;;) {
472
+ let res;
473
+ try {
474
+ res = await send(buildRequest());
475
+ } catch (err) {
476
+ if (retries >= RETRY.maxRetries) throw err;
477
+ retries += 1;
478
+ await sleep(Math.random() * delay);
479
+ delay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);
480
+ continue;
481
+ }
482
+ if (res.ok) return res.json();
483
+ if (!isRetryableStatus(res.status) || retries >= RETRY.maxRetries) {
484
+ const detail = await errorDetail(res);
485
+ throw new Error(`RPC call "${method}" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : ""));
486
+ }
487
+ retries += 1;
488
+ await sleep(Math.random() * delay);
489
+ delay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);
490
+ }
491
+ }
476
492
  function makeClient(contract, url, opts) {
477
493
  const send = opts?.fetch ?? fetch;
494
+ const baseHeaders = { "content-type": "application/json" };
495
+ if (opts?.serviceKey !== void 0) baseHeaders["Authorization"] = `Bearer ${opts.serviceKey}`;
478
496
  const client = {};
479
- for (const [method, schemas] of Object.entries(blindCast(contract.__cmp))) client[method] = async (input) => {
480
- const res = await send(new Request(methodUrl(url, method), {
497
+ for (const method of Object.keys(contract.__cmp)) client[method] = async (input) => {
498
+ const idempotencyKey = crypto.randomUUID();
499
+ const body = JSON.stringify(input);
500
+ return callWithRetry(send, () => new Request(methodUrl(url, method), {
481
501
  method: "POST",
482
- headers: { "content-type": "application/json" },
483
- body: JSON.stringify(input)
484
- }));
485
- if (!res.ok) {
486
- const detail = await errorDetail(res);
487
- throw new Error(`RPC call "${method}" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : ""));
488
- }
489
- return standardValidate(schemas.output, await res.json());
502
+ headers: {
503
+ ...baseHeaders,
504
+ [IDEMPOTENCY_KEY_HEADER$1]: idempotencyKey
505
+ },
506
+ body
507
+ }), method);
490
508
  };
491
509
  return blindCast(client);
492
510
  }
@@ -498,13 +516,23 @@ function contract(fns) {
498
516
  };
499
517
  return Object.freeze(value);
500
518
  }
519
+ /** ADR-0031's need brand for RPC's per-binding service key — the target registers a provisioner under this. */
520
+ const RPC_PEER_KEY = Symbol.for("prisma:rpc/per-binding-key");
521
+ /** The provisioning need `rpc()`'s `serviceKey` param declares (ADR-0030): a shared, unguessable value the target mints per consumer edge. */
522
+ const perBindingToken = () => provisionNeed(RPC_PEER_KEY);
501
523
  function rpc(arg) {
502
524
  if (!isRpcContract(arg)) return arg;
503
525
  return dependency({
504
526
  type: "rpc",
505
527
  connection: {
506
- params: { url: string$1() },
507
- hydrate: ({ url }) => makeClient(arg, url)
528
+ params: {
529
+ url: string$1(),
530
+ serviceKey: string$1({
531
+ optional: true,
532
+ provision: perBindingToken()
533
+ })
534
+ },
535
+ hydrate: ({ url, serviceKey }) => makeClient(arg, url, { serviceKey })
508
536
  },
509
537
  required: arg
510
538
  });
@@ -7631,6 +7659,148 @@ ark.schema;
7631
7659
  ark.define;
7632
7660
  ark.declare;
7633
7661
  /**
7662
+ * RPC's reserved provider param (ADR-0030/ADR-0031): the declaration —
7663
+ * name + schema + brand — for the accepted-keys set a provider stores, shared
7664
+ * by `control.ts` (which registers the deploy-side `value(refs)` that mints
7665
+ * and aggregates it — see its `rpcAcceptedKeysValue`) and `compute.ts` (which
7666
+ * validates and stashes it at boot), so writer and reader cannot drift.
7667
+ * Finding the edges themselves is `provisioned-edges.ts`'s generic,
7668
+ * brand-blind scan — RPC is not special-cased anywhere in this target.
7669
+ *
7670
+ * This module is reachable from the RUNTIME/authoring side — it must never
7671
+ * import `@internal/lowering` or `effect`, or those tokens leak into a user
7672
+ * service's bundle (the deploy-side `value(refs)` lives in control.ts, the
7673
+ * control-plane-only entry).
7674
+ */
7675
+ /**
7676
+ * The reserved provider param for RPC's accepted-keys set: the var name is
7677
+ * `RPC_ACCEPTED_KEYS`, derived through `configKey` at both ends
7678
+ * (`configKey(address, …)` at deploy, `configKey('', …)` at boot — the
7679
+ * address-free form is `@internal/service-rpc`'s `RPC_ACCEPTED_KEYS_ENV`). `brand` is
7680
+ * `RPC_PEER_KEY`, the same brand `perBindingToken()`'s need carries — control.ts
7681
+ * looks its `value(refs)` up by this field.
7682
+ */
7683
+ const RPC_ACCEPTED_KEYS_PARAM = {
7684
+ name: "RPC_ACCEPTED_KEYS",
7685
+ schema: type("string[]"),
7686
+ brand: RPC_PEER_KEY
7687
+ };
7688
+ /** ADR-0031's need brand for the streams module's bearer key — control.ts registers the provisioner under this. */
7689
+ const STREAMS_API_KEY = Symbol.for("prisma:streams/api-key");
7690
+ /**
7691
+ * The reserved provider param for the streams bearer key: the var name is
7692
+ * `STREAMS_API_KEY`. `brand` is `STREAMS_API_KEY` itself (the same symbol
7693
+ * `streamsApiKeyNeed()`'s need carries) — control.ts looks its `value(refs)`
7694
+ * up by this field.
7695
+ */
7696
+ const STREAMS_API_KEY_PARAM = {
7697
+ name: "STREAMS_API_KEY",
7698
+ schema: type("string"),
7699
+ brand: STREAMS_API_KEY
7700
+ };
7701
+ configKey("", {
7702
+ owner: "service",
7703
+ name: STREAMS_API_KEY_PARAM.name
7704
+ });
7705
+ const RESERVED_PROVIDER_PARAMS = [RPC_ACCEPTED_KEYS_PARAM, STREAMS_API_KEY_PARAM];
7706
+ Object.freeze({
7707
+ kind: "s3",
7708
+ __cmp: {
7709
+ url: "",
7710
+ bucket: "",
7711
+ accessKeyId: "",
7712
+ secretAccessKey: ""
7713
+ },
7714
+ satisfies: (required) => required.kind === "s3"
7715
+ });
7716
+ const reservedParams = { port: number$1({ default: 3e3 }) };
7717
+ /**
7718
+ * A Prisma Compute service — declarations only (deps + params + build + the
7719
+ * ports it exposes), no descriptor. `params` merges with the reserved
7720
+ * `ReservedParams` (`port`); a user param whose name collides with a reserved
7721
+ * one fails at authoring, the same way a colliding dependency name does.
7722
+ * Returns the extension's runnable/loadable node:
7723
+ * · run(address, boot) — the process controller: deserialize the platform
7724
+ * environment (keyed off `address`, the extension's ONE env read) into a
7725
+ * typed Config, re-emit it under address-free process-local stash keys,
7726
+ * then call boot() to start the app's entry.
7727
+ * · load() / config() — called from inside the app's entry: read the stash;
7728
+ * load() hydrates + memoizes the deps, config() returns the typed params.
7729
+ * Separate accessors so a dep and a param never share a namespace (ADR-0021).
7730
+ *
7731
+ * `service()`'s underlying node carries `extension: '@prisma/composer-prisma-cloud'` —
7732
+ * the control-plane registry key `prisma-composer deploy` resolves through the
7733
+ * app's `prisma-composer.config.ts` (ADR-0017). This module loads nothing at
7734
+ * deploy time; nodes are pure data.
7735
+ */
7736
+ const compute = (def) => {
7737
+ const userParams = def.params ?? blindCast({});
7738
+ for (const reserved of Object.keys(reservedParams)) {
7739
+ if (reserved in def.deps) throw new Error(`compute(): dependency "${reserved}" collides with the reserved service param of the same name — rename the dependency.`);
7740
+ if (reserved in userParams) throw new Error(`compute(): param "${reserved}" collides with the reserved service param of the same name — rename the param.`);
7741
+ }
7742
+ const params = blindCast({
7743
+ ...userParams,
7744
+ ...reservedParams
7745
+ });
7746
+ const node = service$1({
7747
+ name: def.name,
7748
+ extension: "@prisma/composer-prisma-cloud",
7749
+ type: "compute",
7750
+ inputs: def.deps,
7751
+ params,
7752
+ ...def.secrets !== void 0 ? { secrets: def.secrets } : {},
7753
+ build: def.build,
7754
+ ...def.expose !== void 0 ? { expose: def.expose } : {}
7755
+ });
7756
+ let resolved;
7757
+ let loadedDeps;
7758
+ let loadedParams;
7759
+ let loadedSecrets;
7760
+ function processConfig() {
7761
+ if (resolved === void 0) resolved = deserialize(node, "");
7762
+ return resolved;
7763
+ }
7764
+ const runnable = {
7765
+ ...node,
7766
+ async run(address, boot) {
7767
+ const config = deserialize(node, address);
7768
+ stash(node, config);
7769
+ stashProviderParams(RESERVED_PROVIDER_PARAMS, address);
7770
+ stashSecrets(node, address);
7771
+ const port = config.service["port"];
7772
+ if (typeof port === "number") process.env["PORT"] = String(port);
7773
+ return boot();
7774
+ },
7775
+ load() {
7776
+ if (loadedDeps === void 0) loadedDeps = blindCast(hydrateSync(node, processConfig()));
7777
+ return loadedDeps;
7778
+ },
7779
+ config() {
7780
+ if (loadedParams === void 0) loadedParams = blindCast(processConfig().service);
7781
+ return loadedParams;
7782
+ },
7783
+ secrets() {
7784
+ if (loadedSecrets === void 0) loadedSecrets = blindCast(hydrateSecrets(node, deserializeSecrets(node, "")));
7785
+ return loadedSecrets;
7786
+ }
7787
+ };
7788
+ return Object.freeze(blindCast(runnable));
7789
+ };
7790
+ Object.freeze({
7791
+ kind: "postgres",
7792
+ __cmp: { url: "" },
7793
+ satisfies: (required) => required.kind === "postgres"
7794
+ });
7795
+ Object.freeze({
7796
+ kind: "credentials",
7797
+ __cmp: {
7798
+ accessKeyId: "",
7799
+ secretAccessKey: ""
7800
+ },
7801
+ satisfies: (required) => required.kind === "credentials"
7802
+ });
7803
+ /**
7634
7804
  * The one call edge between the scheduler and the app's runner: `trigger(jobId)`.
7635
7805
  * The scheduler depends on it (`rpc(triggerContract)`); the runner exposes it
7636
7806
  * (`expose: { trigger: triggerContract }`). `jobId` travels as data through this