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