@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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/dist/app-config-CpWN1ZfP-CZ1c6Eqk.d.mts +325 -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 +1229 -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-D-h0FACe.d.mts +1 -0
  13. package/dist/config-ad92ubCB-D0-dZUgA.d.mts +568 -0
  14. package/dist/config.d.mts +3 -0
  15. package/dist/config.mjs +9 -0
  16. package/dist/config.mjs.map +1 -0
  17. package/dist/deploy-D-h0FACe.d.mts +1 -0
  18. package/dist/deploy.d.mts +3 -0
  19. package/dist/deploy.mjs +305 -0
  20. package/dist/deploy.mjs.map +1 -0
  21. package/dist/dist-B0axxnBf.mjs +193 -0
  22. package/dist/dist-B0axxnBf.mjs.map +1 -0
  23. package/dist/graph-BmrUEdo9-6Oq1hSmS.mjs +688 -0
  24. package/dist/graph-BmrUEdo9-6Oq1hSmS.mjs.map +1 -0
  25. package/dist/graph-DXuL5tN6-BeAHOb0u.d.mts +18 -0
  26. package/dist/index-C1f1Aot7.d.mts +16 -0
  27. package/dist/index-Cy4C23u1.d.mts +32 -0
  28. package/dist/index.d.mts +4 -0
  29. package/dist/index.mjs +3 -0
  30. package/dist/nextjs-control.d.mts +14 -0
  31. package/dist/nextjs-control.mjs +105 -0
  32. package/dist/nextjs-control.mjs.map +1 -0
  33. package/dist/nextjs.d.mts +2 -0
  34. package/dist/nextjs.mjs +12 -0
  35. package/dist/nextjs.mjs.map +1 -0
  36. package/dist/node-control.d.mts +11 -0
  37. package/dist/node-control.mjs +142 -0
  38. package/dist/node-control.mjs.map +1 -0
  39. package/dist/node.d.mts +23 -0
  40. package/dist/node.mjs +12 -0
  41. package/dist/node.mjs.map +1 -0
  42. package/dist/report.d.mts +17 -0
  43. package/dist/report.mjs +79 -0
  44. package/dist/report.mjs.map +1 -0
  45. package/dist/rpc.d.mts +53 -0
  46. package/dist/rpc.mjs +184 -0
  47. package/dist/rpc.mjs.map +1 -0
  48. package/dist/testing.d.mts +23 -0
  49. package/dist/testing.mjs +45 -0
  50. package/dist/testing.mjs.map +1 -0
  51. package/package.json +69 -0
