@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.
- package/LICENSE +201 -0
- package/dist/app-config-BUqyK6N6-CVq3uvHF.d.mts +188 -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 +1134 -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-BVVgDSdq.d.mts +1 -0
- package/dist/config-ob5OhCSP-sP3GW3uu.d.mts +477 -0
- package/dist/config.d.mts +2 -0
- package/dist/config.mjs +9 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deploy-BVVgDSdq.d.mts +1 -0
- package/dist/deploy.d.mts +2 -0
- package/dist/deploy.mjs +154 -0
- package/dist/deploy.mjs.map +1 -0
- package/dist/dist-zBU8ASQW.mjs +181 -0
- package/dist/dist-zBU8ASQW.mjs.map +1 -0
- package/dist/graph-BYdCQKya-BI0njTow.mjs +595 -0
- package/dist/graph-BYdCQKya-BI0njTow.mjs.map +1 -0
- package/dist/index-CZSc9drz.d.mts +47 -0
- package/dist/index-Dh4Zro0y.d.mts +15 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/nextjs-control.d.mts +13 -0
- package/dist/nextjs-control.mjs +108 -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 +9 -0
- package/dist/node-control.mjs +70 -0
- package/dist/node-control.mjs.map +1 -0
- package/dist/node.d.mts +10 -0
- package/dist/node.mjs +11 -0
- package/dist/node.mjs.map +1 -0
- package/dist/rpc.d.mts +43 -0
- package/dist/rpc.mjs +132 -0
- package/dist/rpc.mjs.map +1 -0
- package/dist/testing.d.mts +24 -0
- package/dist/testing.mjs +45 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/tsdown.d.mts +9 -0
- package/dist/tsdown.mjs +35 -0
- package/dist/tsdown.mjs.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
|
|
3
|
+
//#region ../../0-framework/0-foundation/foundation/dist/secret.d.mts
|
|
4
|
+
//#region src/secret.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A value wrapper that redacts everywhere except the one explicit reader,
|
|
7
|
+
* `expose()`. Sensitivity is carried by the TYPE (`SecretBox<T>`), not a flag a
|
|
8
|
+
* sink must remember to check: `String(box)`, template interpolation,
|
|
9
|
+
* `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
|
|
10
|
+
* a secret can't leak through an accidental log or serialization.
|
|
11
|
+
*
|
|
12
|
+
* Shape matches the platform's own `secrecy` type (pdp-control-plane). The class
|
|
13
|
+
* is nominal enough on its own — no phantom brand.
|
|
14
|
+
*/
|
|
15
|
+
declare class SecretBox<T> {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(value: T);
|
|
18
|
+
/** The sole explicit door to the wrapped value. */
|
|
19
|
+
expose(): T;
|
|
20
|
+
toString(): string;
|
|
21
|
+
toJSON(): string;
|
|
22
|
+
valueOf(): string;
|
|
23
|
+
[Symbol.toPrimitive](): string;
|
|
24
|
+
}
|
|
25
|
+
/** The common case: a secret string. */
|
|
26
|
+
type SecretString = SecretBox<string>; //#endregion
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region ../../0-framework/1-core/core/dist/config-ob5OhCSP.d.mts
|
|
29
|
+
//#region src/contract.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* A Contract is the declared interface of a service-to-service dependency: a
|
|
32
|
+
* protocol brand (`kind`) plus an opaque comparison type (`Cmp`) the core
|
|
33
|
+
* never inspects. Wiring compatibility is plain TypeScript assignability on
|
|
34
|
+
* `Cmp`, checked at `ModuleBuilder.provision`'s call site (node.ts); `satisfies`
|
|
35
|
+
* is its runtime mirror, called at Load (graph.ts). Correctness comes from
|
|
36
|
+
* the kind's builder shaping `Cmp` so assignability means the right thing —
|
|
37
|
+
* see @prisma/composer/rpc's `contract()`/`rpc()`.
|
|
38
|
+
*/
|
|
39
|
+
interface Contract<Kind extends string, Cmp> {
|
|
40
|
+
readonly kind: Kind;
|
|
41
|
+
readonly __cmp: Cmp;
|
|
42
|
+
satisfies(required: Contract<Kind, unknown>): boolean;
|
|
43
|
+
} //#endregion
|
|
44
|
+
//#region src/node.d.ts
|
|
45
|
+
declare const NODE: unique symbol;
|
|
46
|
+
declare const SECRET_NEED: unique symbol;
|
|
47
|
+
declare const SECRET_SOURCE: unique symbol;
|
|
48
|
+
/** A declared secret input slot — nameless; the root binds it and the topology forwards it in. */
|
|
49
|
+
interface SecretNeed {
|
|
50
|
+
readonly [SECRET_NEED]: true;
|
|
51
|
+
readonly kind: 'secret';
|
|
52
|
+
}
|
|
53
|
+
/** A service/module's secret slots: name → the need it declares. */
|
|
54
|
+
type Secrets = Record<string, SecretNeed>;
|
|
55
|
+
/** The wiring value bound to a secret slot: a target-defined payload core forwards but never inspects. A target (e.g. @prisma/composer-prisma-cloud's `envSecret`) builds one via `secretSource()`. */
|
|
56
|
+
interface SecretSource<T = unknown> {
|
|
57
|
+
readonly [SECRET_SOURCE]: true;
|
|
58
|
+
/** Target-defined. Core never reads this; the target that authored the source reads it back. */
|
|
59
|
+
readonly payload: T;
|
|
60
|
+
}
|
|
61
|
+
/** What `provision(node, { secrets })` supplies: one source per declared secret slot. */
|
|
62
|
+
type SecretBindings<S extends Secrets> = { [K in keyof S]: SecretSource };
|
|
63
|
+
/** What `secrets()` returns: one redacting SecretBox per declared slot. */
|
|
64
|
+
type SecretValues<S extends Secrets> = { readonly [K in keyof S]: SecretString };
|
|
65
|
+
/** Declares a secret NEED. Nameless — the platform name is bound at the root via `envSecret`. */
|
|
66
|
+
declare function secret(): SecretNeed;
|
|
67
|
+
/** 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. */
|
|
68
|
+
declare function secretSource<T>(payload: T): SecretSource<T>;
|
|
69
|
+
/** True if `value` is a secret source (an `envSecret` result or a forwarded ctx.secrets ref). */
|
|
70
|
+
declare function isSecretSource(value: unknown): value is SecretSource;
|
|
71
|
+
/** Opaque `Contract<any, any>` bound shared by every node/port type that doesn't care which contract. */
|
|
72
|
+
type AnyContract = Contract<any, any>;
|
|
73
|
+
/** How a service's app becomes a runnable artifact — the build descriptor's routing key (`extension`/`type`) plus paths resolved relative to the authoring module. */
|
|
74
|
+
interface BuildAdapter {
|
|
75
|
+
/** The extension package that provides the build descriptor, e.g. "@prisma/composer/node". */
|
|
76
|
+
readonly extension: string;
|
|
77
|
+
/** The build descriptor's node ID within its extension, e.g. "node" · "nextjs". */
|
|
78
|
+
readonly type: string;
|
|
79
|
+
/** The authoring module's `import.meta.url` — every other path on this descriptor resolves relative to `dirname(module)`. */
|
|
80
|
+
readonly module: string;
|
|
81
|
+
/** The app's built runnable, resolved relative to `dirname(module)` and interpreted by the type's build descriptor (e.g. "node": a server file; "nextjs": located in the standalone tree). */
|
|
82
|
+
readonly entry: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A Resource's identity: the one place a piece of infrastructure exists.
|
|
86
|
+
* Provisioned by a module, never embedded in a service's deps. `provides`
|
|
87
|
+
* is the Contract the resource offers; `type` is derived from `provides.kind`.
|
|
88
|
+
*/
|
|
89
|
+
interface ResourceNode<C extends AnyContract = AnyContract> {
|
|
90
|
+
readonly [NODE]: true;
|
|
91
|
+
readonly kind: 'resource';
|
|
92
|
+
/** Human-readable, given at authoring — logs/diagnostics only; identity remains the deploy address (ADR-0006). */
|
|
93
|
+
readonly name: string;
|
|
94
|
+
/** The extension package that authored this node, e.g. "@prisma/composer-prisma-cloud" — the registry key at deploy. */
|
|
95
|
+
readonly extension: string;
|
|
96
|
+
readonly type: C['kind'];
|
|
97
|
+
/** The Contract this resource provides — the resource's single port. */
|
|
98
|
+
readonly provides: C;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A Service: inputs + its own declared params + how it is built. Inspectable,
|
|
102
|
+
* inert until run, and carries NO runtime behavior — an extension's factory
|
|
103
|
+
* wraps it into a runnable/loadable shape (see RunnableServiceNode).
|
|
104
|
+
*/
|
|
105
|
+
interface ServiceNode<D extends Deps = Deps, P extends Params = Params, E extends Expose = Expose, S extends Secrets = Secrets> {
|
|
106
|
+
readonly [NODE]: true;
|
|
107
|
+
readonly kind: 'service';
|
|
108
|
+
/** Human-readable, given at authoring — logs/diagnostics only; identity remains the deploy address (ADR-0006). */
|
|
109
|
+
readonly name: string;
|
|
110
|
+
/** The extension package that authored this node, e.g. "@prisma/composer-prisma-cloud" — the registry key at deploy. */
|
|
111
|
+
readonly extension: string;
|
|
112
|
+
readonly type: string;
|
|
113
|
+
readonly inputs: D;
|
|
114
|
+
/** Service-level config declarations (e.g. port). */
|
|
115
|
+
readonly params: P;
|
|
116
|
+
/** Declared secret input slots (authored as `secrets`) — bound at the root via `envSecret`, read via the `secrets()` accessor (ADR-0029). Named `secretSlots` on the node so the data field does not collide with that accessor. */
|
|
117
|
+
readonly secretSlots: S;
|
|
118
|
+
/** How the app's entry is built + assembled. */
|
|
119
|
+
readonly build: BuildAdapter;
|
|
120
|
+
/** Named output ports this service exposes — the Contracts a consumer's `rpc(contract)` can require. `undefined` when the service exposes nothing. */
|
|
121
|
+
readonly expose: E | undefined;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The extension's runnable/loadable service node. `run` boots the app after
|
|
125
|
+
* deserializing its Config; `load`/`config` then read deps/params back out
|
|
126
|
+
* (kept separate per ADR-0021 so a same-named dep and param never collide).
|
|
127
|
+
*/
|
|
128
|
+
interface RunnableServiceNode<D extends Deps = Deps, P extends Params = Params, E extends Expose = Expose, S extends Secrets = Secrets> extends ServiceNode<D, P, E, S> {
|
|
129
|
+
run(address: string, boot: () => Promise<unknown>): Promise<unknown>;
|
|
130
|
+
load(): HydratedDeps<D>;
|
|
131
|
+
config(): Values<P>;
|
|
132
|
+
/** The service's secrets, each a redacting SecretBox — a third accessor beside load()/config() (ADR-0021). */
|
|
133
|
+
secrets(): SecretValues<S>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A service's dependency slot. At Load the enclosing module wires a
|
|
137
|
+
* producer's ref into it; at run it hydrates a client via Connection. `Req`
|
|
138
|
+
* is the required contract (`unknown` for an untyped end like `http()`).
|
|
139
|
+
*/
|
|
140
|
+
interface DependencyEnd<C = unknown, Req = unknown> {
|
|
141
|
+
readonly [NODE]: true;
|
|
142
|
+
readonly kind: 'dependency';
|
|
143
|
+
/** Human-readable, given at authoring — logs/diagnostics only. */
|
|
144
|
+
readonly name: string;
|
|
145
|
+
readonly type: string;
|
|
146
|
+
readonly connection: Connection<Params, C>;
|
|
147
|
+
/** The required contract, or `undefined` for an untyped end (e.g. `http()`). */
|
|
148
|
+
readonly required: Req | undefined;
|
|
149
|
+
}
|
|
150
|
+
/** A Module: the same Deps/Expose boundary a service has, around transparent wiring instead of a black-box body — its `body` runs at Load, not at authoring. */
|
|
151
|
+
interface ModuleNode<D extends Deps = Deps, E extends Expose = Expose, S extends Secrets = Secrets> {
|
|
152
|
+
readonly [NODE]: true;
|
|
153
|
+
readonly kind: 'module';
|
|
154
|
+
/** Human-readable, given at authoring — logs/diagnostics only. */
|
|
155
|
+
readonly name: string;
|
|
156
|
+
readonly deps: D;
|
|
157
|
+
/** Declared secret input slots (authored as `secrets`) — forwarded to internals via `ctx.secrets` (ADR-0029). */
|
|
158
|
+
readonly secretSlots: S;
|
|
159
|
+
readonly expose: E;
|
|
160
|
+
body(ctx: ModuleContext<D, S>): ModuleOutputs<E> | void;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* What a module's body receives: its declared inputs as forwardable wiring
|
|
164
|
+
* values, plus `provision` to register the owned services/modules it wires them into.
|
|
165
|
+
*/
|
|
166
|
+
interface ModuleContext<D extends Deps, S extends Secrets = Secrets> {
|
|
167
|
+
/** The module's declared inputs as wiring values — pass them into provision(). */
|
|
168
|
+
readonly inputs: { [K in keyof D]: InputRef<D[K]> };
|
|
169
|
+
/** The module's declared secret slots as forwardable sources — pass them into a child's `secrets` (ADR-0029). */
|
|
170
|
+
readonly secrets: { readonly [K in keyof S]: SecretSource };
|
|
171
|
+
/** Registers an owned child (service or module) under a stable id. */
|
|
172
|
+
readonly provision: ModuleBuilder['provision'];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A module's forwarded-input value: the same ref-port shape a producer's
|
|
176
|
+
* output carries, so it flows down a nested `provision()` call indistinguishably
|
|
177
|
+
* from a sibling's exposed port.
|
|
178
|
+
*/
|
|
179
|
+
type InputRef<DE> = DE extends DependencyEnd<any, infer Req extends AnyContract> ? RefPort<Req> : never;
|
|
180
|
+
/** One ref-port per declared expose key, contract-checked against `E` (mirrors `Wiring`'s `NoInfer` use). */
|
|
181
|
+
type ModuleOutputs<E extends Expose> = { [P in keyof E]: RefPort<NoInfer<E[P]>> };
|
|
182
|
+
/**
|
|
183
|
+
* A provisioned producer's port as a wiring-time value: its contract, tagged
|
|
184
|
+
* with which provider produced it (`__providerId`, read by Load to resolve the edge).
|
|
185
|
+
*/
|
|
186
|
+
type RefPort<C extends AnyContract> = C & {
|
|
187
|
+
readonly __providerId: string;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* What `provision(id, service)` hands back: a stable id plus one ref-port per
|
|
191
|
+
* exposed contract. `provision(id, resource)` returns the same shape with the
|
|
192
|
+
* resource's one port flattened onto the ref itself.
|
|
193
|
+
*/
|
|
194
|
+
type ProvisionedRef<E extends Expose = Record<never, never>> = {
|
|
195
|
+
readonly id: string;
|
|
196
|
+
} & { readonly [P in keyof E]: RefPort<E[P]> };
|
|
197
|
+
/** A DependencyEnd's required contract (unknown for an untyped end). */
|
|
198
|
+
type ReqOf<DE> = DE extends DependencyEnd<any, infer Req> ? Req : never;
|
|
199
|
+
/**
|
|
200
|
+
* The producers that satisfy a node's declared dependency slots — one ref per
|
|
201
|
+
* slot, checked against its required contract. A slot also accepts
|
|
202
|
+
* `InputRef<D[K]>` so a module body can forward its own `ctx.inputs` straight
|
|
203
|
+
* into a nested `provision()` call — the same value shape a producer's own
|
|
204
|
+
* exposed port carries.
|
|
205
|
+
*/
|
|
206
|
+
type DepBindings<D extends Deps> = { [K in keyof D]: NoInfer<ReqOf<D[K]>> | InputRef<D[K]> };
|
|
207
|
+
/**
|
|
208
|
+
* `provision`'s trailing options: an explicit `id` (default: the node's own
|
|
209
|
+
* `name`) plus, for a node that declares dependency slots, the `deps` that
|
|
210
|
+
* satisfy them. `deps` is required exactly when the node has slots —
|
|
211
|
+
* `[keyof D] extends [never]` is the "no slots" test — so a dependency can
|
|
212
|
+
* never be left unwired at compile time. The whole object is therefore
|
|
213
|
+
* optional for a slot-less node and required for one with slots.
|
|
214
|
+
*/
|
|
215
|
+
type ProvisionArgs<D extends Deps, S extends Secrets> = [keyof D] extends [never] ? [keyof S] extends [never] ? [opts?: {
|
|
216
|
+
id?: string;
|
|
217
|
+
}] : [opts: {
|
|
218
|
+
id?: string;
|
|
219
|
+
secrets: SecretBindings<S>;
|
|
220
|
+
}] : [keyof S] extends [never] ? [opts: {
|
|
221
|
+
id?: string;
|
|
222
|
+
deps: DepBindings<D>;
|
|
223
|
+
}] : [opts: {
|
|
224
|
+
id?: string;
|
|
225
|
+
deps: DepBindings<D>;
|
|
226
|
+
secrets: SecretBindings<S>;
|
|
227
|
+
}];
|
|
228
|
+
interface ModuleBuilder {
|
|
229
|
+
/** Provisions an owned resource; its id defaults to the node's `name`. */
|
|
230
|
+
provision<C extends AnyContract>(resource: ResourceNode<C>, opts?: {
|
|
231
|
+
id?: string;
|
|
232
|
+
}): {
|
|
233
|
+
readonly id: string;
|
|
234
|
+
} & RefPort<C>;
|
|
235
|
+
/** Registers an owned service; its id defaults to the node's `name`; `deps`/`secrets` are required iff it declares them. */
|
|
236
|
+
provision<D extends Deps, E extends Expose, S extends Secrets>(service: ServiceNode<D, any, E, S>, ...args: ProvisionArgs<D, S>): ProvisionedRef<E>;
|
|
237
|
+
/**
|
|
238
|
+
* The service call with `deps`/`secrets` spelled out. `ProvisionArgs` above
|
|
239
|
+
* cannot resolve while `D`/`S` are still unbound type parameters — a generic
|
|
240
|
+
* wrapper like `cron()` provisioning a caller-supplied service — so that call
|
|
241
|
+
* site resolves to this concrete overload instead.
|
|
242
|
+
*/
|
|
243
|
+
provision<D extends Deps, E extends Expose, S extends Secrets>(service: ServiceNode<D, any, E, S>, opts: {
|
|
244
|
+
id?: string;
|
|
245
|
+
deps: DepBindings<D>;
|
|
246
|
+
secrets?: SecretBindings<S>;
|
|
247
|
+
}): ProvisionedRef<E>;
|
|
248
|
+
/** Registers an owned child module; its id defaults to the node's `name`; `deps`/`secrets` are required iff it declares them. */
|
|
249
|
+
provision<D extends Deps, E extends Expose, S extends Secrets>(child: ModuleNode<D, E, S>, ...args: ProvisionArgs<D, S>): ProvisionedRef<E>;
|
|
250
|
+
/** The child-module call with `deps`/`secrets` spelled out — the same generic-wrapper escape as the service overload above. */
|
|
251
|
+
provision<D extends Deps, E extends Expose, S extends Secrets>(child: ModuleNode<D, E, S>, opts: {
|
|
252
|
+
id?: string;
|
|
253
|
+
deps: DepBindings<D>;
|
|
254
|
+
secrets?: SecretBindings<S>;
|
|
255
|
+
}): ProvisionedRef<E>;
|
|
256
|
+
}
|
|
257
|
+
/** Dependency map: name → the slot the service declares. Only declarations are admitted, never a concrete ResourceNode. */
|
|
258
|
+
type Deps = Record<string, DependencyEnd<any, any>>;
|
|
259
|
+
/** Output-port map: name → the Contract a service exposes for others to depend on. */
|
|
260
|
+
type Expose = Readonly<Record<string, AnyContract>>;
|
|
261
|
+
type Hydrated<N> = N extends DependencyEnd<infer C, any> ? C : never;
|
|
262
|
+
type HydratedDeps<D extends Deps> = { readonly [K in keyof D]: Hydrated<D[K]> };
|
|
263
|
+
/**
|
|
264
|
+
* Seals a node instance after its constructor has assigned all fields — the
|
|
265
|
+
* last statement of a concrete node class's constructor. A free function, not
|
|
266
|
+
* a base-class method, so an instance stays structurally a plain frozen node.
|
|
267
|
+
*/
|
|
268
|
+
declare function freezeNode<T extends object>(node: T): T;
|
|
269
|
+
/**
|
|
270
|
+
* Everything `resource()` establishes, minus the freeze — an extension
|
|
271
|
+
* whose resource node carries extra fields extends this, assigns them, and
|
|
272
|
+
* calls `freezeNode(this)` as its constructor's last statement.
|
|
273
|
+
*/
|
|
274
|
+
declare abstract class ResourceNodeBase<C extends AnyContract = AnyContract> implements ResourceNode<C> {
|
|
275
|
+
readonly [NODE]: true;
|
|
276
|
+
readonly kind: "resource";
|
|
277
|
+
readonly name: string;
|
|
278
|
+
readonly extension: string;
|
|
279
|
+
readonly type: C['kind'];
|
|
280
|
+
readonly provides: C;
|
|
281
|
+
constructor(def: {
|
|
282
|
+
name: string;
|
|
283
|
+
extension: string;
|
|
284
|
+
provides: C;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Constructs a branded, frozen Resource node — an identity plus the Contract
|
|
289
|
+
* it provides; the routing `type` is the contract's `kind`. Pure — nothing
|
|
290
|
+
* is provisioned until a module provisions it.
|
|
291
|
+
*/
|
|
292
|
+
declare function resource<C extends AnyContract>(def: {
|
|
293
|
+
name: string;
|
|
294
|
+
extension: string;
|
|
295
|
+
provides: C;
|
|
296
|
+
}): ResourceNode<C>;
|
|
297
|
+
/**
|
|
298
|
+
* Constructs a branded, frozen Service node — declarations only (inputs,
|
|
299
|
+
* params, build adapter, and the ports it exposes). Pure; carries no runtime behavior.
|
|
300
|
+
*/
|
|
301
|
+
declare function service<D extends Deps, P extends Params, E extends Expose = Record<never, never>, S extends Secrets = Record<never, never>>(def: {
|
|
302
|
+
name: string;
|
|
303
|
+
extension: string;
|
|
304
|
+
type: string;
|
|
305
|
+
inputs: D;
|
|
306
|
+
params: P;
|
|
307
|
+
secrets?: S;
|
|
308
|
+
build: BuildAdapter;
|
|
309
|
+
expose?: E;
|
|
310
|
+
}): ServiceNode<D, P, E, S>;
|
|
311
|
+
/**
|
|
312
|
+
* Constructs a branded, frozen DependencyEnd. `required` (if given) is the
|
|
313
|
+
* contract Load compares a wired ref against via `satisfies()`; an unnamed
|
|
314
|
+
* end's diagnostic `name` falls back to its `type`.
|
|
315
|
+
*/
|
|
316
|
+
declare function dependency<P extends Params, C, Req = unknown>(def: {
|
|
317
|
+
name?: string;
|
|
318
|
+
type: string;
|
|
319
|
+
connection: Connection<P, C>;
|
|
320
|
+
required?: Req;
|
|
321
|
+
}): DependencyEnd<C, Req>;
|
|
322
|
+
/**
|
|
323
|
+
* A closed root: no `deps`, no `expose`, nothing wiring in or out. The body
|
|
324
|
+
* only provisions and needs no return. Omitting the boundary argument IS the
|
|
325
|
+
* closed-root shape — `module(name, body)` instead of `module(name, {}, () =>
|
|
326
|
+
* ({}))`.
|
|
327
|
+
*/
|
|
328
|
+
declare function module(name: string, body: (ctx: ModuleContext<Record<never, never>, Record<never, never>>) => void): ModuleNode<Record<never, never>, Record<never, never>, Record<never, never>>;
|
|
329
|
+
/**
|
|
330
|
+
* A module with a boundary: `deps` and/or `expose` declare what wires in and
|
|
331
|
+
* out, the same way a service does. The body returns one port per `expose` key.
|
|
332
|
+
*/
|
|
333
|
+
declare function module<D extends Deps = Record<never, never>, E extends Expose = Record<never, never>, S extends Secrets = Record<never, never>>(name: string, boundary: {
|
|
334
|
+
deps?: D;
|
|
335
|
+
secrets?: S;
|
|
336
|
+
expose?: E;
|
|
337
|
+
}, body: (ctx: ModuleContext<D, S>) => ModuleOutputs<E> | void): ModuleNode<D, E, S>;
|
|
338
|
+
/**
|
|
339
|
+
* True if `value` was constructed by this module's factories. Checks the
|
|
340
|
+
* brand only, never a prototype — a graph may mix nodes from a different
|
|
341
|
+
* installed copy of core (dual-package hazard).
|
|
342
|
+
*/
|
|
343
|
+
declare function isNode(value: unknown): value is ServiceNode | ResourceNode | DependencyEnd | ModuleNode; //#endregion
|
|
344
|
+
//#region src/graph-types.d.ts
|
|
345
|
+
/** Path-derived: root-scope children are bare ids ("auth", "db"); a nested module's own children dot-join under its address ("auth.db"). */
|
|
346
|
+
type NodeId = string;
|
|
347
|
+
interface GraphNode {
|
|
348
|
+
readonly id: NodeId;
|
|
349
|
+
readonly node: ServiceNode | ResourceNode | DependencyEnd | ModuleNode;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* `input`: a service consumes its own declared dependency slot — from the
|
|
353
|
+
* slot node to the service. `dependency`: a service consumes a provisioned
|
|
354
|
+
* producer (a service or a resource — the one wiring mechanism) — from the
|
|
355
|
+
* producer to the consumer, labeled with the consumer's input name (from the
|
|
356
|
+
* module wiring).
|
|
357
|
+
*/
|
|
358
|
+
interface Edge {
|
|
359
|
+
readonly from: NodeId;
|
|
360
|
+
readonly to: NodeId;
|
|
361
|
+
readonly input: string;
|
|
362
|
+
readonly kind: 'input' | 'dependency';
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* A resolved secret binding: the root bound a service's secret slot to an
|
|
366
|
+
* opaque, target-defined source, and the wiring forwarded it to that service's
|
|
367
|
+
* address (ADR-0029). Core never inspects the source; the deploy target reads
|
|
368
|
+
* its own payload. A target's serializer keys the pointer row off this; the
|
|
369
|
+
* preflight manifest aggregates the sources.
|
|
370
|
+
*/
|
|
371
|
+
interface SecretBinding {
|
|
372
|
+
/** The graph address of the service that declares the secret slot. */
|
|
373
|
+
readonly serviceAddress: NodeId;
|
|
374
|
+
/** The secret slot key on that service. */
|
|
375
|
+
readonly slot: string;
|
|
376
|
+
/** The opaque source the root bound the slot to. Core never inspects it; the deploy target reads back its own payload. */
|
|
377
|
+
readonly source: SecretSource;
|
|
378
|
+
}
|
|
379
|
+
interface Graph {
|
|
380
|
+
readonly root: GraphNode;
|
|
381
|
+
/** Root + one per input, topo-ordered (deps first). */
|
|
382
|
+
readonly nodes: readonly GraphNode[];
|
|
383
|
+
readonly edges: readonly Edge[];
|
|
384
|
+
/** Every service secret slot resolved to its root-bound opaque source. */
|
|
385
|
+
readonly secrets: readonly SecretBinding[];
|
|
386
|
+
}
|
|
387
|
+
/** Thrown by Load when the graph is malformed. */
|
|
388
|
+
declare class LoadError extends Error {
|
|
389
|
+
constructor(message: string);
|
|
390
|
+
} //#endregion
|
|
391
|
+
//#region src/config.d.ts
|
|
392
|
+
/**
|
|
393
|
+
* A declared config param — pure data: a caller-owned Standard Schema
|
|
394
|
+
* (ADR-0018) plus a few framework facets. The framework carries the schema,
|
|
395
|
+
* infers the value type from it, and validates with it, without ever
|
|
396
|
+
* enumerating permitted shapes. Turning a value into stored config and back is
|
|
397
|
+
* the deploy target's job, not the param's (ADR-0019) — the same split RPC
|
|
398
|
+
* uses: schema on the declaration, wire owned by the mover.
|
|
399
|
+
*/
|
|
400
|
+
interface ConfigParam<S extends StandardSchemaV1 = StandardSchemaV1> {
|
|
401
|
+
readonly schema: S;
|
|
402
|
+
readonly optional?: boolean;
|
|
403
|
+
readonly default?: StandardSchemaV1.InferOutput<S>;
|
|
404
|
+
}
|
|
405
|
+
type Params = Record<string, ConfigParam>;
|
|
406
|
+
/** What implementations receive — undefined only for optional params with no default. */
|
|
407
|
+
type Values<P extends Params> = { readonly [K in keyof P]: P[K]['optional'] extends true ? undefined extends P[K]['default'] ? StandardSchemaV1.InferOutput<P[K]['schema']> | undefined : StandardSchemaV1.InferOutput<P[K]['schema']> : StandardSchemaV1.InferOutput<P[K]['schema']> };
|
|
408
|
+
/**
|
|
409
|
+
* The connection face of a dependency: declared params (data) and how
|
|
410
|
+
* validated values become a client (the hydrate behavior slot). Both P and C
|
|
411
|
+
* are INFERRED — the declaration types hydrate's input; the factory types the
|
|
412
|
+
* loaded dep.
|
|
413
|
+
*/
|
|
414
|
+
interface Connection<P extends Params = Params, C = unknown> {
|
|
415
|
+
readonly params: P;
|
|
416
|
+
hydrate(values: Values<P>): C | Promise<C>;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* The enumerable config surface of a service — derivable from the graph
|
|
420
|
+
* alone, nothing booted, no platform keys. The introspection artifact (values
|
|
421
|
+
* absent). `schema` is a data-only projection of the param's Standard Schema
|
|
422
|
+
* (JSON Schema when the vendor supports the optional conversion, a `{ vendor }`
|
|
423
|
+
* tag otherwise) — never the param's functions. Physical locations are the
|
|
424
|
+
* target pack's business. Secrets are not here — they live on their own slot.
|
|
425
|
+
*/
|
|
426
|
+
interface ConfigDeclaration {
|
|
427
|
+
readonly owner: 'service' | {
|
|
428
|
+
readonly input: string;
|
|
429
|
+
};
|
|
430
|
+
readonly name: string;
|
|
431
|
+
readonly schema: Readonly<Record<string, unknown>>;
|
|
432
|
+
readonly optional: boolean;
|
|
433
|
+
readonly default: unknown;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The resolved, typed configuration of one service — what crosses the
|
|
437
|
+
* core→pack boundary. Core builds it at deploy (leaf values are provisioning
|
|
438
|
+
* refs, so the env writes depend on the resources/producer — the ordering
|
|
439
|
+
* edges); the pack serializes it, and at boot reconstructs the identical
|
|
440
|
+
* structure with concrete values. Both forms conform to the shape from
|
|
441
|
+
* configOf. Core never stringifies.
|
|
442
|
+
*/
|
|
443
|
+
interface Config {
|
|
444
|
+
readonly service: Readonly<Record<string, unknown>>;
|
|
445
|
+
readonly inputs: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Enumerates every config param the service declares: each input's connection
|
|
449
|
+
* params, then the service's own params. Pure — reads `root.inputs`/`params`
|
|
450
|
+
* directly, executes nothing but the (also pure) schema projection. Deliberately
|
|
451
|
+
* does not go through `Load`: a service's connection-end inputs are legitimately
|
|
452
|
+
* unwired from its own point of view (wiring is an enclosing module's concern),
|
|
453
|
+
* and this introspects one service's declared shape regardless of how — or
|
|
454
|
+
* whether — it composes into a larger graph.
|
|
455
|
+
*/
|
|
456
|
+
declare function configOf(root: ServiceNode): readonly ConfigDeclaration[];
|
|
457
|
+
/**
|
|
458
|
+
* The app's provision manifest: every secret binding the root resolved across
|
|
459
|
+
* the graph (ADR-0029) — an opaque, target-defined source per service secret
|
|
460
|
+
* slot; a deploy target's preflight reads its own payload. Pure graph
|
|
461
|
+
* introspection, TARGET-AGNOSTIC — the target consumes it to verify each secret
|
|
462
|
+
* exists on the platform before deploy. The values are provisioned out-of-band.
|
|
463
|
+
*/
|
|
464
|
+
declare function provisionManifest(graph: Graph): readonly SecretBinding[];
|
|
465
|
+
interface ParamOptions<T> {
|
|
466
|
+
readonly optional?: boolean;
|
|
467
|
+
readonly default?: T;
|
|
468
|
+
}
|
|
469
|
+
/** A string-valued param. */
|
|
470
|
+
declare function string(opts?: ParamOptions<string>): ConfigParam<StandardSchemaV1<string, string>>;
|
|
471
|
+
/** A number-valued param. */
|
|
472
|
+
declare function number(opts?: ParamOptions<number>): ConfigParam<StandardSchemaV1<number, number>>;
|
|
473
|
+
/** A param over any caller-supplied Standard Schema — a structured `jobs`, say. */
|
|
474
|
+
declare function param<S extends StandardSchemaV1>(schema: S, opts?: ParamOptions<StandardSchemaV1.InferOutput<S>>): ConfigParam<S>; //#endregion
|
|
475
|
+
//#endregion
|
|
476
|
+
export { SecretNeed as A, isSecretSource as B, ProvisionedRef as C, RunnableServiceNode as D, ResourceNodeBase as E, Values as F, resource as G, number as H, configOf as I, service as J, secret as K, dependency as L, SecretValues as M, Secrets as N, SecretBinding as O, ServiceNode as P, freezeNode as R, Params as S, ResourceNode as T, param as U, module as V, provisionManifest as W, SecretBox as X, string as Y, SecretString as Z, ModuleBuilder as _, Connection as a, ModuleOutputs as b, Deps as c, Graph as d, GraphNode as f, LoadError as g, InputRef as h, ConfigParam as i, SecretSource as j, SecretBindings as k, Edge as l, HydratedDeps as m, Config as n, Contract as o, Hydrated as p, secretSource as q, ConfigDeclaration as r, DependencyEnd as s, BuildAdapter as t, Expose as u, ModuleContext as v, RefPort as w, NodeId as x, ModuleNode as y, isNode as z };
|
|
477
|
+
//# sourceMappingURL=config-ob5OhCSP-sP3GW3uu.d.mts.map
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region ../../0-framework/1-core/core/dist/config.mjs
|
|
2
|
+
/** Typed identity — exists so `prisma-composer.config.ts` gets checked against PrismaAppConfig where it is written. */
|
|
3
|
+
function defineConfig(config) {
|
|
4
|
+
return config;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { defineConfig };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../../0-framework/1-core/core/dist/config.mjs"],"sourcesContent":["//#region src/app-config.ts\n/** Typed identity — exists so `prisma-composer.config.ts` gets checked against PrismaAppConfig where it is written. */\nfunction defineConfig(config) {\n\treturn config;\n}\n//#endregion\nexport { defineConfig };\n\n//# sourceMappingURL=config.mjs.map"],"mappings":";;AAEA,SAAS,aAAa,QAAQ;CAC7B,OAAO;AACR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { S as resolveStateLayer, _ as buildConfig, a as Bundle, b as lowering, c as LowerError, d as Lowering, g as ServiceLowering, i as AssembleInput, l as LowerOptions, n as ApplicationDescriptor, p as PackageInput, r as Artifact, s as LowerContext, t as AlchemyStateLayer, u as LoweredNode, x as mergedProviders, y as lower } from "./app-config-BUqyK6N6-CVq3uvHF.mjs";
|
|
2
|
+
export { AlchemyStateLayer, ApplicationDescriptor, Artifact, AssembleInput, Bundle, LowerContext, LowerError, LowerOptions, LoweredNode, Lowering, PackageInput, ServiceLowering, buildConfig, lower, lowering, mergedProviders, resolveStateLayer };
|
package/dist/deploy.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { t as Load } from "./graph-BYdCQKya-BI0njTow.mjs";
|
|
2
|
+
import * as Alchemy from "alchemy";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
//#region ../../0-framework/1-core/core/dist/deploy.mjs
|
|
6
|
+
var LowerError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "LowerError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/** Assembles a service's typed Config from its dependency edges' lowered outputs plus its own param defaults. */
|
|
13
|
+
function buildConfig(node, id, graph, lowered) {
|
|
14
|
+
const inputs = {};
|
|
15
|
+
for (const [inputName, inputNode] of Object.entries(node.inputs)) {
|
|
16
|
+
const edge = graph.edges.find((e) => e.to === id && e.input === inputName && e.kind === "dependency");
|
|
17
|
+
const producedOutputs = edge !== void 0 ? lowered.get(edge.from)?.outputs ?? {} : {};
|
|
18
|
+
const values = {};
|
|
19
|
+
for (const name of Object.keys(inputNode.connection.params)) values[name] = producedOutputs[name];
|
|
20
|
+
inputs[inputName] = values;
|
|
21
|
+
}
|
|
22
|
+
const service = {};
|
|
23
|
+
for (const [name, param] of Object.entries(node.params)) if (param.default !== void 0) service[name] = param.default;
|
|
24
|
+
return {
|
|
25
|
+
service,
|
|
26
|
+
inputs
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function missingBundleError(id) {
|
|
30
|
+
return new LowerError(`No bundle provided for service "${id}" (opts.bundles["${id}"] is required).`);
|
|
31
|
+
}
|
|
32
|
+
function duplicateExtensionError(id) {
|
|
33
|
+
return new LowerError(`Extension "${id}" is listed more than once in \`extensions\` — each extension id must be unique.`);
|
|
34
|
+
}
|
|
35
|
+
/** Registries as extension id → descriptor. Fails on a duplicate id — the CLI validates config, but lowering() is the programmatic escape hatch that doesn't. */
|
|
36
|
+
function extensionsById(config) {
|
|
37
|
+
const map = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const extension of config.extensions) {
|
|
39
|
+
if (map.has(extension.id)) return Effect.fail(duplicateExtensionError(extension.id));
|
|
40
|
+
map.set(extension.id, extension);
|
|
41
|
+
}
|
|
42
|
+
return Effect.succeed(map);
|
|
43
|
+
}
|
|
44
|
+
function unknownExtensionError(extension, id) {
|
|
45
|
+
return new LowerError(`No extension "${extension}" is configured (needed by node "${id}") — add it to prisma-composer.config.ts's \`extensions\` (import its /control entry and list its descriptor).`);
|
|
46
|
+
}
|
|
47
|
+
function unknownNodeTypeError(extension, type) {
|
|
48
|
+
return new LowerError(`Extension "${extension.id}" has no descriptor for node type "${type}" (known: ${Object.keys(extension.nodes).join(", ")}).`);
|
|
49
|
+
}
|
|
50
|
+
function wrongKindError(extension, type, expected, got) {
|
|
51
|
+
return new LowerError(`Extension "${extension}"'s descriptor for node type "${type}" is a "${got}" descriptor — this node needs a "${expected}" descriptor.`);
|
|
52
|
+
}
|
|
53
|
+
/** Looks up one node's descriptor: extension by `node.extension`, then descriptor by `node.type`, then the kind check. */
|
|
54
|
+
function descriptorFor(extensions, node, id) {
|
|
55
|
+
const extension = extensions.get(node.extension);
|
|
56
|
+
if (extension === void 0) return Effect.fail(unknownExtensionError(node.extension, id));
|
|
57
|
+
const descriptor = extension.nodes[node.type];
|
|
58
|
+
if (descriptor === void 0) return Effect.fail(unknownNodeTypeError(extension, node.type));
|
|
59
|
+
if (descriptor.kind !== node.kind) return Effect.fail(wrongKindError(node.extension, node.type, node.kind, descriptor.kind));
|
|
60
|
+
return Effect.succeed(descriptor);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The state-layer precedence a deploy resolves to: an explicit opts.state
|
|
64
|
+
* always wins; failing that, the config's own (required) state. A pure
|
|
65
|
+
* function so the precedence is testable without booting Alchemy.
|
|
66
|
+
*/
|
|
67
|
+
function resolveStateLayer(opts, config) {
|
|
68
|
+
return opts.state ?? config.state();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* All configured extensions' providers merged, config array order — an
|
|
72
|
+
* extension without `providers` is skipped; no used-extensions-only
|
|
73
|
+
* filtering (ADR-0017's pinned providers rule).
|
|
74
|
+
*/
|
|
75
|
+
function mergedProviders(config) {
|
|
76
|
+
const [first, ...rest] = config.extensions.flatMap((extension) => extension.providers !== void 0 ? [extension.providers()] : []);
|
|
77
|
+
return first === void 0 ? Layer.empty : Layer.mergeAll(first, ...rest);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Composable form for mixed stacks: hand-wired Alchemy resources alongside Prisma App nodes in one stack effect.
|
|
81
|
+
* Fails with LowerError or whatever an extension's lowering raises — the error type is open.
|
|
82
|
+
*/
|
|
83
|
+
function lowering(root, config, opts) {
|
|
84
|
+
return Effect.gen(function* () {
|
|
85
|
+
const graph = Load(root, { id: opts.name });
|
|
86
|
+
const extensions = yield* extensionsById(config);
|
|
87
|
+
const lowered = /* @__PURE__ */ new Map();
|
|
88
|
+
const noApplication = { outputs: {} };
|
|
89
|
+
const applications = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const descriptor of config.extensions) {
|
|
91
|
+
if (descriptor.application === void 0) continue;
|
|
92
|
+
const appCtx = {
|
|
93
|
+
id: graph.root.id,
|
|
94
|
+
address: "",
|
|
95
|
+
node: graph.root.node,
|
|
96
|
+
graph,
|
|
97
|
+
opts,
|
|
98
|
+
application: noApplication,
|
|
99
|
+
lowered
|
|
100
|
+
};
|
|
101
|
+
applications.set(descriptor.id, yield* descriptor.application.provision(appCtx));
|
|
102
|
+
}
|
|
103
|
+
for (const { id, node } of graph.nodes) {
|
|
104
|
+
if (node.kind === "module") continue;
|
|
105
|
+
if (node.kind === "dependency") continue;
|
|
106
|
+
const ctx = {
|
|
107
|
+
id,
|
|
108
|
+
address: id,
|
|
109
|
+
node,
|
|
110
|
+
graph,
|
|
111
|
+
opts,
|
|
112
|
+
application: applications.get(node.extension) ?? noApplication,
|
|
113
|
+
lowered
|
|
114
|
+
};
|
|
115
|
+
const descriptor = yield* descriptorFor(extensions, node, id);
|
|
116
|
+
if (descriptor.kind === "resource") {
|
|
117
|
+
lowered.set(id, yield* descriptor(ctx));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (descriptor.kind !== "service") return yield* Effect.fail(wrongKindError(node.extension, node.type, node.kind, descriptor.kind));
|
|
121
|
+
const service = node;
|
|
122
|
+
const provisioned = yield* descriptor.provision(ctx);
|
|
123
|
+
const typedConfig = buildConfig(service, id, graph, lowered);
|
|
124
|
+
const serialized = yield* descriptor.serialize(ctx, provisioned, typedConfig);
|
|
125
|
+
const bundle = opts.bundles[id];
|
|
126
|
+
if (bundle === void 0) return yield* Effect.fail(missingBundleError(id));
|
|
127
|
+
const artifact = yield* descriptor.package(ctx, {
|
|
128
|
+
assembled: {
|
|
129
|
+
dir: bundle.dir,
|
|
130
|
+
entry: bundle.entry
|
|
131
|
+
},
|
|
132
|
+
address: id
|
|
133
|
+
});
|
|
134
|
+
lowered.set(id, yield* descriptor.deploy(ctx, provisioned, artifact, serialized));
|
|
135
|
+
}
|
|
136
|
+
return { outputs: {} };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The whole-stack wrapper: Load → route each node through the config's
|
|
141
|
+
* extension registries → an Alchemy Stack (the default export the alchemy
|
|
142
|
+
* CLI consumes).
|
|
143
|
+
*/
|
|
144
|
+
function lower(root, config, opts) {
|
|
145
|
+
const stackEffect = Effect.orDie(lowering(root, config, opts));
|
|
146
|
+
return Alchemy.Stack(opts.name, {
|
|
147
|
+
providers: mergedProviders(config),
|
|
148
|
+
state: resolveStateLayer(opts, config)
|
|
149
|
+
}, stackEffect);
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { LowerError, buildConfig, lower, lowering, mergedProviders, resolveStateLayer };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=deploy.mjs.map
|