@prisma/composer 0.1.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 (50) hide show
  1. package/LICENSE +201 -0
  2. package/dist/app-config-BUqyK6N6-CVq3uvHF.d.mts +188 -0
  3. package/dist/assertions.d.mts +31 -0
  4. package/dist/assertions.mjs +35 -0
  5. package/dist/assertions.mjs.map +1 -0
  6. package/dist/bin.mjs +1134 -0
  7. package/dist/bin.mjs.map +1 -0
  8. package/dist/casts-Ci5rYYaR.mjs +82 -0
  9. package/dist/casts-Ci5rYYaR.mjs.map +1 -0
  10. package/dist/casts.d.mts +78 -0
  11. package/dist/casts.mjs +2 -0
  12. package/dist/config-BVVgDSdq.d.mts +1 -0
  13. package/dist/config-ob5OhCSP-sP3GW3uu.d.mts +477 -0
  14. package/dist/config.d.mts +2 -0
  15. package/dist/config.mjs +9 -0
  16. package/dist/config.mjs.map +1 -0
  17. package/dist/deploy-BVVgDSdq.d.mts +1 -0
  18. package/dist/deploy.d.mts +2 -0
  19. package/dist/deploy.mjs +154 -0
  20. package/dist/deploy.mjs.map +1 -0
  21. package/dist/dist-zBU8ASQW.mjs +181 -0
  22. package/dist/dist-zBU8ASQW.mjs.map +1 -0
  23. package/dist/graph-BYdCQKya-BI0njTow.mjs +595 -0
  24. package/dist/graph-BYdCQKya-BI0njTow.mjs.map +1 -0
  25. package/dist/index-CZSc9drz.d.mts +47 -0
  26. package/dist/index-Dh4Zro0y.d.mts +15 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +3 -0
  29. package/dist/nextjs-control.d.mts +13 -0
  30. package/dist/nextjs-control.mjs +108 -0
  31. package/dist/nextjs-control.mjs.map +1 -0
  32. package/dist/nextjs.d.mts +2 -0
  33. package/dist/nextjs.mjs +12 -0
  34. package/dist/nextjs.mjs.map +1 -0
  35. package/dist/node-control.d.mts +9 -0
  36. package/dist/node-control.mjs +70 -0
  37. package/dist/node-control.mjs.map +1 -0
  38. package/dist/node.d.mts +10 -0
  39. package/dist/node.mjs +11 -0
  40. package/dist/node.mjs.map +1 -0
  41. package/dist/rpc.d.mts +43 -0
  42. package/dist/rpc.mjs +132 -0
  43. package/dist/rpc.mjs.map +1 -0
  44. package/dist/testing.d.mts +24 -0
  45. package/dist/testing.mjs +45 -0
  46. package/dist/testing.mjs.map +1 -0
  47. package/dist/tsdown.d.mts +9 -0
  48. package/dist/tsdown.mjs +35 -0
  49. package/dist/tsdown.mjs.map +1 -0
  50. package/package.json +68 -0