@@ -0,0 +1,79 @@
1
+ //#region ../../0-framework/3-tooling/cli/dist/render-deployment.mjs
2
+ /** Gap between the deepest tree label and the entity column. */
3
+ const LABEL_GAP = 3;
4
+ const emptyNode = (segment) => ({
5
+ segment,
6
+ children: /* @__PURE__ */ new Map()
7
+ });
8
+ /** Builds the address tree, splitting each dot-address into its segments. */
9
+ function buildTree(nodes) {
10
+ const root = emptyNode("");
11
+ for (const deployed of nodes) {
12
+ let node = root;
13
+ for (const segment of deployed.address.split(".")) {
14
+ let child = node.children.get(segment);
15
+ if (child === void 0) {
16
+ child = emptyNode(segment);
17
+ node.children.set(segment, child);
18
+ }
19
+ node = child;
20
+ }
21
+ node.deployed = deployed;
22
+ }
23
+ return root;
24
+ }
25
+ /** Flattens the tree to rows in address order, drawing the box guides. */
26
+ function toRows(node, guides, rows) {
27
+ const children = Array.from(node.children.values());
28
+ children.forEach((child, index) => {
29
+ const isLast = index === children.length - 1;
30
+ rows.push({
31
+ label: `${guides}${isLast ? "└─ " : "├─ "}${child.segment}`,
32
+ continuation: `${guides}${isLast ? " " : "│ "}`,
33
+ deployed: child.deployed
34
+ });
35
+ toRows(child, `${guides}${isLast ? " " : "│ "}`, rows);
36
+ });
37
+ }
38
+ /** `kind id` — the one line an entity gets. */
39
+ const entityLine = (entity) => `${entity.kind} ${entity.id}`;
40
+ /** Pads `prefix` out to `width`, so every entity starts in the same column. */
41
+ const pad = (prefix, width) => prefix.padEnd(width, " ");
42
+ /**
43
+ * Renders a deploy's result as the app's own topology. Pure — returns the
44
+ * string; the caller prints.
45
+ */
46
+ function renderDeployment(result) {
47
+ const rows = [];
48
+ toRows(buildTree(result.nodes), "", rows);
49
+ const column = Math.max(0, ...rows.map((row) => row.label.length)) + LABEL_GAP;
50
+ const lines = [result.app];
51
+ for (const row of rows) {
52
+ if (row.deployed === void 0) {
53
+ lines.push(row.label);
54
+ continue;
55
+ }
56
+ if (row.deployed.entities.length === 0) {
57
+ lines.push(`${pad(row.label, column)}(no entities reported)`);
58
+ continue;
59
+ }
60
+ row.deployed.entities.forEach((entity, index) => {
61
+ const prefix = index === 0 ? row.label : row.continuation;
62
+ lines.push(`${pad(prefix, column)}${entityLine(entity)}`);
63
+ if (entity.url !== void 0) lines.push(`${pad(row.continuation, column)}${entity.url}`);
64
+ });
65
+ }
66
+ return lines.join("\n");
67
+ }
68
+ /**
69
+ * The report hook the generated stack file wires into `LowerOptions`. Prints a
70
+ * leading blank line so the summary separates from alchemy's own apply output.
71
+ */
72
+ function deploymentReport(result) {
73
+ console.log("");
74
+ console.log(renderDeployment(result));
75
+ }
76
+ //#endregion
77
+ export { deploymentReport, renderDeployment };
78
+
79
+ //# sourceMappingURL=report.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.mjs","names":[],"sources":["../../../0-framework/3-tooling/cli/dist/render-deployment.mjs"],"sourcesContent":["//#region src/render-deployment.ts\n/** Gap between the deepest tree label and the entity column. */\nconst LABEL_GAP = 3;\nconst emptyNode = (segment) => ({\n\tsegment,\n\tchildren: /* @__PURE__ */ new Map()\n});\n/** Builds the address tree, splitting each dot-address into its segments. */\nfunction buildTree(nodes) {\n\tconst root = emptyNode(\"\");\n\tfor (const deployed of nodes) {\n\t\tlet node = root;\n\t\tfor (const segment of deployed.address.split(\".\")) {\n\t\t\tlet child = node.children.get(segment);\n\t\t\tif (child === void 0) {\n\t\t\t\tchild = emptyNode(segment);\n\t\t\t\tnode.children.set(segment, child);\n\t\t\t}\n\t\t\tnode = child;\n\t\t}\n\t\tnode.deployed = deployed;\n\t}\n\treturn root;\n}\n/** Flattens the tree to rows in address order, drawing the box guides. */\nfunction toRows(node, guides, rows) {\n\tconst children = Array.from(node.children.values());\n\tchildren.forEach((child, index) => {\n\t\tconst isLast = index === children.length - 1;\n\t\trows.push({\n\t\t\tlabel: `${guides}${isLast ? \"└─ \" : \"├─ \"}${child.segment}`,\n\t\t\tcontinuation: `${guides}${isLast ? \" \" : \"│ \"}`,\n\t\t\tdeployed: child.deployed\n\t\t});\n\t\ttoRows(child, `${guides}${isLast ? \" \" : \"│ \"}`, rows);\n\t});\n}\n/** `kind id` — the one line an entity gets. */\nconst entityLine = (entity) => `${entity.kind} ${entity.id}`;\n/** Pads `prefix` out to `width`, so every entity starts in the same column. */\nconst pad = (prefix, width) => prefix.padEnd(width, \" \");\n/**\n* Renders a deploy's result as the app's own topology. Pure — returns the\n* string; the caller prints.\n*/\nfunction renderDeployment(result) {\n\tconst rows = [];\n\ttoRows(buildTree(result.nodes), \"\", rows);\n\tconst column = Math.max(0, ...rows.map((row) => row.label.length)) + LABEL_GAP;\n\tconst lines = [result.app];\n\tfor (const row of rows) {\n\t\tif (row.deployed === void 0) {\n\t\t\tlines.push(row.label);\n\t\t\tcontinue;\n\t\t}\n\t\tif (row.deployed.entities.length === 0) {\n\t\t\tlines.push(`${pad(row.label, column)}(no entities reported)`);\n\t\t\tcontinue;\n\t\t}\n\t\trow.deployed.entities.forEach((entity, index) => {\n\t\t\tconst prefix = index === 0 ? row.label : row.continuation;\n\t\t\tlines.push(`${pad(prefix, column)}${entityLine(entity)}`);\n\t\t\tif (entity.url !== void 0) lines.push(`${pad(row.continuation, column)}${entity.url}`);\n\t\t});\n\t}\n\treturn lines.join(\"\\n\");\n}\n/**\n* The report hook the generated stack file wires into `LowerOptions`. Prints a\n* leading blank line so the summary separates from alchemy's own apply output.\n*/\nfunction deploymentReport(result) {\n\tconsole.log(\"\");\n\tconsole.log(renderDeployment(result));\n}\n//#endregion\nexport { deploymentReport, renderDeployment };\n\n//# sourceMappingURL=render-deployment.mjs.map"],"mappings":";;AAEA,MAAM,YAAY;AAClB,MAAM,aAAa,aAAa;CAC/B;CACA,0BAA0B,IAAI,IAAI;AACnC;;AAEA,SAAS,UAAU,OAAO;CACzB,MAAM,OAAO,UAAU,EAAE;CACzB,KAAK,MAAM,YAAY,OAAO;EAC7B,IAAI,OAAO;EACX,KAAK,MAAM,WAAW,SAAS,QAAQ,MAAM,GAAG,GAAG;GAClD,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO;GACrC,IAAI,UAAU,KAAK,GAAG;IACrB,QAAQ,UAAU,OAAO;IACzB,KAAK,SAAS,IAAI,SAAS,KAAK;GACjC;GACA,OAAO;EACR;EACA,KAAK,WAAW;CACjB;CACA,OAAO;AACR;;AAEA,SAAS,OAAO,MAAM,QAAQ,MAAM;CACnC,MAAM,WAAW,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;CAClD,SAAS,SAAS,OAAO,UAAU;EAClC,MAAM,SAAS,UAAU,SAAS,SAAS;EAC3C,KAAK,KAAK;GACT,OAAO,GAAG,SAAS,SAAS,QAAQ,QAAQ,MAAM;GAClD,cAAc,GAAG,SAAS,SAAS,QAAQ;GAC3C,UAAU,MAAM;EACjB,CAAC;EACD,OAAO,OAAO,GAAG,SAAS,SAAS,QAAQ,SAAS,IAAI;CACzD,CAAC;AACF;;AAEA,MAAM,cAAc,WAAW,GAAG,OAAO,KAAK,GAAG,OAAO;;AAExD,MAAM,OAAO,QAAQ,UAAU,OAAO,OAAO,OAAO,GAAG;;;;;AAKvD,SAAS,iBAAiB,QAAQ;CACjC,MAAM,OAAO,CAAC;CACd,OAAO,UAAU,OAAO,KAAK,GAAG,IAAI,IAAI;CACxC,MAAM,SAAS,KAAK,IAAI,GAAG,GAAG,KAAK,KAAK,QAAQ,IAAI,MAAM,MAAM,CAAC,IAAI;CACrE,MAAM,QAAQ,CAAC,OAAO,GAAG;CACzB,KAAK,MAAM,OAAO,MAAM;EACvB,IAAI,IAAI,aAAa,KAAK,GAAG;GAC5B,MAAM,KAAK,IAAI,KAAK;GACpB;EACD;EACA,IAAI,IAAI,SAAS,SAAS,WAAW,GAAG;GACvC,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,MAAM,EAAE,uBAAuB;GAC5D;EACD;EACA,IAAI,SAAS,SAAS,SAAS,QAAQ,UAAU;GAChD,MAAM,SAAS,UAAU,IAAI,IAAI,QAAQ,IAAI;GAC7C,MAAM,KAAK,GAAG,IAAI,QAAQ,MAAM,IAAI,WAAW,MAAM,GAAG;GACxD,IAAI,OAAO,QAAQ,KAAK,GAAG,MAAM,KAAK,GAAG,IAAI,IAAI,cAAc,MAAM,IAAI,OAAO,KAAK;EACtF,CAAC;CACF;CACA,OAAO,MAAM,KAAK,IAAI;AACvB;;;;;AAKA,SAAS,iBAAiB,QAAQ;CACjC,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,iBAAiB,MAAM,CAAC;AACrC"}
package/dist/rpc.d.mts ADDED
@@ -0,0 +1,53 @@
1
+ import { P as RunnableServiceNode, k as ProvisionNeed, o as Contract, s as DependencyEnd } from "./config-ad92ubCB-D0-dZUgA.mjs";
2
+ import "./index-Cy4C23u1.mjs";
3
+ import { StandardSchemaV1 } from "@standard-schema/spec";
4
+ //#region ../../0-framework/2-authoring/rpc/dist/index.d.mts
5
+ //#region src/rpc.d.ts
6
+ /** ADR-0031's need brand for RPC's per-binding service key — the target registers a provisioner under this. */
7
+ declare const RPC_PEER_KEY: unique symbol;
8
+ /** The provisioning need `rpc()`'s `serviceKey` param declares (ADR-0030): a shared, unguessable value the target mints per consumer edge. */
9
+ declare const perBindingToken: () => ProvisionNeed;
10
+ /** The concrete function-map bound every RPC Contract's Cmp must fit. */
11
+ type RpcFns = Record<string, (input: any) => Promise<any>>;
12
+ declare function rpc<I extends StandardSchemaV1, O extends StandardSchemaV1>(m: {
13
+ input: I;
14
+ output: O;
15
+ }): (input: StandardSchemaV1.InferInput<I>) => Promise<StandardSchemaV1.InferOutput<O>>;
16
+ declare function rpc<C extends Contract<'rpc', RpcFns>>(contract: C): DependencyEnd<Client<C>, C>;
17
+ /** The typed client a consumer's `rpc(contract)` dependency hydrates to. */
18
+ type Client<C> = C extends Contract<string, infer Cmp> ? Cmp : never;
19
+ //#endregion
20
+ //#region src/client.d.ts
21
+ /**
22
+ * A fetch-shaped transport. Defaults to the real `fetch`; a served handler
23
+ * (`serve()`'s return value) works too — the binding does not have to be a
24
+ * network hop.
25
+ */
26
+ type Transport = (req: Request) => Promise<Response>;
27
+ declare function makeClient<C extends Contract<'rpc', RpcFns>>(contract: C, url: string, opts?: {
28
+ fetch?: Transport;
29
+ serviceKey?: string;
30
+ }): Client<C>;
31
+ //#endregion
32
+ //#region src/contract.d.ts
33
+ declare function contract<Fns extends Record<string, (input: any) => Promise<any>>>(fns: Fns): Contract<'rpc', Fns>;
34
+ //#endregion
35
+ //#region src/serve.d.ts
36
+ /** The reserved env var the target (slice 2) writes the accepted key set to. */
37
+ declare const RPC_ACCEPTED_KEYS_ENV = "COMPOSER_RPC_ACCEPTED_KEYS";
38
+ type AnyRunnable = RunnableServiceNode<any, any, any>;
39
+ type CmpOf<C> = C extends Contract<string, infer Cmp> ? Cmp : never;
40
+ type HandlerFor<Fn, LoadedDeps> = Fn extends ((input: infer I) => Promise<infer O>) ? (input: I, deps: LoadedDeps) => Promise<O> : never;
41
+ /** Every exposed port's methods, turned into a handler map typed off S's own `expose` and `load()`. */
42
+ type Handlers<S extends AnyRunnable> = { [Port in keyof NonNullable<S['expose']>]: { [M in keyof CmpOf<NonNullable<S['expose']>[Port]>]: HandlerFor<CmpOf<NonNullable<S['expose']>[Port]>[M], ReturnType<S['load']>>; }; };
43
+ /**
44
+ * Routes `POST /rpc/<method>`: parses JSON, validates input, calls the
45
+ * handler with `service.load()`'s deps, validates the output, and responds
46
+ * JSON. An unknown method or invalid input is a 4xx; a handler (or output
47
+ * validation) failure is a 5xx — either way the process does not crash.
48
+ * `load()` is called exactly once, here, before the handler ever runs.
49
+ */
50
+ declare function serve<S extends AnyRunnable, H extends Handlers<S>>(service: S, handlers: H): (req: Request) => Promise<Response>;
51
+ //#endregion
52
+ export { type Client, type Handlers, RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, type Transport, contract, makeClient, perBindingToken, rpc, serve };
53
+ //# sourceMappingURL=rpc.d.mts.map
package/dist/rpc.mjs ADDED
@@ -0,0 +1,184 @@
1
+ import { t as blindCast } from "./casts-Ci5rYYaR.mjs";
2
+ import { i as dependency, p as provisionNeed } from "./graph-BmrUEdo9-6Oq1hSmS.mjs";
3
+ import { l as string } from "./dist-B0axxnBf.mjs";
4
+ //#region ../../0-framework/2-authoring/rpc/dist/index.mjs
5
+ async function standardValidate(schema, value) {
6
+ const result = await schema["~standard"].validate(value);
7
+ if (result.issues !== void 0) throw new Error(`Schema validation failed: ${result.issues.map((issue) => issue.message).join("; ")}`);
8
+ return result.value;
9
+ }
10
+ /** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */
11
+ function methodUrl(base, method) {
12
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
13
+ return new URL(`rpc/${method}`, normalizedBase).toString();
14
+ }
15
+ /** The server's `{ error }` body, if the response has one — undefined otherwise. */
16
+ async function errorDetail(res) {
17
+ try {
18
+ const body = await res.json();
19
+ return typeof body === "object" && body !== null && "error" in body ? String(body.error) : void 0;
20
+ } catch {
21
+ return;
22
+ }
23
+ }
24
+ function makeClient(contract, url, opts) {
25
+ const send = opts?.fetch ?? fetch;
26
+ const headers = { "content-type": "application/json" };
27
+ if (opts?.serviceKey !== void 0) headers["Authorization"] = `Bearer ${opts.serviceKey}`;
28
+ const client = {};
29
+ for (const [method, schemas] of Object.entries(blindCast(contract.__cmp))) client[method] = async (input) => {
30
+ const res = await send(new Request(methodUrl(url, method), {
31
+ method: "POST",
32
+ headers,
33
+ body: JSON.stringify(input)
34
+ }));
35
+ if (!res.ok) {
36
+ const detail = await errorDetail(res);
37
+ throw new Error(`RPC call "${method}" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : ""));
38
+ }
39
+ return standardValidate(schemas.output, await res.json());
40
+ };
41
+ return blindCast(client);
42
+ }
43
+ function contract(fns) {
44
+ const value = {
45
+ kind: "rpc",
46
+ __cmp: fns,
47
+ satisfies: (required) => value === required
48
+ };
49
+ return Object.freeze(value);
50
+ }
51
+ /** ADR-0031's need brand for RPC's per-binding service key — the target registers a provisioner under this. */
52
+ const RPC_PEER_KEY = Symbol.for("prisma:rpc/per-binding-key");
53
+ /** The provisioning need `rpc()`'s `serviceKey` param declares (ADR-0030): a shared, unguessable value the target mints per consumer edge. */
54
+ const perBindingToken = () => provisionNeed(RPC_PEER_KEY);
55
+ function rpc(arg) {
56
+ if (!isRpcContract(arg)) return arg;
57
+ return dependency({
58
+ type: "rpc",
59
+ connection: {
60
+ params: {
61
+ url: string(),
62
+ serviceKey: string({
63
+ optional: true,
64
+ provision: perBindingToken()
65
+ })
66
+ },
67
+ hydrate: ({ url, serviceKey }) => makeClient(arg, url, { serviceKey })
68
+ },
69
+ required: arg
70
+ });
71
+ }
72
+ function isRpcContract(value) {
73
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === "rpc" && "__cmp" in value && "satisfies" in value;
74
+ }
75
+ /** The reserved env var the target (slice 2) writes the accepted key set to. */
76
+ const RPC_ACCEPTED_KEYS_ENV = "COMPOSER_RPC_ACCEPTED_KEYS";
77
+ function jsonResponse(body, status = 200) {
78
+ return new Response(JSON.stringify(body), {
79
+ status,
80
+ headers: { "content-type": "application/json" }
81
+ });
82
+ }
83
+ /** The provisioned accepted key set, or undefined when the deploy never provisioned one (local/test — enforcement off). */
84
+ function acceptedKeys() {
85
+ const raw = process.env[RPC_ACCEPTED_KEYS_ENV];
86
+ if (raw === void 0 || raw === "") return void 0;
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(raw);
90
+ } catch {
91
+ return [];
92
+ }
93
+ return Array.isArray(parsed) && parsed.every((key) => typeof key === "string") ? parsed : [];
94
+ }
95
+ /**
96
+ * Length-independent constant-time string equality — no early exit on the
97
+ * first mismatched character or on a length difference, so a caller cannot
98
+ * time its way toward a valid key. No `node:crypto`, to keep this module
99
+ * runtime-agnostic.
100
+ */
101
+ function constantTimeEquals(a, b) {
102
+ const length = Math.max(a.length, b.length);
103
+ let diff = a.length ^ b.length;
104
+ for (let i = 0; i < length; i++) diff |= (i < a.length ? a.charCodeAt(i) : 0) ^ (i < b.length ? b.charCodeAt(i) : 0);
105
+ return diff === 0;
106
+ }
107
+ /** Whether `presented` is a member of `accepted` — always compares against every key. */
108
+ function isAcceptedKey(presented, accepted) {
109
+ let matched = false;
110
+ for (const key of accepted) matched = constantTimeEquals(presented, key) || matched;
111
+ return matched;
112
+ }
113
+ const BEARER_PREFIX = "Bearer ";
114
+ /** The bearer token on `Authorization`, or `''` if the header is missing or malformed. */
115
+ function bearerToken(req) {
116
+ const header = req.headers.get("authorization");
117
+ return header?.startsWith(BEARER_PREFIX) ? header.slice(7) : "";
118
+ }
119
+ /**
120
+ * Flattens every exposed port's methods into one method → {schemas, handler}
121
+ * table. RPC dispatch is flat (`/rpc/<method>`), so a method name exposed by
122
+ * more than one port is a construction-time error, as is a missing handler.
123
+ */
124
+ function methodTable(expose, handlers) {
125
+ const table = /* @__PURE__ */ new Map();
126
+ for (const [port, contract] of Object.entries(expose)) {
127
+ const portHandlers = handlers[port] ?? {};
128
+ for (const [method, fn] of Object.entries(contract.__cmp)) {
129
+ if (table.has(method)) throw new Error(`serve(): method "${method}" is exposed by more than one port — RPC dispatch is flat (POST /rpc/<method>), so method names must be unique across a service's exposed ports.`);
130
+ const handler = portHandlers[method];
131
+ if (handler === void 0) throw new Error(`serve(): no handler supplied for exposed method "${port}.${method}".`);
132
+ const { input, output } = blindCast(fn);
133
+ table.set(method, {
134
+ input,
135
+ output,
136
+ handler
137
+ });
138
+ }
139
+ }
140
+ return table;
141
+ }
142
+ /**
143
+ * Routes `POST /rpc/<method>`: parses JSON, validates input, calls the
144
+ * handler with `service.load()`'s deps, validates the output, and responds
145
+ * JSON. An unknown method or invalid input is a 4xx; a handler (or output
146
+ * validation) failure is a 5xx — either way the process does not crash.
147
+ * `load()` is called exactly once, here, before the handler ever runs.
148
+ */
149
+ function serve(service, handlers) {
150
+ const table = methodTable(service.expose ?? {}, blindCast(handlers));
151
+ const deps = service.load();
152
+ return async (req) => {
153
+ const accepted = acceptedKeys();
154
+ if (accepted !== void 0 && !isAcceptedKey(bearerToken(req), accepted)) return jsonResponse({ error: "Unauthorized: missing or invalid service key" }, 401);
155
+ const { pathname } = new URL(req.url);
156
+ const methodName = /^\/rpc\/([^/]+)$/.exec(pathname)?.[1];
157
+ if (methodName === void 0) return jsonResponse({ error: `Not found: ${pathname}` }, 404);
158
+ const method = table.get(methodName);
159
+ if (method === void 0) return jsonResponse({ error: `Unknown RPC method "${methodName}"` }, 404);
160
+ if (req.method !== "POST") return jsonResponse({ error: `Method "${methodName}" requires POST` }, 405);
161
+ let body;
162
+ try {
163
+ body = await req.json();
164
+ } catch {
165
+ return jsonResponse({ error: "Request body must be JSON" }, 400);
166
+ }
167
+ let input;
168
+ try {
169
+ input = await standardValidate(method.input, body);
170
+ } catch (err) {
171
+ return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 400);
172
+ }
173
+ try {
174
+ const result = await method.handler(input, deps);
175
+ return jsonResponse(await standardValidate(method.output, result));
176
+ } catch (err) {
177
+ return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 500);
178
+ }
179
+ };
180
+ }
181
+ //#endregion
182
+ export { RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, contract, makeClient, perBindingToken, rpc, serve };
183
+
184
+ //# sourceMappingURL=rpc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.mjs","names":[],"sources":["../../../0-framework/2-authoring/rpc/dist/index.mjs"],"sourcesContent":["import { blindCast } from \"@internal/foundation/casts\";\nimport { dependency, provisionNeed, string } from \"@internal/core\";\n//#region src/standard-schema.ts\nasync function standardValidate(schema, value) {\n\tconst result = await schema[\"~standard\"].validate(value);\n\tif (result.issues !== void 0) throw new Error(`Schema validation failed: ${result.issues.map((issue) => issue.message).join(\"; \")}`);\n\treturn result.value;\n}\n//#endregion\n//#region src/client.ts\n/** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */\nfunction methodUrl(base, method) {\n\tconst normalizedBase = base.endsWith(\"/\") ? base : `${base}/`;\n\treturn new URL(`rpc/${method}`, normalizedBase).toString();\n}\n/** The server's `{ error }` body, if the response has one — undefined otherwise. */\nasync function errorDetail(res) {\n\ttry {\n\t\tconst body = await res.json();\n\t\treturn typeof body === \"object\" && body !== null && \"error\" in body ? String(body.error) : void 0;\n\t} catch {\n\t\treturn;\n\t}\n}\nfunction makeClient(contract, url, opts) {\n\tconst send = opts?.fetch ?? fetch;\n\tconst headers = { \"content-type\": \"application/json\" };\n\tif (opts?.serviceKey !== void 0) headers[\"Authorization\"] = `Bearer ${opts.serviceKey}`;\n\tconst client = {};\n\tfor (const [method, schemas] of Object.entries(blindCast(contract.__cmp))) client[method] = async (input) => {\n\t\tconst res = await send(new Request(methodUrl(url, method), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders,\n\t\t\tbody: JSON.stringify(input)\n\t\t}));\n\t\tif (!res.ok) {\n\t\t\tconst detail = await errorDetail(res);\n\t\t\tthrow new Error(`RPC call \"${method}\" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : \"\"));\n\t\t}\n\t\treturn standardValidate(schemas.output, await res.json());\n\t};\n\treturn blindCast(client);\n}\n//#endregion\n//#region src/contract.ts\nfunction contract(fns) {\n\tconst value = {\n\t\tkind: \"rpc\",\n\t\t__cmp: fns,\n\t\tsatisfies: (required) => value === required\n\t};\n\treturn Object.freeze(value);\n}\n//#endregion\n//#region src/rpc.ts\n/** ADR-0031's need brand for RPC's per-binding service key — the target registers a provisioner under this. */\nconst RPC_PEER_KEY = Symbol.for(\"prisma:rpc/per-binding-key\");\n/** The provisioning need `rpc()`'s `serviceKey` param declares (ADR-0030): a shared, unguessable value the target mints per consumer edge. */\nconst perBindingToken = () => provisionNeed(RPC_PEER_KEY);\nfunction rpc(arg) {\n\tif (!isRpcContract(arg)) return arg;\n\treturn dependency({\n\t\ttype: \"rpc\",\n\t\tconnection: {\n\t\t\tparams: {\n\t\t\t\turl: string(),\n\t\t\t\tserviceKey: string({\n\t\t\t\t\toptional: true,\n\t\t\t\t\tprovision: perBindingToken()\n\t\t\t\t})\n\t\t\t},\n\t\t\thydrate: ({ url, serviceKey }) => makeClient(arg, url, { serviceKey })\n\t\t},\n\t\trequired: arg\n\t});\n}\nfunction isRpcContract(value) {\n\treturn typeof value === \"object\" && value !== null && \"kind\" in value && value.kind === \"rpc\" && \"__cmp\" in value && \"satisfies\" in value;\n}\n//#endregion\n//#region src/serve.ts\n/** The reserved env var the target (slice 2) writes the accepted key set to. */\nconst RPC_ACCEPTED_KEYS_ENV = \"COMPOSER_RPC_ACCEPTED_KEYS\";\nfunction jsonResponse(body, status = 200) {\n\treturn new Response(JSON.stringify(body), {\n\t\tstatus,\n\t\theaders: { \"content-type\": \"application/json\" }\n\t});\n}\n/** The provisioned accepted key set, or undefined when the deploy never provisioned one (local/test — enforcement off). */\nfunction acceptedKeys() {\n\tconst raw = process.env[RPC_ACCEPTED_KEYS_ENV];\n\tif (raw === void 0 || raw === \"\") return void 0;\n\tlet parsed;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn [];\n\t}\n\treturn Array.isArray(parsed) && parsed.every((key) => typeof key === \"string\") ? parsed : [];\n}\n/**\n* Length-independent constant-time string equality — no early exit on the\n* first mismatched character or on a length difference, so a caller cannot\n* time its way toward a valid key. No `node:crypto`, to keep this module\n* runtime-agnostic.\n*/\nfunction constantTimeEquals(a, b) {\n\tconst length = Math.max(a.length, b.length);\n\tlet diff = a.length ^ b.length;\n\tfor (let i = 0; i < length; i++) diff |= (i < a.length ? a.charCodeAt(i) : 0) ^ (i < b.length ? b.charCodeAt(i) : 0);\n\treturn diff === 0;\n}\n/** Whether `presented` is a member of `accepted` — always compares against every key. */\nfunction isAcceptedKey(presented, accepted) {\n\tlet matched = false;\n\tfor (const key of accepted) matched = constantTimeEquals(presented, key) || matched;\n\treturn matched;\n}\nconst BEARER_PREFIX = \"Bearer \";\n/** The bearer token on `Authorization`, or `''` if the header is missing or malformed. */\nfunction bearerToken(req) {\n\tconst header = req.headers.get(\"authorization\");\n\treturn header?.startsWith(BEARER_PREFIX) ? header.slice(7) : \"\";\n}\n/**\n* Flattens every exposed port's methods into one method → {schemas, handler}\n* table. RPC dispatch is flat (`/rpc/<method>`), so a method name exposed by\n* more than one port is a construction-time error, as is a missing handler.\n*/\nfunction methodTable(expose, handlers) {\n\tconst table = /* @__PURE__ */ new Map();\n\tfor (const [port, contract] of Object.entries(expose)) {\n\t\tconst portHandlers = handlers[port] ?? {};\n\t\tfor (const [method, fn] of Object.entries(contract.__cmp)) {\n\t\t\tif (table.has(method)) throw new Error(`serve(): method \"${method}\" is exposed by more than one port — RPC dispatch is flat (POST /rpc/<method>), so method names must be unique across a service's exposed ports.`);\n\t\t\tconst handler = portHandlers[method];\n\t\t\tif (handler === void 0) throw new Error(`serve(): no handler supplied for exposed method \"${port}.${method}\".`);\n\t\t\tconst { input, output } = blindCast(fn);\n\t\t\ttable.set(method, {\n\t\t\t\tinput,\n\t\t\t\toutput,\n\t\t\t\thandler\n\t\t\t});\n\t\t}\n\t}\n\treturn table;\n}\n/**\n* Routes `POST /rpc/<method>`: parses JSON, validates input, calls the\n* handler with `service.load()`'s deps, validates the output, and responds\n* JSON. An unknown method or invalid input is a 4xx; a handler (or output\n* validation) failure is a 5xx — either way the process does not crash.\n* `load()` is called exactly once, here, before the handler ever runs.\n*/\nfunction serve(service, handlers) {\n\tconst table = methodTable(service.expose ?? {}, blindCast(handlers));\n\tconst deps = service.load();\n\treturn async (req) => {\n\t\tconst accepted = acceptedKeys();\n\t\tif (accepted !== void 0 && !isAcceptedKey(bearerToken(req), accepted)) return jsonResponse({ error: \"Unauthorized: missing or invalid service key\" }, 401);\n\t\tconst { pathname } = new URL(req.url);\n\t\tconst methodName = /^\\/rpc\\/([^/]+)$/.exec(pathname)?.[1];\n\t\tif (methodName === void 0) return jsonResponse({ error: `Not found: ${pathname}` }, 404);\n\t\tconst method = table.get(methodName);\n\t\tif (method === void 0) return jsonResponse({ error: `Unknown RPC method \"${methodName}\"` }, 404);\n\t\tif (req.method !== \"POST\") return jsonResponse({ error: `Method \"${methodName}\" requires POST` }, 405);\n\t\tlet body;\n\t\ttry {\n\t\t\tbody = await req.json();\n\t\t} catch {\n\t\t\treturn jsonResponse({ error: \"Request body must be JSON\" }, 400);\n\t\t}\n\t\tlet input;\n\t\ttry {\n\t\t\tinput = await standardValidate(method.input, body);\n\t\t} catch (err) {\n\t\t\treturn jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst result = await method.handler(input, deps);\n\t\t\treturn jsonResponse(await standardValidate(method.output, result));\n\t\t} catch (err) {\n\t\t\treturn jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 500);\n\t\t}\n\t};\n}\n//#endregion\nexport { RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, contract, makeClient, perBindingToken, rpc, serve };\n\n//# sourceMappingURL=index.mjs.map"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { B as Secrets, H as Values, O as Params, P as RunnableServiceNode, c as Deps, m as HydratedDeps, u as Expose } from "./config-ad92ubCB-D0-dZUgA.mjs";
2
+ //#region ../../0-framework/1-core/core/dist/testing.d.mts
3
+ //#region src/testing.d.ts
4
+ /**
5
+ * `mockService`'s override argument: every declared dependency, typed against
6
+ * its own hydrated shape (`Client<C>` for an RPC dep, the resource binding
7
+ * for a resource dep) — a double of the wrong shape is a compile error. The
8
+ * service's own params are optional; an omitted one falls back to its
9
+ * declared default, same as a real `load()`.
10
+ */
11
+ type LoadOverrides<D extends Deps, P extends Params> = HydratedDeps<D> & Partial<Values<P>>;
12
+ /**
13
+ * Returns a service node whose `load()` yields the dependency doubles and
14
+ * `config()` yields the service's params (defaults overlaid with any
15
+ * overrides) — everything else about the node (its deps, params, build,
16
+ * expose) is unchanged. `overrides` is one flat object: dependency keys route
17
+ * to `load()`, param keys to `config()`. `run()` is not meaningful on a mock
18
+ * (there is no boot, no environment) and throws if called.
19
+ */
20
+ declare function mockService<D extends Deps, P extends Params, E extends Expose, S extends Secrets>(service: RunnableServiceNode<D, P, E, S>, overrides: LoadOverrides<D, P>): RunnableServiceNode<D, P, E, S>;
21
+ //#endregion
22
+ export { LoadOverrides, mockService };
23
+ //# sourceMappingURL=testing.d.mts.map
@@ -0,0 +1,45 @@
1
+ import { t as blindCast } from "./casts-Ci5rYYaR.mjs";
2
+ //#region ../../0-framework/1-core/core/dist/testing.mjs
3
+ /**
4
+ * The unit-test seam (testing.md § Unit): `mockService` replaces a service
5
+ * node's `load()` and `config()` output so any code that pulls dependencies or
6
+ * params through them — a page, a server action, a helper — runs against typed
7
+ * doubles with no server and no environment. Target-agnostic: every service
8
+ * node has `load()`/`config()`. It does no module mocking; wiring the
9
+ * substitution into a test runner (`vi.mock`, `mock.module`) stays in the test.
10
+ * The integration seam (`bootstrapService`) is target-specific and lives in the
11
+ * target's own testing entry (e.g. `@prisma/composer-prisma-cloud/testing`).
12
+ */
13
+ function paramDefaults(params) {
14
+ const defaults = {};
15
+ for (const [name, param] of Object.entries(params)) if (param.default !== void 0) defaults[name] = param.default;
16
+ return blindCast(defaults);
17
+ }
18
+ /**
19
+ * Returns a service node whose `load()` yields the dependency doubles and
20
+ * `config()` yields the service's params (defaults overlaid with any
21
+ * overrides) — everything else about the node (its deps, params, build,
22
+ * expose) is unchanged. `overrides` is one flat object: dependency keys route
23
+ * to `load()`, param keys to `config()`. `run()` is not meaningful on a mock
24
+ * (there is no boot, no environment) and throws if called.
25
+ */
26
+ function mockService(service, overrides) {
27
+ const entries = Object.entries(overrides);
28
+ const deps = blindCast(Object.fromEntries(entries.filter(([name]) => name in service.inputs)));
29
+ const config = blindCast({
30
+ ...paramDefaults(service.params),
31
+ ...Object.fromEntries(entries.filter(([name]) => name in service.params))
32
+ });
33
+ return Object.freeze({
34
+ ...service,
35
+ run() {
36
+ throw new Error(`mockService(): "${service.name}" is a load()/config()-only mock — it has no run() (no boot, no environment).`);
37
+ },
38
+ load: () => deps,
39
+ config: () => config
40
+ });
41
+ }
42
+ //#endregion
43
+ export { mockService };
44
+
45
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../../../0-framework/1-core/core/dist/testing.mjs"],"sourcesContent":["import { blindCast } from \"@internal/foundation/casts\";\n//#region src/testing.ts\n/**\n* The unit-test seam (testing.md § Unit): `mockService` replaces a service\n* node's `load()` and `config()` output so any code that pulls dependencies or\n* params through them — a page, a server action, a helper — runs against typed\n* doubles with no server and no environment. Target-agnostic: every service\n* node has `load()`/`config()`. It does no module mocking; wiring the\n* substitution into a test runner (`vi.mock`, `mock.module`) stays in the test.\n* The integration seam (`bootstrapService`) is target-specific and lives in the\n* target's own testing entry (e.g. `@prisma/composer-prisma-cloud/testing`).\n*/\nfunction paramDefaults(params) {\n\tconst defaults = {};\n\tfor (const [name, param] of Object.entries(params)) if (param.default !== void 0) defaults[name] = param.default;\n\treturn blindCast(defaults);\n}\n/**\n* Returns a service node whose `load()` yields the dependency doubles and\n* `config()` yields the service's params (defaults overlaid with any\n* overrides) — everything else about the node (its deps, params, build,\n* expose) is unchanged. `overrides` is one flat object: dependency keys route\n* to `load()`, param keys to `config()`. `run()` is not meaningful on a mock\n* (there is no boot, no environment) and throws if called.\n*/\nfunction mockService(service, overrides) {\n\tconst entries = Object.entries(overrides);\n\tconst deps = blindCast(Object.fromEntries(entries.filter(([name]) => name in service.inputs)));\n\tconst config = blindCast({\n\t\t...paramDefaults(service.params),\n\t\t...Object.fromEntries(entries.filter(([name]) => name in service.params))\n\t});\n\treturn Object.freeze({\n\t\t...service,\n\t\trun() {\n\t\t\tthrow new Error(`mockService(): \"${service.name}\" is a load()/config()-only mock — it has no run() (no boot, no environment).`);\n\t\t},\n\t\tload: () => deps,\n\t\tconfig: () => config\n\t});\n}\n//#endregion\nexport { mockService };\n\n//# sourceMappingURL=testing.mjs.map"],"mappings":";;;;;;;;;;;;AAYA,SAAS,cAAc,QAAQ;CAC9B,MAAM,WAAW,CAAC;CAClB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAAG,IAAI,MAAM,YAAY,KAAK,GAAG,SAAS,QAAQ,MAAM;CACzG,OAAO,UAAU,QAAQ;AAC1B;;;;;;;;;AASA,SAAS,YAAY,SAAS,WAAW;CACxC,MAAM,UAAU,OAAO,QAAQ,SAAS;CACxC,MAAM,OAAO,UAAU,OAAO,YAAY,QAAQ,QAAQ,CAAC,UAAU,QAAQ,QAAQ,MAAM,CAAC,CAAC;CAC7F,MAAM,SAAS,UAAU;EACxB,GAAG,cAAc,QAAQ,MAAM;EAC/B,GAAG,OAAO,YAAY,QAAQ,QAAQ,CAAC,UAAU,QAAQ,QAAQ,MAAM,CAAC;CACzE,CAAC;CACD,OAAO,OAAO,OAAO;EACpB,GAAG;EACH,MAAM;GACL,MAAM,IAAI,MAAM,mBAAmB,QAAQ,KAAK,8EAA8E;EAC/H;EACA,YAAY;EACZ,cAAc;CACf,CAAC;AACF"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@prisma/composer",
3
+ "version": "0.1.0-dev.1",
4
+ "type": "module",
5
+ "description": "Prisma Composer — build a Prisma App by composing Modules. Core authoring, deploy pipeline, the prisma-composer CLI, and the rpc/node/nextjs authoring surfaces.",
6
+ "bin": {
7
+ "prisma-composer": "./dist/bin.mjs"
8
+ },
9
+ "exports": {
10
+ ".": "./dist/index.mjs",
11
+ "./config": "./dist/config.mjs",
12
+ "./deploy": "./dist/deploy.mjs",
13
+ "./report": "./dist/report.mjs",
14
+ "./testing": "./dist/testing.mjs",
15
+ "./casts": "./dist/casts.mjs",
16
+ "./assertions": "./dist/assertions.mjs",
17
+ "./rpc": "./dist/rpc.mjs",
18
+ "./node": "./dist/node.mjs",
19
+ "./node/control": "./dist/node-control.mjs",
20
+ "./nextjs": "./dist/nextjs.mjs",
21
+ "./nextjs/control": "./dist/nextjs-control.mjs",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "dependencies": {
28
+ "@standard-schema/spec": "^1.1.0",
29
+ "alchemy": "2.0.0-beta.59",
30
+ "arktype": "^2.2.3",
31
+ "c12": "^3.3.4",
32
+ "clipanion": "^3.2.1",
33
+ "effect": "4.0.0-beta.93",
34
+ "esbuild": "^0.28.1",
35
+ "postgres": "^3.4.9",
36
+ "@prisma/management-api-sdk": "^1.47.0"
37
+ },
38
+ "devDependencies": {
39
+ "@internal/assemble": "0.1.0-dev.1",
40
+ "@internal/cli": "0.1.0-dev.1",
41
+ "@internal/core": "0.1.0-dev.1",
42
+ "@internal/foundation": "0.1.0-dev.1",
43
+ "@internal/lowering": "0.1.0-dev.1",
44
+ "@internal/nextjs": "0.1.0-dev.1",
45
+ "@internal/node": "0.1.0-dev.1",
46
+ "@internal/rpc": "0.1.0-dev.1",
47
+ "@internal/tsdown-config": "0.1.0-dev.1",
48
+ "@types/node": "^25.9.3",
49
+ "tsdown": "^0.22.4",
50
+ "typescript": "^6.0.3"
51
+ },
52
+ "license": "Apache-2.0",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/prisma/composer.git",
56
+ "directory": "packages/9-public/composer"
57
+ },
58
+ "engines": {
59
+ "node": ">=24"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "scripts": {
65
+ "build": "tsdown",
66
+ "clean": "rm -rf dist",
67
+ "typecheck": "tsc --noEmit"
68
+ }
69
+ }