@@ -0,0 +1,595 @@
1
+ import { t as blindCast } from "./casts-Ci5rYYaR.mjs";
2
+ //#region ../../0-framework/1-core/core/dist/graph-BYdCQKya.mjs
3
+ /** Thrown by Load when the graph is malformed. */
4
+ var LoadError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "LoadError";
8
+ }
9
+ };
10
+ /**
11
+ * Core model: node types and the factories that construct them, plain frozen
12
+ * data objects. A node's `extension` + `type` form its deploy-time registry key (ADR-0017).
13
+ */
14
+ const NODE = Symbol.for("prisma:node");
15
+ const SECRET_NEED = blindCast(Symbol.for("prisma:secret-need"));
16
+ const SECRET_SOURCE = blindCast(Symbol.for("prisma:secret-source"));
17
+ /** Declares a secret NEED. Nameless — the platform name is bound at the root via `envSecret`. */
18
+ function secret() {
19
+ return Object.freeze({
20
+ [SECRET_NEED]: true,
21
+ kind: "secret"
22
+ });
23
+ }
24
+ /** Builds an opaque secret source from a target-defined payload — the SPI a deploy target's own source constructor (e.g. `envSecret`) calls. Core forwards the source and never inspects the payload. */
25
+ function secretSource(payload) {
26
+ return Object.freeze({
27
+ [SECRET_SOURCE]: true,
28
+ payload
29
+ });
30
+ }
31
+ /** True if `value` is a secret source (an `envSecret` result or a forwarded ctx.secrets ref). */
32
+ function isSecretSource(value) {
33
+ return typeof value === "object" && value !== null && blindCast(value)[SECRET_SOURCE] === true;
34
+ }
35
+ function requireType(type, factory) {
36
+ if (typeof type !== "string" || type.length === 0) throw new Error(`${factory}() requires a non-empty node type.`);
37
+ }
38
+ function requireName(name, factory) {
39
+ if (typeof name !== "string" || name.length === 0) throw new Error(`${factory}() requires a non-empty name.`);
40
+ }
41
+ function requireExtension(extension, factory) {
42
+ if (typeof extension !== "string" || extension.length === 0) throw new Error(`${factory}() requires a non-empty extension (the authoring extension's package name).`);
43
+ }
44
+ /**
45
+ * Config keys join address/input/param names with "_" and uppercase — an
46
+ * underscore inside a name would collide with that separator (e.g. param
47
+ * "db_url" vs input "db"'s param "url" both hitting env key "DB_URL").
48
+ */
49
+ function requireNoUnderscoreName(name, kind, factory) {
50
+ if (name.includes("_")) throw new Error(`${factory}() ${kind} name "${name}" may not contain "_" — config keys join names with "_" as the separator (e.g. an input "db"'s param "url" becomes env key "DB_URL"), so an underscore inside a name would collide with that separator.`);
51
+ }
52
+ function requireNoUnderscoreNames(names, kind, factory) {
53
+ for (const name of names) requireNoUnderscoreName(name, kind, factory);
54
+ }
55
+ function freezeParams(params) {
56
+ const frozen = {};
57
+ for (const [name, param] of Object.entries(params)) frozen[name] = Object.freeze({ ...param });
58
+ return Object.freeze(frozen);
59
+ }
60
+ function freezeSecrets(secrets) {
61
+ const frozen = {};
62
+ for (const [name, need] of Object.entries(secrets)) frozen[name] = Object.freeze({ ...need });
63
+ return blindCast(Object.freeze(frozen));
64
+ }
65
+ /** A frozen shallow copy that keeps the caller's declared type. */
66
+ function frozenShallowCopy(obj) {
67
+ return blindCast(Object.freeze({ ...obj }));
68
+ }
69
+ /**
70
+ * Seals a node instance after its constructor has assigned all fields — the
71
+ * last statement of a concrete node class's constructor. A free function, not
72
+ * a base-class method, so an instance stays structurally a plain frozen node.
73
+ */
74
+ function freezeNode(node) {
75
+ Object.freeze(node);
76
+ return node;
77
+ }
78
+ /**
79
+ * Everything `resource()` establishes, minus the freeze — an extension
80
+ * whose resource node carries extra fields extends this, assigns them, and
81
+ * calls `freezeNode(this)` as its constructor's last statement.
82
+ */
83
+ var ResourceNodeBase = class {
84
+ [NODE] = true;
85
+ kind = "resource";
86
+ name;
87
+ extension;
88
+ type;
89
+ provides;
90
+ constructor(def) {
91
+ requireName(def.name, "resource");
92
+ requireExtension(def.extension, "resource");
93
+ const provides = def.provides;
94
+ if (typeof provides !== "object" || provides === null || typeof provides.kind !== "string" || provides.kind.length === 0 || typeof provides.satisfies !== "function") throw new Error("resource() requires `provides` — the Contract this resource offers (a non-empty `kind` plus its `satisfies()`).");
95
+ this.name = def.name;
96
+ this.extension = def.extension;
97
+ this.type = provides.kind;
98
+ this.provides = provides;
99
+ }
100
+ };
101
+ /** The core leaf: exactly the base, frozen. */
102
+ var FrozenResourceNode = class extends ResourceNodeBase {
103
+ constructor(def) {
104
+ super(def);
105
+ freezeNode(this);
106
+ }
107
+ };
108
+ /**
109
+ * Constructs a branded, frozen Resource node — an identity plus the Contract
110
+ * it provides; the routing `type` is the contract's `kind`. Pure — nothing
111
+ * is provisioned until a module provisions it.
112
+ */
113
+ function resource(def) {
114
+ return new FrozenResourceNode(def);
115
+ }
116
+ /**
117
+ * Constructs a branded, frozen Service node — declarations only (inputs,
118
+ * params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
119
+ */
120
+ function service(def) {
121
+ requireName(def.name, "service");
122
+ requireExtension(def.extension, "service");
123
+ requireType(def.type, "service");
124
+ requireNoUnderscoreNames(Object.keys(def.inputs), "input", "service");
125
+ requireNoUnderscoreNames(Object.keys(def.params), "param", "service");
126
+ requireNoUnderscoreNames(Object.keys(def.secrets ?? {}), "secret", "service");
127
+ 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.`);
128
+ return Object.freeze({
129
+ [NODE]: true,
130
+ kind: "service",
131
+ name: def.name,
132
+ extension: def.extension,
133
+ type: def.type,
134
+ inputs: frozenShallowCopy(def.inputs),
135
+ params: freezeParams(def.params),
136
+ secretSlots: freezeSecrets(def.secrets ?? blindCast({})),
137
+ build: Object.freeze({ ...def.build }),
138
+ expose: def.expose !== void 0 ? frozenShallowCopy(def.expose) : void 0
139
+ });
140
+ }
141
+ /**
142
+ * Constructs a branded, frozen DependencyEnd. `required` (if given) is the
143
+ * contract Load compares a wired ref against via `satisfies()`; an unnamed
144
+ * end's diagnostic `name` falls back to its `type`.
145
+ */
146
+ function dependency(def) {
147
+ requireType(def.type, "dependency");
148
+ requireNoUnderscoreNames(Object.keys(def.connection.params), "param", "dependency");
149
+ const connection = Object.freeze({
150
+ params: freezeParams(def.connection.params),
151
+ hydrate: def.connection.hydrate
152
+ });
153
+ return Object.freeze({
154
+ [NODE]: true,
155
+ kind: "dependency",
156
+ name: def.name !== void 0 && def.name.length > 0 ? def.name : def.type,
157
+ type: def.type,
158
+ connection,
159
+ required: def.required
160
+ });
161
+ }
162
+ /**
163
+ * Constructs a branded, frozen Module node. Construction is INERT — the body is
164
+ * wiring, not user code, and runs only when the module is Loaded.
165
+ */
166
+ function module(name, boundaryOrBody, maybeBody) {
167
+ requireName(name, "module");
168
+ const closedRoot = typeof boundaryOrBody === "function";
169
+ const boundary = closedRoot ? {} : boundaryOrBody;
170
+ const deps = frozenShallowCopy(boundary.deps ?? {});
171
+ const secretSlots = frozenShallowCopy(boundary.secrets ?? {});
172
+ const expose = frozenShallowCopy(boundary.expose ?? {});
173
+ const body = closedRoot ? (ctx) => {
174
+ boundaryOrBody(ctx);
175
+ return {};
176
+ } : maybeBody;
177
+ return Object.freeze({
178
+ [NODE]: true,
179
+ kind: "module",
180
+ name,
181
+ deps,
182
+ secretSlots,
183
+ expose,
184
+ body
185
+ });
186
+ }
187
+ /**
188
+ * True if `value` was constructed by this module's factories. Checks the
189
+ * brand only, never a prototype — a graph may mix nodes from a different
190
+ * installed copy of core (dual-package hazard).
191
+ */
192
+ function isNode(value) {
193
+ return typeof value === "object" && value !== null && value[NODE] === true;
194
+ }
195
+ /**
196
+ * Stable topological sort: every edge's `from` precedes its `to` in the
197
+ * result. Ties (nodes with no ordering constraint between them) keep their
198
+ * relative order from `nodes` — so a graph already authored producer-first
199
+ * comes out byte-identical to its pre-sort layout; only a graph that
200
+ * genuinely needs reordering (e.g. a module wired via a forged ref pointing at a
201
+ * not-yet-provisioned producer) actually moves. A Kahn's-algorithm variant
202
+ * that always picks the ready node with the smallest original index. Edges
203
+ * whose endpoint falls outside `nodes` (e.g. a service-root's input edges
204
+ * targeting the root, which is appended separately) are ignored. Cycles
205
+ * cannot reach here: `assertDependencyDag` already rejects them for
206
+ * dependency edges, and input edges never cycle.
207
+ */
208
+ function topoSort(nodes, edges) {
209
+ const byId = new Map(nodes.map((n) => [n.id, n]));
210
+ const indexOf = new Map(nodes.map((n, i) => [n.id, i]));
211
+ const indegree = new Map(nodes.map((n) => [n.id, 0]));
212
+ const successors = /* @__PURE__ */ new Map();
213
+ for (const edge of edges) {
214
+ if (!byId.has(edge.from) || !byId.has(edge.to)) continue;
215
+ const targets = successors.get(edge.from) ?? [];
216
+ targets.push(edge.to);
217
+ successors.set(edge.from, targets);
218
+ indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1);
219
+ }
220
+ const ready = new Set(nodes.filter((n) => indegree.get(n.id) === 0).map((n) => n.id));
221
+ const order = [];
222
+ while (ready.size > 0) {
223
+ let next;
224
+ let bestIndex = Number.POSITIVE_INFINITY;
225
+ for (const id of ready) {
226
+ const index = indexOf.get(id) ?? Number.POSITIVE_INFINITY;
227
+ if (index < bestIndex) {
228
+ bestIndex = index;
229
+ next = id;
230
+ }
231
+ }
232
+ if (next === void 0) break;
233
+ ready.delete(next);
234
+ order.push(next);
235
+ for (const target of successors.get(next) ?? []) {
236
+ const remaining = (indegree.get(target) ?? 0) - 1;
237
+ indegree.set(target, remaining);
238
+ if (remaining === 0) ready.add(target);
239
+ }
240
+ }
241
+ if (order.length !== nodes.length) throw new LoadError(`topological sort processed ${order.length} of ${nodes.length} nodes — the graph contains a cycle that slipped past the DAG validation.`);
242
+ return order.map((id) => byId.get(id)).filter((n) => n !== void 0);
243
+ }
244
+ function serviceInputs(service, serviceId) {
245
+ if (typeof service.inputs !== "object" || service.inputs === null) throw new LoadError(`Service "${serviceId}" has no inputs map.`);
246
+ const nodes = [];
247
+ const edges = [];
248
+ for (const [input, value] of Object.entries(service.inputs)) {
249
+ const kind = isNode(value) ? value.kind : void 0;
250
+ if (kind === "resource") throw new LoadError(`Input "${input}" of "${serviceId}" is a resource node — a resource is provisioned by the composing module, never created for a service that mentions it. Declare the input as a dependency (the pack's dependency factory) and wire the module-provisioned resource's ref into it.`);
251
+ if (kind !== "dependency") throw new LoadError(`Input "${input}" of "${serviceId}" is not a branded dependency end (construct it with the dependency() factory).`);
252
+ if (value.type.length === 0) throw new LoadError(`Input "${input}" of "${serviceId}" has an empty node type.`);
253
+ const id = `${serviceId}.${input}`;
254
+ nodes.push({
255
+ id,
256
+ node: value
257
+ });
258
+ edges.push({
259
+ from: id,
260
+ to: serviceId,
261
+ input,
262
+ kind: "input"
263
+ });
264
+ }
265
+ return {
266
+ nodes,
267
+ edges
268
+ };
269
+ }
270
+ function loadService(root, rootId) {
271
+ for (const [input, value] of Object.entries(root.inputs)) if (isNode(value) && value.kind === "dependency") throw new LoadError(`Service "${rootId}" has an unwired dependency input "${input}" — this service is composed by a module; deploy the module instead of loading "${rootId}" directly.`);
272
+ const secretSlots = Object.keys(root.secretSlots);
273
+ if (secretSlots.length > 0) {
274
+ const names = secretSlots.map((k) => `"${k}"`).join(", ");
275
+ throw new LoadError(`Service "${rootId}" declares secret slot${secretSlots.length > 1 ? "s" : ""} ${names} but is being loaded directly — a lone service has no enclosing scope to bind them. Compose it inside a module that binds each with envSecret('NAME').`);
276
+ }
277
+ const rootGraphNode = {
278
+ id: rootId,
279
+ node: root
280
+ };
281
+ const { nodes, edges } = serviceInputs(root, rootId);
282
+ return {
283
+ root: rootGraphNode,
284
+ nodes: [...topoSort(nodes, edges), rootGraphNode],
285
+ edges,
286
+ secrets: []
287
+ };
288
+ }
289
+ /**
290
+ * Builds the ref a provision() call hands back: the id (so a producer with no
291
+ * exposed ports — or an untyped slot — can still be wired wholesale) plus one
292
+ * ref-port per exposed contract, each the contract's own runtime value (so
293
+ * its `satisfies()` still works) tagged with the provider's id.
294
+ */
295
+ function refFor(id, service) {
296
+ const ports = {};
297
+ for (const [port, contract] of Object.entries(service.expose ?? {})) ports[port] = {
298
+ ...contract,
299
+ __providerId: id
300
+ };
301
+ return blindCast({
302
+ id,
303
+ ...ports
304
+ });
305
+ }
306
+ /**
307
+ * The resource variant of refFor: a resource has exactly one port — the
308
+ * contract it provides — flattened onto the ref itself, tagged with the
309
+ * provider id. `id` is written last so a hostile contract value cannot
310
+ * clobber it.
311
+ */
312
+ function refForResource(id, resource) {
313
+ return blindCast({
314
+ ...resource.provides,
315
+ __providerId: id,
316
+ id
317
+ });
318
+ }
319
+ /** A wired value's producer id: a ref-port's `__providerId`, or a bare ref's `id`. */
320
+ function producerIdOf(ref) {
321
+ if (typeof ref !== "object" || ref === null) return void 0;
322
+ if ("__providerId" in ref && typeof ref.__providerId === "string") return ref.__providerId;
323
+ if ("id" in ref && typeof ref.id === "string") return ref.id;
324
+ }
325
+ /**
326
+ * Brands each `ctx.inputs` entry with the input key it stands for (see
327
+ * flatten): diagnostic only — usage attribution relies on the per-key object
328
+ * identity the branding copy creates, never on reading this back.
329
+ */
330
+ const MODULE_INPUT_KEY = Symbol("prisma:module-input-key");
331
+ /** Same per-key identity trick as MODULE_INPUT_KEY, for the parallel `ctx.secrets` forwarding channel. */
332
+ const MODULE_SECRET_KEY = Symbol("prisma:module-secret-key");
333
+ /** Whether `ref` carries a callable `satisfies` that accepts `required` truthily. */
334
+ function satisfiesRequired(ref, required) {
335
+ return typeof ref === "object" && ref !== null && "satisfies" in ref && typeof ref.satisfies === "function" && ref.satisfies(required);
336
+ }
337
+ /**
338
+ * Checks one recorded `wiring` object against the Deps it was wired against:
339
+ * every named input exists and is a dependency slot, every referenced
340
+ * producer is a real (by-now-provisioned) address, and a wired ref whose
341
+ * slot declares a required contract must satisfy() it — no producer-kind
342
+ * branching, the contract alone determines validity, whether the producer is
343
+ * a service port or a resource. Shared by both provisioned kinds so a
344
+ * module-as-child gets exactly the checks a service gets, and run once per
345
+ * entry against the one `byId` shared by the whole recursive flatten, so a
346
+ * forwarded ref resolves through to its real producer address regardless of
347
+ * which ancestor scope provisioned it.
348
+ */
349
+ function validateWiring(pending, byId) {
350
+ const { deps, wiring, targetId, targetKind, enclosingModuleName } = pending;
351
+ for (const [input, ref] of Object.entries(wiring)) {
352
+ const declared = blindCast(deps[input]);
353
+ if (declared === void 0 || !isNode(declared) || declared.kind !== "dependency") throw new LoadError(`The deps for "${targetId}" name "${input}", which is not a dependency slot of that ${targetKind}.`);
354
+ const producerId = producerIdOf(ref);
355
+ const producer = producerId !== void 0 ? byId.get(producerId) : void 0;
356
+ if (producerId === void 0 || producer === void 0) throw new LoadError(`The deps for "${targetId}.${input}" reference "${String(producerId)}", which is not provisioned in module "${enclosingModuleName}".`);
357
+ const required = declared.required;
358
+ if (required !== void 0 && !satisfiesRequired(ref, required)) throw new LoadError(`The deps for "${targetId}.${input}" do not satisfy the slot's required contract.`);
359
+ }
360
+ for (const [input, rawValue] of Object.entries(deps)) {
361
+ const value = blindCast(rawValue);
362
+ if (!isNode(value) || wiring[input] !== void 0) continue;
363
+ if (value.kind === "dependency") throw new LoadError(`Dependency input "${input}" of provisioned ${targetKind} "${targetId}" is not wired to a producer (module "${enclosingModuleName}").`);
364
+ }
365
+ }
366
+ /** The (unvalidated) edges a `wiring` object implies — one dependency edge per entry; `validateWiring` does the real checking. */
367
+ function wiringEdges(wiring, targetId) {
368
+ return Object.entries(wiring).map(([input, ref]) => ({
369
+ from: producerIdOf(ref) ?? "",
370
+ to: targetId,
371
+ input,
372
+ kind: "dependency"
373
+ }));
374
+ }
375
+ /**
376
+ * Checks the secrets wired into one provisioned child: every declared slot is
377
+ * bound to a real secret source (an `envSecret` or a forwarded ctx.secrets
378
+ * ref), and no wired key names a slot the child doesn't declare — the secret
379
+ * analog of `validateWiring`'s per-input checks, but resolved inline (a source
380
+ * carries its own name, so no whole-graph pass is needed).
381
+ */
382
+ function validateSecretBinding(child, id, secretWiring, enclosingModuleName) {
383
+ const { kind } = child;
384
+ for (const slot of Object.keys(child.secretSlots)) {
385
+ const bound = secretWiring[slot];
386
+ if (bound === void 0) throw new LoadError(`Secret slot "${slot}" of provisioned ${kind} "${id}" is not bound (module "${enclosingModuleName}") — bind it with envSecret('NAME') or forward ctx.secrets.`);
387
+ if (!isSecretSource(bound)) throw new LoadError(`Secret slot "${slot}" of "${id}" (module "${enclosingModuleName}") was wired with a non-secret value — use envSecret('NAME') or a forwarded ctx.secrets ref.`);
388
+ }
389
+ for (const slot of Object.keys(secretWiring)) if (!Object.hasOwn(child.secretSlots, slot)) throw new LoadError(`The secrets for "${id}" name "${slot}", which is not a secret slot of that ${kind} (module "${enclosingModuleName}").`);
390
+ }
391
+ /**
392
+ * Recursively flattens one module's body into the shared graph state and
393
+ * returns its resolved ModuleOutputs (one ref-port per expose key) for the
394
+ * caller (the enclosing provision() call, or Load itself for the root) to
395
+ * use. `address` is this module's OWN full address, or `undefined` for the root
396
+ * scope — its direct children then get bare (unprefixed) addresses, keeping
397
+ * a single-level module identical to before nesting existed. `wiring` supplies a
398
+ * resolved producer ref-port for each of this module's OWN declared deps (empty
399
+ * for the root, which may not declare any — see the root non-empty-deps
400
+ * check in loadModule). `nodes`, `edges`, `pending`, and `byId` are shared
401
+ * across the ENTIRE recursive flatten, not per scope — a nested module may
402
+ * forward in a producer provisioned by an ancestor scope, and it is the
403
+ * shared `byId` (keyed by full address) that lets that resolve.
404
+ */
405
+ function flatten(moduleNode, address, wiring, secretWiring, nodes, edges, pending, secretBindings, byId) {
406
+ const localIds = /* @__PURE__ */ new Set();
407
+ const used = /* @__PURE__ */ new Set();
408
+ const usedSecrets = /* @__PURE__ */ new Set();
409
+ const ctxInputs = {};
410
+ for (const key of Object.keys(moduleNode.deps)) {
411
+ const wired = wiring[key];
412
+ ctxInputs[key] = typeof wired === "object" && wired !== null ? {
413
+ ...wired,
414
+ [MODULE_INPUT_KEY]: key
415
+ } : wired;
416
+ }
417
+ const markUsed = (values) => {
418
+ for (const value of Object.values(values)) for (const key of Object.keys(ctxInputs)) if (value === ctxInputs[key]) used.add(key);
419
+ };
420
+ const ctxSecrets = {};
421
+ for (const key of Object.keys(moduleNode.secretSlots)) {
422
+ const bound = secretWiring[key];
423
+ ctxSecrets[key] = typeof bound === "object" && bound !== null ? {
424
+ ...bound,
425
+ [MODULE_SECRET_KEY]: key
426
+ } : bound;
427
+ }
428
+ const markSecretsUsed = (values) => {
429
+ for (const value of Object.values(values)) for (const key of Object.keys(ctxSecrets)) if (value === ctxSecrets[key]) usedSecrets.add(key);
430
+ };
431
+ const provision = (child, opts) => {
432
+ const id = opts?.id ?? child.name;
433
+ const provisionWiring = opts?.deps;
434
+ const provisionSecrets = opts?.secrets;
435
+ if (typeof id !== "string" || id.length === 0) throw new LoadError(`provision() requires a non-empty id (module "${moduleNode.name}").`);
436
+ if (id.includes("_") || id.includes(".")) throw new LoadError(`provision() id "${id}" (module "${moduleNode.name}") may not contain "_" or "." — "_" is the config-key separator and "." the node-id path separator; either inside an id collides with the joined form of other names.`);
437
+ if (localIds.has(id)) throw new LoadError(`Duplicate provision id "${id}" in module "${moduleNode.name}".`);
438
+ const untrusted = child;
439
+ if (!isNode(untrusted) || untrusted.kind !== "service" && untrusted.kind !== "resource" && untrusted.kind !== "module") throw new LoadError(`provision("${id}") expects a branded service, resource, or module node (construct it with the service()/resource()/module() factories or a pack's own).`);
440
+ localIds.add(id);
441
+ const fullAddress = address === void 0 ? id : `${address}.${id}`;
442
+ if (child.kind === "resource") {
443
+ if (provisionWiring !== void 0) throw new LoadError(`provision("${id}") received deps for a resource — a resource has no dependency slots to satisfy.`);
444
+ if (provisionSecrets !== void 0) throw new LoadError(`provision("${id}") received secrets for a resource — a resource has no secret slots to satisfy.`);
445
+ if (child.type.length === 0) throw new LoadError(`provision("${id}") received a resource with an empty node type.`);
446
+ byId.set(fullAddress, child);
447
+ nodes.push({
448
+ id: fullAddress,
449
+ node: child
450
+ });
451
+ return refForResource(fullAddress, child);
452
+ }
453
+ const localWiring = { ...provisionWiring ?? {} };
454
+ markUsed(localWiring);
455
+ const localSecrets = { ...provisionSecrets ?? {} };
456
+ markSecretsUsed(localSecrets);
457
+ validateSecretBinding(child, id, localSecrets, moduleNode.name);
458
+ if (child.kind === "service") {
459
+ for (const slot of Object.keys(child.secretSlots)) {
460
+ const bound = localSecrets[slot];
461
+ if (isSecretSource(bound)) secretBindings.push({
462
+ serviceAddress: fullAddress,
463
+ slot,
464
+ source: bound
465
+ });
466
+ }
467
+ const inputs = serviceInputs(child, fullAddress);
468
+ nodes.push(...inputs.nodes, {
469
+ id: fullAddress,
470
+ node: child
471
+ });
472
+ edges.push(...inputs.edges, ...wiringEdges(localWiring, fullAddress));
473
+ pending.push({
474
+ deps: child.inputs,
475
+ wiring: localWiring,
476
+ targetId: fullAddress,
477
+ targetKind: "service",
478
+ enclosingModuleName: moduleNode.name
479
+ });
480
+ byId.set(fullAddress, child);
481
+ return refFor(fullAddress, child);
482
+ }
483
+ edges.push(...wiringEdges(localWiring, fullAddress));
484
+ pending.push({
485
+ deps: child.deps,
486
+ wiring: localWiring,
487
+ targetId: fullAddress,
488
+ targetKind: "module",
489
+ enclosingModuleName: moduleNode.name
490
+ });
491
+ const childOutputs = flatten(child, fullAddress, localWiring, localSecrets, nodes, edges, pending, secretBindings, byId);
492
+ nodes.push({
493
+ id: fullAddress,
494
+ node: child
495
+ });
496
+ byId.set(fullAddress, child);
497
+ return blindCast({
498
+ id: fullAddress,
499
+ ...childOutputs
500
+ });
501
+ };
502
+ const ctx = blindCast({
503
+ inputs: ctxInputs,
504
+ secrets: ctxSecrets,
505
+ provision: blindCast(provision)
506
+ });
507
+ const outputs = blindCast(moduleNode.body(ctx) ?? {});
508
+ markUsed(outputs);
509
+ for (const key of Object.keys(moduleNode.deps)) if (!used.has(key)) throw new LoadError(`Module "${moduleNode.name}" declares input "${key}" but never forwards it into a provision nor returns it as an output.`);
510
+ for (const key of Object.keys(moduleNode.secretSlots)) if (!usedSecrets.has(key)) throw new LoadError(`Module "${moduleNode.name}" declares secret "${key}" but never forwards it into a provision.`);
511
+ for (const [key, contract] of Object.entries(moduleNode.expose)) {
512
+ const port = outputs[key];
513
+ if (port === void 0) throw new LoadError(`Module "${moduleNode.name}" declares expose "${key}" but its body did not return a port for it.`);
514
+ if (!satisfiesRequired(port, contract)) throw new LoadError(`Module "${moduleNode.name}"'s returned port for expose "${key}" does not satisfy its declared contract.`);
515
+ }
516
+ return outputs;
517
+ }
518
+ function loadModule(root, opts) {
519
+ const rootId = opts?.id ?? root.name;
520
+ const rootDepKeys = Object.keys(root.deps);
521
+ if (rootDepKeys.length > 0) {
522
+ const names = rootDepKeys.map((k) => `"${k}"`).join(", ");
523
+ throw new LoadError(`Module "${root.name}" declares input${rootDepKeys.length > 1 ? "s" : ""} ${names} but is being deployed as the root — a root has no enclosing scope to wire them; compose "${root.name}" from another module that provisions and wires it instead.`);
524
+ }
525
+ const rootSecretKeys = Object.keys(root.secretSlots);
526
+ if (rootSecretKeys.length > 0) {
527
+ const names = rootSecretKeys.map((k) => `"${k}"`).join(", ");
528
+ throw new LoadError(`Module "${root.name}" declares secret${rootSecretKeys.length > 1 ? "s" : ""} ${names} but is being deployed as the root — a root has no enclosing scope to bind them; the root binds secrets with envSecret('NAME'), it does not declare secret slots of its own.`);
529
+ }
530
+ const nodes = [];
531
+ const edges = [];
532
+ const pending = [];
533
+ const secretBindings = [];
534
+ const byId = /* @__PURE__ */ new Map();
535
+ flatten(root, void 0, {}, {}, nodes, edges, pending, secretBindings, byId);
536
+ for (const entry of pending) validateWiring(entry, byId);
537
+ assertDependencyDag(edges);
538
+ const rootGraphNode = {
539
+ id: rootId,
540
+ node: root
541
+ };
542
+ return {
543
+ root: rootGraphNode,
544
+ nodes: [...topoSort(nodes, edges), rootGraphNode],
545
+ edges,
546
+ secrets: secretBindings
547
+ };
548
+ }
549
+ /**
550
+ * The dependency edges must form a DAG — a cycle means neither producer can
551
+ * deploy first. Resources take no wiring, so only service/module-to-service/module
552
+ * edges can ever participate in a cycle; no special-casing needed.
553
+ */
554
+ function assertDependencyDag(edges) {
555
+ const adjacency = /* @__PURE__ */ new Map();
556
+ for (const edge of edges) {
557
+ if (edge.kind !== "dependency") continue;
558
+ const targets = adjacency.get(edge.from) ?? [];
559
+ targets.push(edge.to);
560
+ adjacency.set(edge.from, targets);
561
+ }
562
+ const visiting = /* @__PURE__ */ new Set();
563
+ const done = /* @__PURE__ */ new Set();
564
+ const stack = [];
565
+ const visit = (id) => {
566
+ if (done.has(id)) return;
567
+ if (visiting.has(id)) throw new LoadError(`Dependency cycle: ${[...stack.slice(stack.indexOf(id)), id].join(" → ")} — no deploy order exists.`);
568
+ visiting.add(id);
569
+ stack.push(id);
570
+ for (const next of adjacency.get(id) ?? []) visit(next);
571
+ stack.pop();
572
+ visiting.delete(id);
573
+ done.add(id);
574
+ };
575
+ for (const id of adjacency.keys()) visit(id);
576
+ }
577
+ /**
578
+ * Builds the in-memory graph from a root node. A service root walks its own
579
+ * `inputs`; a module root executes its body (wiring, not user code — the
580
+ * designed exception to imports-run-nothing) and recursively flattens every
581
+ * module it provisions into one graph of hierarchical addresses. A malformed
582
+ * graph is a `LoadError` that names its fix; the individual validation rules
583
+ * live with `loadService` / `loadModule` and are covered by name in the Load
584
+ * tests. Executes nothing of the user's own code beyond module bodies.
585
+ */
586
+ function Load(root, opts) {
587
+ if (!isNode(root)) throw new LoadError("Load expects a branded service or module node (construct it with the service()/module() factories).");
588
+ if (root.kind === "module") return loadModule(root, opts);
589
+ if (root.kind === "service") return loadService(root, opts?.id ?? "root");
590
+ throw new LoadError("Load expects a service or module root (received another node kind).");
591
+ }
592
+ //#endregion
593
+ export { freezeNode as a, module as c, secretSource as d, service as f, dependency as i, resource as l, LoadError as n, isNode as o, ResourceNodeBase as r, isSecretSource as s, Load as t, secret as u };
594
+
595
+ //# sourceMappingURL=graph-BYdCQKya-BI0njTow.mjs.map