@prisma/composer 0.2.0 → 0.3.0-dev.10
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/dist/{app-config-DJdU4ubR-BwioNK8j.d.mts → app-config-bsTo57A8-BfG21wAI.d.mts} +89 -5
- package/dist/app-config-bsTo57A8-BfG21wAI.d.mts.map +1 -0
- package/dist/app-config-joXc-BKm-CdcXqNMD.mjs +22 -0
- package/dist/app-config-joXc-BKm-CdcXqNMD.mjs.map +1 -0
- package/dist/arktype.d.mts +16 -0
- package/dist/arktype.d.mts.map +1 -0
- package/dist/arktype.mjs +22 -0
- package/dist/arktype.mjs.map +1 -0
- package/dist/assertions.d.mts.map +1 -0
- package/dist/bin.mjs +2444 -206
- package/dist/bin.mjs.map +1 -1
- package/dist/{casts-Ci5rYYaR.mjs → casts-DpaahrlC-C5XMnP9S.mjs} +2 -2
- package/dist/casts-DpaahrlC-C5XMnP9S.mjs.map +1 -0
- package/dist/casts.d.mts.map +1 -0
- package/dist/casts.mjs +1 -1
- package/dist/config-DUzvB5cQ.d.mts +1 -0
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +2 -9
- package/dist/deploy-C4DxNkJ9-D73lmBqy.mjs +313 -0
- package/dist/deploy-C4DxNkJ9-D73lmBqy.mjs.map +1 -0
- package/dist/deploy-DUzvB5cQ.d.mts +1 -0
- package/dist/deploy.d.mts +2 -2
- package/dist/deploy.mjs +1 -311
- package/dist/{dist-B0axxnBf.mjs → dist-BdvFDCLV.mjs} +12 -76
- package/dist/dist-BdvFDCLV.mjs.map +1 -0
- package/dist/{graph-B7NcPiOr-aSUOCGTH.d.mts → graph-DDpXh7S8-b-93BcHn.d.mts} +3 -3
- package/dist/graph-DDpXh7S8-b-93BcHn.d.mts.map +1 -0
- package/dist/{graph-BmrUEdo9-6Oq1hSmS.mjs → graph-DgbALPDo-BEHF94BN.mjs} +75 -52
- package/dist/graph-DgbALPDo-BEHF94BN.mjs.map +1 -0
- package/dist/{graph-types-BgT9UEdm-Bz-_OcJH.d.mts → graph-types-C3FOaaXm-C6_rGzfn.d.mts} +87 -87
- package/dist/graph-types-C3FOaaXm-C6_rGzfn.d.mts.map +1 -0
- package/dist/index-BQVrMGfe.d.mts +22 -0
- package/dist/index-BQVrMGfe.d.mts.map +1 -0
- package/dist/index.d.mts +5 -4
- package/dist/index.mjs +4 -3
- package/dist/local-target.d.mts +29 -0
- package/dist/local-target.d.mts.map +1 -0
- package/dist/local-target.mjs +47 -0
- package/dist/local-target.mjs.map +1 -0
- package/dist/{nextjs-DLyeRR7M-B9ukbB2L.d.mts → nextjs-DLyeRR7M-D47aMmfr.d.mts} +3 -3
- package/dist/nextjs-DLyeRR7M-D47aMmfr.d.mts.map +1 -0
- package/dist/nextjs-control.d.mts +4 -4
- package/dist/nextjs-control.d.mts.map +1 -0
- package/dist/nextjs-control.mjs +2 -1
- package/dist/nextjs-control.mjs.map +1 -1
- package/dist/nextjs.d.mts +1 -1
- package/dist/node-control.d.mts +3 -3
- package/dist/node-control.d.mts.map +1 -0
- package/dist/node-control.mjs +44 -25
- package/dist/node-control.mjs.map +1 -1
- package/dist/node.d.mts +2 -2
- package/dist/node.d.mts.map +1 -0
- package/dist/report.d.mts +2 -2
- package/dist/report.d.mts.map +1 -0
- package/dist/report.mjs +4 -0
- package/dist/report.mjs.map +1 -1
- package/dist/secret-CHOw4LBU-C8yXoC9N.d.mts +25 -0
- package/dist/secret-CHOw4LBU-C8yXoC9N.d.mts.map +1 -0
- package/dist/secret-DoWGmN7m-DO5Brq1Y.mjs +55 -0
- package/dist/secret-DoWGmN7m-DO5Brq1Y.mjs.map +1 -0
- package/dist/service-rpc.d.mts +15 -5
- package/dist/service-rpc.d.mts.map +1 -0
- package/dist/service-rpc.mjs +6 -4
- package/dist/service-rpc.mjs.map +1 -1
- package/dist/testing.d.mts +17 -11
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +12 -20
- package/dist/testing.mjs.map +1 -1
- package/package.json +15 -12
- package/src/exports/arktype.ts +1 -0
- package/src/exports/assertions.ts +1 -0
- package/src/exports/casts.ts +1 -0
- package/src/exports/config.ts +1 -0
- package/src/exports/deploy.ts +1 -0
- package/src/exports/index.ts +1 -0
- package/src/exports/local-target.ts +1 -0
- package/src/exports/nextjs-control.ts +1 -0
- package/src/exports/nextjs.ts +2 -0
- package/src/exports/node-control.ts +1 -0
- package/src/exports/node.ts +2 -0
- package/src/exports/report.ts +1 -0
- package/src/exports/service-rpc.ts +1 -0
- package/src/exports/testing.ts +1 -0
- package/dist/casts-Ci5rYYaR.mjs.map +0 -1
- package/dist/config-ByZICgry.d.mts +0 -1
- package/dist/config.mjs.map +0 -1
- package/dist/deploy-ByZICgry.d.mts +0 -1
- package/dist/deploy.mjs.map +0 -1
- package/dist/dist-B0axxnBf.mjs.map +0 -1
- package/dist/graph-BmrUEdo9-6Oq1hSmS.mjs.map +0 -1
- package/dist/index-B2DJ5CN4.d.mts +0 -32
package/dist/node-control.mjs
CHANGED
|
@@ -46,26 +46,10 @@ function resolveFile(entrySpec, moduleDir) {
|
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
* followed — only `dir` is ever copied.
|
|
54
|
-
*/
|
|
55
|
-
async function resolveDir(dirSpec, entrySpec, moduleDir) {
|
|
56
|
-
const dirPath = path.resolve(moduleDir, dirSpec);
|
|
57
|
-
if (!fs.existsSync(dirPath)) throw new Error(`no built directory at ${dirPath} — run your build first (the build adapter's dir, "${dirSpec}", resolves against dirname(module)).`);
|
|
58
|
-
if (!fs.statSync(dirPath).isDirectory()) throw new Error(`the build adapter's dir ("${dirPath}") is not a directory — drop dir to deploy a single built file, naming it as entry.`);
|
|
59
|
-
const entryPath = path.resolve(dirPath, entrySpec);
|
|
60
|
-
if (!entryPath.startsWith(dirPath + path.sep)) throw new Error(`the build adapter's entry ("${entrySpec}") resolves to ${entryPath}, which is not inside dir ("${dirPath}") — in the directory form entry names a file inside dir, and only dir is copied.`);
|
|
61
|
-
if (!fs.existsSync(entryPath) || !fs.statSync(entryPath).isFile()) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, "${entrySpec}", resolves inside dir, "${dirPath}").`);
|
|
62
|
-
await assertNoSymlinks(dirPath);
|
|
63
|
-
return {
|
|
64
|
-
source: dirPath,
|
|
65
|
-
sourceField: "dir",
|
|
66
|
-
entry: path.relative(dirPath, entryPath).split(path.sep).join("/"),
|
|
67
|
-
copyInto: (bundleDir) => fs.promises.cp(dirPath, bundleDir, { recursive: true })
|
|
68
|
-
};
|
|
49
|
+
/** The shared "dir contains symlinks" error, reused by both the root-is-a-symlink case and the nested-symlink walk below — one message shape, never two to drift apart. */
|
|
50
|
+
function symlinksFoundError(dirPath, found) {
|
|
51
|
+
const listed = found.slice(0, 5).join(", ");
|
|
52
|
+
return /* @__PURE__ */ new Error(`the build adapter's dir ("${dirPath}") contains symlinks, which the platform's packager rejects: ${listed}${found.length > 5 ? `, and ${found.length - 5} more` : ""}. The tree is copied verbatim, so make your build emit real files in dir (for example, a hoisted node_modules, or dereference the links into dir with cp -RL).`);
|
|
69
53
|
}
|
|
70
54
|
/**
|
|
71
55
|
* Compute's packager rejects symlinks, so a tree containing one cannot deploy.
|
|
@@ -73,7 +57,10 @@ async function resolveDir(dirSpec, entrySpec, moduleDir) {
|
|
|
73
57
|
* the artifact must be what the author's build produced (ADR-0005), and
|
|
74
58
|
* following a link that points outside `dir` would pull in files the author
|
|
75
59
|
* never named. The walk reads dirents (lstat semantics), so a symlinked
|
|
76
|
-
* directory is reported and never descended into.
|
|
60
|
+
* directory is reported and never descended into. Checks only `dirPath`'s
|
|
61
|
+
* children — the caller (`resolveDir`) checks `dirPath` itself before this
|
|
62
|
+
* runs, since that check also decides "not a directory" vs "is a symlink"
|
|
63
|
+
* and must happen before any dereferencing stat.
|
|
77
64
|
*/
|
|
78
65
|
async function assertNoSymlinks(dirPath) {
|
|
79
66
|
const found = [];
|
|
@@ -85,9 +72,40 @@ async function assertNoSymlinks(dirPath) {
|
|
|
85
72
|
}
|
|
86
73
|
};
|
|
87
74
|
await walk(dirPath);
|
|
88
|
-
if (found.length
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
if (found.length > 0) throw symlinksFoundError(dirPath, found);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The directory form: `dir` is the built tree, resolved against dirname(module)
|
|
79
|
+
* (ADR-0004) and copied whole; `entry` resolves inside `dir` and names the file
|
|
80
|
+
* that boots. An `entry` that resolves outside `dir` is rejected rather than
|
|
81
|
+
* followed — only `dir` is ever copied.
|
|
82
|
+
*
|
|
83
|
+
* `dir` itself can be a symlink — not just something inside it. `lstat` it
|
|
84
|
+
* before anything else so that case hard-errors identically whether the link
|
|
85
|
+
* points at a real directory (which a dereferencing `stat` would otherwise
|
|
86
|
+
* accept as "is a directory") or at a file: neither is ever silently
|
|
87
|
+
* dereferenced and copied (ADR-0005).
|
|
88
|
+
*/
|
|
89
|
+
async function resolveDir(dirSpec, entrySpec, moduleDir) {
|
|
90
|
+
const dirPath = path.resolve(moduleDir, dirSpec);
|
|
91
|
+
let dirLstat;
|
|
92
|
+
try {
|
|
93
|
+
dirLstat = await fs.promises.lstat(dirPath);
|
|
94
|
+
} catch {
|
|
95
|
+
throw new Error(`no built directory at ${dirPath} — run your build first (the build adapter's dir, "${dirSpec}", resolves against dirname(module)).`);
|
|
96
|
+
}
|
|
97
|
+
if (dirLstat.isSymbolicLink()) throw symlinksFoundError(dirPath, [dirPath]);
|
|
98
|
+
if (!dirLstat.isDirectory()) throw new Error(`the build adapter's dir ("${dirPath}") is not a directory — drop dir to deploy a single built file, naming it as entry.`);
|
|
99
|
+
const entryPath = path.resolve(dirPath, entrySpec);
|
|
100
|
+
if (!entryPath.startsWith(dirPath + path.sep)) throw new Error(`the build adapter's entry ("${entrySpec}") resolves to ${entryPath}, which is not inside dir ("${dirPath}") — in the directory form entry names a file inside dir, and only dir is copied.`);
|
|
101
|
+
if (!fs.existsSync(entryPath) || !fs.statSync(entryPath).isFile()) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, "${entrySpec}", resolves inside dir, "${dirPath}").`);
|
|
102
|
+
await assertNoSymlinks(dirPath);
|
|
103
|
+
return {
|
|
104
|
+
source: dirPath,
|
|
105
|
+
sourceField: "dir",
|
|
106
|
+
entry: path.relative(dirPath, entryPath).split(path.sep).join("/"),
|
|
107
|
+
copyInto: (bundleDir) => fs.promises.cp(dirPath, bundleDir, { recursive: true })
|
|
108
|
+
};
|
|
91
109
|
}
|
|
92
110
|
/**
|
|
93
111
|
* The working dir is cleared on every assemble, so it must not overlap the copy
|
|
@@ -125,7 +143,8 @@ async function assemble(input) {
|
|
|
125
143
|
await runnable.copyInto(path.join(workDir, "bundle"));
|
|
126
144
|
return {
|
|
127
145
|
dir: workDir,
|
|
128
|
-
entry: path.posix.join("bundle", runnable.entry)
|
|
146
|
+
entry: path.posix.join("bundle", runnable.entry),
|
|
147
|
+
watch: [runnable.source]
|
|
129
148
|
};
|
|
130
149
|
}
|
|
131
150
|
/** The node build extension descriptor — `prisma-composer.config.ts` lists it under `extensions`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-control.mjs","names":[],"sources":["../../../0-framework/2-authoring/node/dist/control.mjs"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { build } from \"esbuild\";\n//#region src/control/build.ts\n/**\n* The extension's control entry (ADR-0017): `nodeBuild()` returns the build\n* descriptor `prisma-composer.config.ts` lists. Deploy-only (ADR-0005): the user\n* builds their own runnable; `assemble` copies what they built under `bundle/`\n* and adds the framework's boot wrapper — it never bundles or transforms the\n* app's code.\n*\n* Two forms, chosen by the descriptor: without `dir`, `entry` is a single\n* self-contained file and only that file is copied. With `dir`, the whole\n* directory is copied verbatim and `entry` names the file inside it that boots.\n* Neither form discovers anything — no tree-walking for an entry, no filename\n* heuristics; the author states the paths and we copy exactly those.\n*\n* The wrapper is a SEPARATE esbuild build of the service module (declarations\n* only, whose node carries run()/load()), emitted as `main.mjs` at the\n* working-dir root — a dictated name (object entry `{ main }`), not a\n* discovered one. run() and the app entry must be independent module instances\n* that hand off through process.env, so the wrapper is its own self-contained\n* build; `@prisma/*` is inlined, `bun` is a Compute built-in.\n*\n* Artifact layout: `<cwd>/.prisma-composer/artifacts/<address>/` (deploy-owned,\n* ADR-0005) holds `main.mjs` at the root and the app's built runnable under\n* `bundle/`.\n*/\n/** Narrows the shared BuildAdapter to this extension's own descriptor — the value-level mirror of the registry routing on (extension, type). `dir` is optional: absent is the single-file form. */\nfunction isNodeBuild(descriptor) {\n\treturn descriptor.type === \"node\" && (!(\"dir\" in descriptor) || typeof descriptor.dir === \"string\");\n}\n/** The single-file form: `entry` is the whole built runnable, resolved against dirname(module) (ADR-0004). */\nfunction resolveFile(entrySpec, moduleDir) {\n\tconst entryPath = path.resolve(moduleDir, entrySpec);\n\tif (!fs.existsSync(entryPath)) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, \"${entrySpec}\", resolves against dirname(module)).`);\n\tconst entryFile = path.basename(entryPath);\n\treturn {\n\t\tsource: entryPath,\n\t\tsourceField: \"entry\",\n\t\tentry: entryFile,\n\t\tcopyInto: async (bundleDir) => {\n\t\t\tawait fs.promises.mkdir(bundleDir, { recursive: true });\n\t\t\tawait fs.promises.copyFile(entryPath, path.join(bundleDir, entryFile));\n\t\t}\n\t};\n}\n/**\n* The directory form: `dir` is the built tree, resolved against dirname(module)\n* (ADR-0004) and copied whole; `entry` resolves inside `dir` and names the file\n* that boots. An `entry` that resolves outside `dir` is rejected rather than\n* followed — only `dir` is ever copied.\n*/\nasync function resolveDir(dirSpec, entrySpec, moduleDir) {\n\tconst dirPath = path.resolve(moduleDir, dirSpec);\n\tif (!fs.existsSync(dirPath)) throw new Error(`no built directory at ${dirPath} — run your build first (the build adapter's dir, \"${dirSpec}\", resolves against dirname(module)).`);\n\tif (!fs.statSync(dirPath).isDirectory()) throw new Error(`the build adapter's dir (\"${dirPath}\") is not a directory — drop dir to deploy a single built file, naming it as entry.`);\n\tconst entryPath = path.resolve(dirPath, entrySpec);\n\tif (!entryPath.startsWith(dirPath + path.sep)) throw new Error(`the build adapter's entry (\"${entrySpec}\") resolves to ${entryPath}, which is not inside dir (\"${dirPath}\") — in the directory form entry names a file inside dir, and only dir is copied.`);\n\tif (!fs.existsSync(entryPath) || !fs.statSync(entryPath).isFile()) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, \"${entrySpec}\", resolves inside dir, \"${dirPath}\").`);\n\tawait assertNoSymlinks(dirPath);\n\treturn {\n\t\tsource: dirPath,\n\t\tsourceField: \"dir\",\n\t\tentry: path.relative(dirPath, entryPath).split(path.sep).join(\"/\"),\n\t\tcopyInto: (bundleDir) => fs.promises.cp(dirPath, bundleDir, { recursive: true })\n\t};\n}\n/**\n* Compute's packager rejects symlinks, so a tree containing one cannot deploy.\n* We fail here, naming the links, rather than dereferencing them on the copy:\n* the artifact must be what the author's build produced (ADR-0005), and\n* following a link that points outside `dir` would pull in files the author\n* never named. The walk reads dirents (lstat semantics), so a symlinked\n* directory is reported and never descended into.\n*/\nasync function assertNoSymlinks(dirPath) {\n\tconst found = [];\n\tconst walk = async (current) => {\n\t\tfor (const entry of await fs.promises.readdir(current, { withFileTypes: true })) {\n\t\t\tconst full = path.join(current, entry.name);\n\t\t\tif (entry.isSymbolicLink()) found.push(full);\n\t\t\telse if (entry.isDirectory()) await walk(full);\n\t\t}\n\t};\n\tawait walk(dirPath);\n\tif (found.length === 0) return;\n\tconst listed = found.slice(0, 5).join(\", \");\n\tthrow new Error(`the build adapter's dir (\"${dirPath}\") contains symlinks, which the platform's packager rejects: ${listed}${found.length > 5 ? `, and ${found.length - 5} more` : \"\"}. The tree is copied verbatim, so make your build emit real files in dir (for example, a hoisted node_modules, or dereference the links into dir with cp -RL).`);\n}\n/**\n* The working dir is cleared on every assemble, so it must not overlap the copy\n* source: inside it, the rm would delete the source before the copy; the other\n* way round, the copy would recurse into its own output.\n*/\nfunction assertOutsideWorkDir(runnable, workDir) {\n\tconst { source, sourceField } = runnable;\n\tif (source === workDir || source.startsWith(workDir + path.sep)) throw new Error(`the build adapter's ${sourceField} (\"${source}\") resolves inside the deploy working dir (\"${workDir}\"), which is cleared on every assemble — point ${sourceField} at your build output elsewhere.`);\n\tif (workDir.startsWith(source + path.sep)) throw new Error(`the deploy working dir (\"${workDir}\") sits inside the build adapter's ${sourceField} (\"${source}\"), so assembling would copy the artifact into itself — point ${sourceField} at your build output elsewhere.`);\n}\nasync function assemble(input) {\n\tif (!isNodeBuild(input.build)) throw new Error(`@prisma/composer/node/control: expected a \"node\" build adapter, got \"${input.build.type}\".`);\n\tconst buildDescriptor = input.build;\n\tconst serviceModule = fileURLToPath(buildDescriptor.module);\n\tconst moduleDir = path.dirname(serviceModule);\n\tconst runnable = buildDescriptor.dir === void 0 ? resolveFile(buildDescriptor.entry, moduleDir) : await resolveDir(buildDescriptor.dir, buildDescriptor.entry, moduleDir);\n\tconst workDir = path.join(input.cwd, \".prisma-composer\", \"artifacts\", input.address);\n\tassertOutsideWorkDir(runnable, workDir);\n\tawait fs.promises.rm(workDir, {\n\t\trecursive: true,\n\t\tforce: true\n\t});\n\tawait fs.promises.mkdir(workDir, { recursive: true });\n\tawait build({\n\t\tentryPoints: { main: serviceModule },\n\t\toutdir: workDir,\n\t\tbundle: true,\n\t\tformat: \"esm\",\n\t\tplatform: \"node\",\n\t\texternal: [\"bun\", \"bun:*\"],\n\t\toutExtension: { \".js\": \".mjs\" }\n\t});\n\tif (!fs.existsSync(path.join(workDir, \"main.mjs\"))) throw new Error(`esbuild produced no main.mjs in ${workDir}`);\n\tawait runnable.copyInto(path.join(workDir, \"bundle\"));\n\treturn {\n\t\tdir: workDir,\n\t\tentry: path.posix.join(\"bundle\", runnable.entry)\n\t};\n}\n/** The node build extension descriptor — `prisma-composer.config.ts` lists it under `extensions`. */\nconst nodeBuild = () => ({\n\tid: \"@prisma/composer/node\",\n\tnodes: { node: {\n\t\tkind: \"build\",\n\t\tassemble\n\t} }\n});\n//#endregion\nexport { assemble, nodeBuild };\n\n//# sourceMappingURL=control.mjs.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-control.mjs","names":[],"sources":["../../../0-framework/2-authoring/node/dist/control.mjs"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { build } from \"esbuild\";\n//#region src/control/build.ts\n/**\n* The extension's control entry (ADR-0017): `nodeBuild()` returns the build\n* descriptor `prisma-composer.config.ts` lists. Deploy-only (ADR-0005): the user\n* builds their own runnable; `assemble` copies what they built under `bundle/`\n* and adds the framework's boot wrapper — it never bundles or transforms the\n* app's code.\n*\n* Two forms, chosen by the descriptor: without `dir`, `entry` is a single\n* self-contained file and only that file is copied. With `dir`, the whole\n* directory is copied verbatim and `entry` names the file inside it that boots.\n* Neither form discovers anything — no tree-walking for an entry, no filename\n* heuristics; the author states the paths and we copy exactly those.\n*\n* The wrapper is a SEPARATE esbuild build of the service module (declarations\n* only, whose node carries run()/load()), emitted as `main.mjs` at the\n* working-dir root — a dictated name (object entry `{ main }`), not a\n* discovered one. run() and the app entry must be independent module instances\n* that hand off through process.env, so the wrapper is its own self-contained\n* build; `@prisma/*` is inlined, `bun` is a Compute built-in.\n*\n* Artifact layout: `<cwd>/.prisma-composer/artifacts/<address>/` (deploy-owned,\n* ADR-0005) holds `main.mjs` at the root and the app's built runnable under\n* `bundle/`.\n*/\n/** Narrows the shared BuildAdapter to this extension's own descriptor — the value-level mirror of the registry routing on (extension, type). `dir` is optional: absent is the single-file form. */\nfunction isNodeBuild(descriptor) {\n\treturn descriptor.type === \"node\" && (!(\"dir\" in descriptor) || typeof descriptor.dir === \"string\");\n}\n/** The single-file form: `entry` is the whole built runnable, resolved against dirname(module) (ADR-0004). */\nfunction resolveFile(entrySpec, moduleDir) {\n\tconst entryPath = path.resolve(moduleDir, entrySpec);\n\tif (!fs.existsSync(entryPath)) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, \"${entrySpec}\", resolves against dirname(module)).`);\n\tconst entryFile = path.basename(entryPath);\n\treturn {\n\t\tsource: entryPath,\n\t\tsourceField: \"entry\",\n\t\tentry: entryFile,\n\t\tcopyInto: async (bundleDir) => {\n\t\t\tawait fs.promises.mkdir(bundleDir, { recursive: true });\n\t\t\tawait fs.promises.copyFile(entryPath, path.join(bundleDir, entryFile));\n\t\t}\n\t};\n}\n/** The shared \"dir contains symlinks\" error, reused by both the root-is-a-symlink case and the nested-symlink walk below — one message shape, never two to drift apart. */\nfunction symlinksFoundError(dirPath, found) {\n\tconst listed = found.slice(0, 5).join(\", \");\n\treturn /* @__PURE__ */ new Error(`the build adapter's dir (\"${dirPath}\") contains symlinks, which the platform's packager rejects: ${listed}${found.length > 5 ? `, and ${found.length - 5} more` : \"\"}. The tree is copied verbatim, so make your build emit real files in dir (for example, a hoisted node_modules, or dereference the links into dir with cp -RL).`);\n}\n/**\n* Compute's packager rejects symlinks, so a tree containing one cannot deploy.\n* We fail here, naming the links, rather than dereferencing them on the copy:\n* the artifact must be what the author's build produced (ADR-0005), and\n* following a link that points outside `dir` would pull in files the author\n* never named. The walk reads dirents (lstat semantics), so a symlinked\n* directory is reported and never descended into. Checks only `dirPath`'s\n* children — the caller (`resolveDir`) checks `dirPath` itself before this\n* runs, since that check also decides \"not a directory\" vs \"is a symlink\"\n* and must happen before any dereferencing stat.\n*/\nasync function assertNoSymlinks(dirPath) {\n\tconst found = [];\n\tconst walk = async (current) => {\n\t\tfor (const entry of await fs.promises.readdir(current, { withFileTypes: true })) {\n\t\t\tconst full = path.join(current, entry.name);\n\t\t\tif (entry.isSymbolicLink()) found.push(full);\n\t\t\telse if (entry.isDirectory()) await walk(full);\n\t\t}\n\t};\n\tawait walk(dirPath);\n\tif (found.length > 0) throw symlinksFoundError(dirPath, found);\n}\n/**\n* The directory form: `dir` is the built tree, resolved against dirname(module)\n* (ADR-0004) and copied whole; `entry` resolves inside `dir` and names the file\n* that boots. An `entry` that resolves outside `dir` is rejected rather than\n* followed — only `dir` is ever copied.\n*\n* `dir` itself can be a symlink — not just something inside it. `lstat` it\n* before anything else so that case hard-errors identically whether the link\n* points at a real directory (which a dereferencing `stat` would otherwise\n* accept as \"is a directory\") or at a file: neither is ever silently\n* dereferenced and copied (ADR-0005).\n*/\nasync function resolveDir(dirSpec, entrySpec, moduleDir) {\n\tconst dirPath = path.resolve(moduleDir, dirSpec);\n\tlet dirLstat;\n\ttry {\n\t\tdirLstat = await fs.promises.lstat(dirPath);\n\t} catch {\n\t\tthrow new Error(`no built directory at ${dirPath} — run your build first (the build adapter's dir, \"${dirSpec}\", resolves against dirname(module)).`);\n\t}\n\tif (dirLstat.isSymbolicLink()) throw symlinksFoundError(dirPath, [dirPath]);\n\tif (!dirLstat.isDirectory()) throw new Error(`the build adapter's dir (\"${dirPath}\") is not a directory — drop dir to deploy a single built file, naming it as entry.`);\n\tconst entryPath = path.resolve(dirPath, entrySpec);\n\tif (!entryPath.startsWith(dirPath + path.sep)) throw new Error(`the build adapter's entry (\"${entrySpec}\") resolves to ${entryPath}, which is not inside dir (\"${dirPath}\") — in the directory form entry names a file inside dir, and only dir is copied.`);\n\tif (!fs.existsSync(entryPath) || !fs.statSync(entryPath).isFile()) throw new Error(`no built entry at ${entryPath} — run your build first (the build adapter's entry, \"${entrySpec}\", resolves inside dir, \"${dirPath}\").`);\n\tawait assertNoSymlinks(dirPath);\n\treturn {\n\t\tsource: dirPath,\n\t\tsourceField: \"dir\",\n\t\tentry: path.relative(dirPath, entryPath).split(path.sep).join(\"/\"),\n\t\tcopyInto: (bundleDir) => fs.promises.cp(dirPath, bundleDir, { recursive: true })\n\t};\n}\n/**\n* The working dir is cleared on every assemble, so it must not overlap the copy\n* source: inside it, the rm would delete the source before the copy; the other\n* way round, the copy would recurse into its own output.\n*/\nfunction assertOutsideWorkDir(runnable, workDir) {\n\tconst { source, sourceField } = runnable;\n\tif (source === workDir || source.startsWith(workDir + path.sep)) throw new Error(`the build adapter's ${sourceField} (\"${source}\") resolves inside the deploy working dir (\"${workDir}\"), which is cleared on every assemble — point ${sourceField} at your build output elsewhere.`);\n\tif (workDir.startsWith(source + path.sep)) throw new Error(`the deploy working dir (\"${workDir}\") sits inside the build adapter's ${sourceField} (\"${source}\"), so assembling would copy the artifact into itself — point ${sourceField} at your build output elsewhere.`);\n}\nasync function assemble(input) {\n\tif (!isNodeBuild(input.build)) throw new Error(`@prisma/composer/node/control: expected a \"node\" build adapter, got \"${input.build.type}\".`);\n\tconst buildDescriptor = input.build;\n\tconst serviceModule = fileURLToPath(buildDescriptor.module);\n\tconst moduleDir = path.dirname(serviceModule);\n\tconst runnable = buildDescriptor.dir === void 0 ? resolveFile(buildDescriptor.entry, moduleDir) : await resolveDir(buildDescriptor.dir, buildDescriptor.entry, moduleDir);\n\tconst workDir = path.join(input.cwd, \".prisma-composer\", \"artifacts\", input.address);\n\tassertOutsideWorkDir(runnable, workDir);\n\tawait fs.promises.rm(workDir, {\n\t\trecursive: true,\n\t\tforce: true\n\t});\n\tawait fs.promises.mkdir(workDir, { recursive: true });\n\tawait build({\n\t\tentryPoints: { main: serviceModule },\n\t\toutdir: workDir,\n\t\tbundle: true,\n\t\tformat: \"esm\",\n\t\tplatform: \"node\",\n\t\texternal: [\"bun\", \"bun:*\"],\n\t\toutExtension: { \".js\": \".mjs\" }\n\t});\n\tif (!fs.existsSync(path.join(workDir, \"main.mjs\"))) throw new Error(`esbuild produced no main.mjs in ${workDir}`);\n\tawait runnable.copyInto(path.join(workDir, \"bundle\"));\n\treturn {\n\t\tdir: workDir,\n\t\tentry: path.posix.join(\"bundle\", runnable.entry),\n\t\twatch: [runnable.source]\n\t};\n}\n/** The node build extension descriptor — `prisma-composer.config.ts` lists it under `extensions`. */\nconst nodeBuild = () => ({\n\tid: \"@prisma/composer/node\",\n\tnodes: { node: {\n\t\tkind: \"build\",\n\t\tassemble\n\t} }\n});\n//#endregion\nexport { assemble, nodeBuild };\n\n//# sourceMappingURL=control.mjs.map"],"mappings":"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"}
|
package/dist/node.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as BuildAdapter } from "./graph-types-
|
|
2
|
-
import "./index-
|
|
1
|
+
import { t as BuildAdapter } from "./graph-types-C3FOaaXm-C6_rGzfn.mjs";
|
|
2
|
+
import "./index-BQVrMGfe.mjs";
|
|
3
3
|
//#region ../../0-framework/2-authoring/node/dist/index.d.mts
|
|
4
4
|
//#region src/node.d.ts
|
|
5
5
|
/** The node build adapter's descriptor. `dir` is the directory form's own extra path input (the built tree to copy verbatim), beyond the shared `{ extension, type, module, entry }`; absent, `entry` is the whole built runnable. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.mts","names":[],"sources":["../../../0-framework/2-authoring/node/dist/index.d.mts"],"mappings":";;;;;UAGU,yBAAyB;WACxB;WACA;;;KAGN;EACH;EACA;EACA;;EAEA;EACA;EACA;;cAEY,YAAY,MAAM,qBAAqB"}
|
package/dist/report.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./deploy-
|
|
1
|
+
import { d as DeploymentResult } from "./app-config-bsTo57A8-BfG21wAI.mjs";
|
|
2
|
+
import "./deploy-DUzvB5cQ.mjs";
|
|
3
3
|
//#region ../../0-framework/3-tooling/cli/dist/report.d.mts
|
|
4
4
|
//#region src/render-deployment.d.ts
|
|
5
5
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.mts","names":[],"sources":["../../../0-framework/3-tooling/cli/dist/report.d.mts"],"mappings":";;;;;;;;iBAMiB,iBAAiB,QAAQ;;;;;iBAKzB,iBAAiB,QAAQ"}
|
package/dist/report.mjs
CHANGED
|
@@ -61,6 +61,10 @@ function renderDeployment(result) {
|
|
|
61
61
|
const prefix = index === 0 ? row.label : row.continuation;
|
|
62
62
|
lines.push(`${pad(prefix, column)}${entityLine(entity)}`);
|
|
63
63
|
if (entity.url !== void 0) lines.push(`${pad(row.continuation, column)}${entity.url}`);
|
|
64
|
+
for (const [key, value] of Object.entries(entity.details ?? {})) {
|
|
65
|
+
if (value === "") continue;
|
|
66
|
+
for (const line of value.split("\n")) lines.push(`${pad(row.continuation, column)}${key} ${line}`);
|
|
67
|
+
}
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
return lines.join("\n");
|
package/dist/report.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.mjs","names":[],"sources":["../../../0-framework/3-tooling/cli/dist/report.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=report.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;
|
|
1
|
+
{"version":3,"file":"report.mjs","names":[],"sources":["../../../0-framework/3-tooling/cli/dist/report.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\tfor (const [key, value] of Object.entries(entity.details ?? {})) {\n\t\t\t\tif (value === \"\") continue;\n\t\t\t\tfor (const line of value.split(\"\\n\")) lines.push(`${pad(row.continuation, column)}${key} ${line}`);\n\t\t\t}\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=report.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;GACrF,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,GAAG;IAChE,IAAI,UAAU,IAAI;IAClB,KAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,GAAG,MAAM,KAAK,GAAG,IAAI,IAAI,cAAc,MAAM,IAAI,IAAI,GAAG,MAAM;GAClG;EACD,CAAC;CACF;CACA,OAAO,MAAM,KAAK,IAAI;AACvB;;;;;AAKA,SAAS,iBAAiB,QAAQ;CACjC,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,iBAAiB,MAAM,CAAC;AACrC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region ../../0-framework/0-foundation/foundation/dist/secret-CHOw4LBU.d.mts
|
|
2
|
+
//#region src/secret.d.ts
|
|
3
|
+
declare const SECRET_BOX: unique symbol;
|
|
4
|
+
declare class SecretBox<T> {
|
|
5
|
+
#private;
|
|
6
|
+
readonly [SECRET_BOX] = true;
|
|
7
|
+
constructor(value: T);
|
|
8
|
+
/** The sole explicit door to the wrapped value. */
|
|
9
|
+
expose(): T;
|
|
10
|
+
toString(): string;
|
|
11
|
+
toJSON(): string;
|
|
12
|
+
valueOf(): string;
|
|
13
|
+
[Symbol.toPrimitive](): string;
|
|
14
|
+
}
|
|
15
|
+
/** The common case: a secret string. */
|
|
16
|
+
type SecretString = SecretBox<string>;
|
|
17
|
+
/**
|
|
18
|
+
* True for a redacting secret box — what a schema's secret leaf checks
|
|
19
|
+
* (ADR-0042). Reads the brand rather than `instanceof`, so a box from a
|
|
20
|
+
* duplicated module copy in a bundle counts and a look-alike does not.
|
|
21
|
+
*/
|
|
22
|
+
declare function isSecretString(value: unknown): value is SecretString;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SecretString as n, isSecretString as r, SecretBox as t };
|
|
25
|
+
//# sourceMappingURL=secret-CHOw4LBU-C8yXoC9N.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-CHOw4LBU-C8yXoC9N.d.mts","names":[],"sources":["../../../0-framework/0-foundation/foundation/dist/secret-CHOw4LBU.d.mts"],"mappings":";;cACc;cACA,UAAU;;YAEZ;EACV,YAAY,OAAO;;EAEnB,UAAU;EACV;EACA;EACA;GACC,OAAO;;;KAGL,eAAe;;;;;;iBAMH,eAAe,iBAAiB,SAAS"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { t as blindCast } from "./casts-DpaahrlC-C5XMnP9S.mjs";
|
|
2
|
+
//#region ../../0-framework/0-foundation/foundation/dist/secret-DoWGmN7m.mjs
|
|
3
|
+
/**
|
|
4
|
+
* A value wrapper that redacts everywhere except the one explicit reader,
|
|
5
|
+
* `expose()`. Sensitivity is carried by the TYPE (`SecretBox<T>`), not a flag a
|
|
6
|
+
* sink must remember to check: `String(box)`, template interpolation,
|
|
7
|
+
* `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so
|
|
8
|
+
* a secret can't leak through an accidental log or serialization.
|
|
9
|
+
*
|
|
10
|
+
* Every box carries a brand property keyed by the registered symbol
|
|
11
|
+
* `Symbol.for('prisma:secret-box')`, which is what `isSecretString` checks. A
|
|
12
|
+
* symbol key is invisible to `JSON.stringify` and `Object.keys`, so the box's
|
|
13
|
+
* public shape still matches the platform's own `secrecy` type
|
|
14
|
+
* (pdp-control-plane).
|
|
15
|
+
*/
|
|
16
|
+
const REDACTED = "[REDACTED]";
|
|
17
|
+
const SECRET_BOX = blindCast(Symbol.for("prisma:secret-box"));
|
|
18
|
+
var SecretBox = class {
|
|
19
|
+
[SECRET_BOX] = true;
|
|
20
|
+
#value;
|
|
21
|
+
constructor(value) {
|
|
22
|
+
this.#value = value;
|
|
23
|
+
}
|
|
24
|
+
/** The sole explicit door to the wrapped value. */
|
|
25
|
+
expose() {
|
|
26
|
+
return this.#value;
|
|
27
|
+
}
|
|
28
|
+
toString() {
|
|
29
|
+
return REDACTED;
|
|
30
|
+
}
|
|
31
|
+
toJSON() {
|
|
32
|
+
return REDACTED;
|
|
33
|
+
}
|
|
34
|
+
valueOf() {
|
|
35
|
+
return REDACTED;
|
|
36
|
+
}
|
|
37
|
+
[Symbol.toPrimitive]() {
|
|
38
|
+
return REDACTED;
|
|
39
|
+
}
|
|
40
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
41
|
+
return REDACTED;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* True for a redacting secret box — what a schema's secret leaf checks
|
|
46
|
+
* (ADR-0042). Reads the brand rather than `instanceof`, so a box from a
|
|
47
|
+
* duplicated module copy in a bundle counts and a look-alike does not.
|
|
48
|
+
*/
|
|
49
|
+
function isSecretString(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && blindCast(value)[SECRET_BOX] === true;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { isSecretString as n, SecretBox as t };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=secret-DoWGmN7m-DO5Brq1Y.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-DoWGmN7m-DO5Brq1Y.mjs","names":["#value"],"sources":["../../../0-framework/0-foundation/foundation/dist/secret-DoWGmN7m.mjs"],"sourcesContent":["import { t as blindCast } from \"./casts-DpaahrlC.mjs\";\n//#region src/secret.ts\n/**\n* A value wrapper that redacts everywhere except the one explicit reader,\n* `expose()`. Sensitivity is carried by the TYPE (`SecretBox<T>`), not a flag a\n* sink must remember to check: `String(box)`, template interpolation,\n* `JSON.stringify`, and `console.log`/`util.inspect` all print `[REDACTED]`, so\n* a secret can't leak through an accidental log or serialization.\n*\n* Every box carries a brand property keyed by the registered symbol\n* `Symbol.for('prisma:secret-box')`, which is what `isSecretString` checks. A\n* symbol key is invisible to `JSON.stringify` and `Object.keys`, so the box's\n* public shape still matches the platform's own `secrecy` type\n* (pdp-control-plane).\n*/\nconst REDACTED = \"[REDACTED]\";\nconst SECRET_BOX = blindCast(Symbol.for(\"prisma:secret-box\"));\nvar SecretBox = class {\n\t[SECRET_BOX] = true;\n\t#value;\n\tconstructor(value) {\n\t\tthis.#value = value;\n\t}\n\t/** The sole explicit door to the wrapped value. */\n\texpose() {\n\t\treturn this.#value;\n\t}\n\ttoString() {\n\t\treturn REDACTED;\n\t}\n\ttoJSON() {\n\t\treturn REDACTED;\n\t}\n\tvalueOf() {\n\t\treturn REDACTED;\n\t}\n\t[Symbol.toPrimitive]() {\n\t\treturn REDACTED;\n\t}\n\t[Symbol.for(\"nodejs.util.inspect.custom\")]() {\n\t\treturn REDACTED;\n\t}\n};\n/**\n* True for a redacting secret box — what a schema's secret leaf checks\n* (ADR-0042). Reads the brand rather than `instanceof`, so a box from a\n* duplicated module copy in a bundle counts and a look-alike does not.\n*/\nfunction isSecretString(value) {\n\treturn typeof value === \"object\" && value !== null && blindCast(value)[SECRET_BOX] === true;\n}\n//#endregion\nexport { isSecretString as n, SecretBox as t };\n\n//# sourceMappingURL=secret-DoWGmN7m.mjs.map"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,WAAW;AACjB,MAAM,aAAa,UAAU,OAAO,IAAI,mBAAmB,CAAC;AAC5D,IAAI,YAAY,MAAM;CACrB,CAAC,cAAc;CACf;CACA,YAAY,OAAO;EAClB,KAAKA,SAAS;CACf;;CAEA,SAAS;EACR,OAAO,KAAKA;CACb;CACA,WAAW;EACV,OAAO;CACR;CACA,SAAS;EACR,OAAO;CACR;CACA,UAAU;EACT,OAAO;CACR;CACA,CAAC,OAAO,eAAe;EACtB,OAAO;CACR;CACA,CAAC,OAAO,IAAI,4BAA4B,KAAK;EAC5C,OAAO;CACR;AACD;;;;;;AAMA,SAAS,eAAe,OAAO;CAC9B,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,KAAK,CAAC,CAAC,gBAAgB;AACxF"}
|
package/dist/service-rpc.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./index-
|
|
1
|
+
import { L as RunnableServiceNode, M as ProvisionNeed, o as Contract, s as DependencyEnd } from "./graph-types-C3FOaaXm-C6_rGzfn.mjs";
|
|
2
|
+
import "./index-BQVrMGfe.mjs";
|
|
3
3
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
4
|
//#region ../../0-framework/2-authoring/service-rpc/dist/index.d.mts
|
|
5
5
|
//#region src/rpc.d.ts
|
|
@@ -14,6 +14,8 @@ declare function rpc<I extends StandardSchemaV1, O extends StandardSchemaV1>(m:
|
|
|
14
14
|
output: O;
|
|
15
15
|
}): (input: StandardSchemaV1.InferInput<I>) => Promise<StandardSchemaV1.InferOutput<O>>;
|
|
16
16
|
declare function rpc<C extends Contract<'rpc', RpcFns>>(contract: C): DependencyEnd<Client<C>, C>;
|
|
17
|
+
/** True iff `value` is an RPC-kind Contract (a function-map `__cmp`, dispatchable by serve()). */
|
|
18
|
+
declare function isRpcContract(value: unknown): value is Contract<'rpc', RpcFns>;
|
|
17
19
|
/** The typed client a consumer's `rpc(contract)` dependency hydrates to. */
|
|
18
20
|
type Client<C> = C extends Contract<string, infer Cmp> ? Cmp : never;
|
|
19
21
|
//#endregion
|
|
@@ -43,8 +45,16 @@ interface RpcHandlerContext {
|
|
|
43
45
|
readonly idempotencyKey: string | undefined;
|
|
44
46
|
}
|
|
45
47
|
type HandlerFor<Fn, LoadedDeps> = Fn extends ((input: infer I) => Promise<infer O>) ? (input: I, deps: LoadedDeps, ctx: RpcHandlerContext) => Promise<O> : never;
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The keys of `E` whose contract is an RPC contract. A service may expose
|
|
50
|
+
* non-rpc ports beside its rpc ones (e.g. a public HTTP surface whose
|
|
51
|
+
* contract carries connection config, not methods) — serve() has nothing to
|
|
52
|
+
* dispatch for those, so `Handlers<S>` must not demand a handler map for
|
|
53
|
+
* them. The runtime mirror is `methodTable`'s kind check.
|
|
54
|
+
*/
|
|
55
|
+
type RpcPortKeys<E> = { [P in keyof E]: E[P] extends Contract<'rpc', any> ? P : never; }[keyof E];
|
|
56
|
+
/** Every exposed RPC port's methods, turned into a handler map typed off S's own `expose` and `load()`. Non-rpc exposed ports are skipped. */
|
|
57
|
+
type Handlers<S extends AnyRunnable> = { [Port in RpcPortKeys<NonNullable<S['expose']>>]: { [M in keyof CmpOf<NonNullable<S['expose']>[Port]>]: HandlerFor<CmpOf<NonNullable<S['expose']>[Port]>[M], ReturnType<S['load']>>; }; };
|
|
48
58
|
/**
|
|
49
59
|
* Routes `POST /rpc/<method>`: checks the service key, requires an
|
|
50
60
|
* Idempotency-Key, single-flights/replays through `IdempotencyStore`, and —
|
|
@@ -57,5 +67,5 @@ type Handlers<S extends AnyRunnable> = { [Port in keyof NonNullable<S['expose']>
|
|
|
57
67
|
*/
|
|
58
68
|
declare function serve<S extends AnyRunnable, H extends Handlers<S>>(service: S, handlers: H): (req: Request) => Promise<Response>;
|
|
59
69
|
//#endregion
|
|
60
|
-
export { type Client, type Handlers, RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, type RpcHandlerContext, type Transport, contract, makeClient, perBindingToken, rpc, serve };
|
|
70
|
+
export { type Client, type Handlers, RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, type RpcHandlerContext, type Transport, contract, isRpcContract, makeClient, perBindingToken, rpc, serve };
|
|
61
71
|
//# sourceMappingURL=service-rpc.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-rpc.d.mts","names":[],"sources":["../../../0-framework/2-authoring/service-rpc/dist/index.d.mts"],"mappings":";;;;;;cAIc;;cAEA,uBAAuB;;KAEhC,SAAS,gBAAgB,eAAe;iBAC5B,IAAI,UAAU,kBAAkB,UAAU,kBAAkB;EAC3E,OAAO;EACP,QAAQ;KACL,OAAO,iBAAiB,WAAW,OAAO,QAAQ,iBAAiB,YAAY;iBACnE,IAAI,UAAU,gBAAgB,SAAS,UAAU,IAAI,cAAc,OAAO,IAAI;;iBAE9E,cAAc,iBAAiB,SAAS,gBAAgB;;KAEpE,OAAO,KAAK,UAAU,uBAAuB,OAAO;;;;;;;;KAQpD,aAAa,KAAK,YAAY,QAAQ;iBAC1B,WAAW,UAAU,gBAAgB,SAAS,UAAU,GAAG,aAAa;EACvF,QAAQ;EACR;IACE,OAAO;;;iBAGM,SAAS,YAAY,gBAAgB,eAAe,eAAe,KAAK,MAAM,gBAAgB;;;;cAIjG;KACT,cAAc;KACd,MAAM,KAAK,UAAU,uBAAuB,OAAO;;UAE9C;;WAEC;;KAEN,WAAW,IAAI,cAAc,aAAa,aAAa,MAAM,cAAc,OAAO,OAAO,GAAG,MAAM,YAAY,KAAK,sBAAsB,QAAQ;;;;;;;;KAQjJ,YAAY,QAAQ,WAAW,IAAI,EAAE,WAAW,uBAAuB,mBAAmB;;KAE1F,SAAS,UAAU,kBAAkB,QAAQ,YAAY,YAAY,mBAAmB,WAAW,MAAM,YAAY,aAAa,SAAS,WAAW,MAAM,YAAY,aAAa,OAAO,IAAI,WAAW;;;;;;;;;;;iBAW/L,MAAM,UAAU,aAAa,UAAU,SAAS,IAAI,SAAS,GAAG,UAAU,KAAK,KAAK,YAAY,QAAQ"}
|
package/dist/service-rpc.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as blindCast } from "./casts-
|
|
2
|
-
import { i as dependency, p as provisionNeed } from "./graph-
|
|
3
|
-
import {
|
|
1
|
+
import { t as blindCast } from "./casts-DpaahrlC-C5XMnP9S.mjs";
|
|
2
|
+
import { i as dependency, p as provisionNeed } from "./graph-DgbALPDo-BEHF94BN.mjs";
|
|
3
|
+
import { c as string } from "./dist-BdvFDCLV.mjs";
|
|
4
4
|
//#region ../../0-framework/2-authoring/service-rpc/dist/index.mjs
|
|
5
5
|
/** Bounded jittered backoff for retrying a dropped call. `maxRetries` is retries after the first attempt. */
|
|
6
6
|
const RETRY = {
|
|
@@ -108,6 +108,7 @@ function rpc(arg) {
|
|
|
108
108
|
required: arg
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
|
+
/** True iff `value` is an RPC-kind Contract (a function-map `__cmp`, dispatchable by serve()). */
|
|
111
112
|
function isRpcContract(value) {
|
|
112
113
|
return typeof value === "object" && value !== null && "kind" in value && value.kind === "rpc" && "__cmp" in value && "satisfies" in value;
|
|
113
114
|
}
|
|
@@ -268,6 +269,7 @@ const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
|
268
269
|
function methodTable(expose, handlers) {
|
|
269
270
|
const table = /* @__PURE__ */ new Map();
|
|
270
271
|
for (const [port, contract] of Object.entries(expose)) {
|
|
272
|
+
if (!isRpcContract(contract)) continue;
|
|
271
273
|
const portHandlers = handlers[port] ?? {};
|
|
272
274
|
for (const [method, fn] of Object.entries(contract.__cmp)) {
|
|
273
275
|
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.`);
|
|
@@ -348,6 +350,6 @@ function serve(service, handlers) {
|
|
|
348
350
|
};
|
|
349
351
|
}
|
|
350
352
|
//#endregion
|
|
351
|
-
export { RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, contract, makeClient, perBindingToken, rpc, serve };
|
|
353
|
+
export { RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, contract, isRpcContract, makeClient, perBindingToken, rpc, serve };
|
|
352
354
|
|
|
353
355
|
//# sourceMappingURL=service-rpc.mjs.map
|
package/dist/service-rpc.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-rpc.mjs","names":[],"sources":["../../../0-framework/2-authoring/service-rpc/dist/index.mjs"],"sourcesContent":["import { blindCast } from \"@internal/foundation/casts\";\nimport { dependency, provisionNeed, string } from \"@internal/core\";\n//#region src/client.ts\n/** Bounded jittered backoff for retrying a dropped call. `maxRetries` is retries after the first attempt. */\nconst RETRY = {\n\tinitialDelayMs: 250,\n\tmultiplier: 2,\n\tmaxDelayMs: 5e3,\n\tmaxRetries: 5\n};\nconst IDEMPOTENCY_KEY_HEADER$1 = \"Idempotency-Key\";\nfunction sleep(ms) {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n/** Whether a non-OK response is safe to retry: 429 or any 5xx, never another 4xx. */\nfunction isRetryableStatus(status) {\n\treturn status === 429 || status >= 500;\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}\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/**\n* Sends one call over `send`, retrying a thrown error, 429, or 5xx with\n* full-jitter backoff. `buildRequest` runs per attempt but carries the same\n* idempotency key each time — only the transport call repeats, not the key.\n*/\nasync function callWithRetry(send, buildRequest, method) {\n\tlet delay = RETRY.initialDelayMs;\n\tlet retries = 0;\n\tfor (;;) {\n\t\tlet res;\n\t\ttry {\n\t\t\tres = await send(buildRequest());\n\t\t} catch (err) {\n\t\t\tif (retries >= RETRY.maxRetries) throw err;\n\t\t\tretries += 1;\n\t\t\tawait sleep(Math.random() * delay);\n\t\t\tdelay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);\n\t\t\tcontinue;\n\t\t}\n\t\tif (res.ok) return res.json();\n\t\tif (!isRetryableStatus(res.status) || retries >= RETRY.maxRetries) {\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\tretries += 1;\n\t\tawait sleep(Math.random() * delay);\n\t\tdelay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);\n\t}\n}\nfunction makeClient(contract, url, opts) {\n\tconst send = opts?.fetch ?? fetch;\n\tconst baseHeaders = { \"content-type\": \"application/json\" };\n\tif (opts?.serviceKey !== void 0) baseHeaders[\"Authorization\"] = `Bearer ${opts.serviceKey}`;\n\tconst client = {};\n\tfor (const method of Object.keys(contract.__cmp)) client[method] = async (input) => {\n\t\tconst idempotencyKey = crypto.randomUUID();\n\t\tconst body = JSON.stringify(input);\n\t\treturn callWithRetry(send, () => new Request(methodUrl(url, method), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...baseHeaders,\n\t\t\t\t[IDEMPOTENCY_KEY_HEADER$1]: idempotencyKey\n\t\t\t},\n\t\t\tbody\n\t\t}), method);\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/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/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 outcome(body, status = 200) {\n\treturn {\n\t\tstatus,\n\t\tbodyText: JSON.stringify(body)\n\t};\n}\nfunction toResponse(o) {\n\treturn new Response(o.bodyText, {\n\t\tstatus: o.status,\n\t\theaders: { \"content-type\": \"application/json\" }\n\t});\n}\n/** The generic message every caller-facing 500 carries — the real error goes to `console.error` instead. */\nconst INTERNAL_ERROR_MESSAGE = \"Internal server error\";\n/** Request body cap. Internal RPC payloads are small records, not uploads; 1 MiB bounds a slow request's memory. */\nconst MAX_BODY_BYTES = 1048576;\nvar RequestBodyTooLargeError = class extends Error {};\n/** Reads the body as text, aborting past `maxBytes` of bytes actually read — `content-length` is caller-supplied, so untrusted. */\nasync function readBoundedBody(req, maxBytes) {\n\tconst reader = req.body?.getReader();\n\tif (reader === void 0) return \"\";\n\tconst decoder = new TextDecoder();\n\tlet text = \"\";\n\tlet total = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\ttotal += value.byteLength;\n\t\tif (total > maxBytes) {\n\t\t\tawait reader.cancel();\n\t\t\tthrow new RequestBodyTooLargeError();\n\t\t}\n\t\ttext += decoder.decode(value, { stream: true });\n\t}\n\ttext += decoder.decode();\n\treturn text;\n}\n/** How long a completed 2xx/4xx answer stays replayable for a repeated key. */\nconst REPLAY_TTL_MS = 6e4;\n/**\n* Per-method, per-key deduplication. A duplicate arriving mid-execution\n* single-flights onto the same promise; a completed 2xx/4xx replays for\n* REPLAY_TTL_MS; a 5xx is never kept, since that is what a retry re-executes.\n* Keyed by method first, so a replay can never answer a different method.\n*/\nvar IdempotencyStore = class {\n\tbyMethod = /* @__PURE__ */ new Map();\n\tlru = /* @__PURE__ */ new Set();\n\tasync dispatch(method, key, run) {\n\t\tconst bucket = this.bucketFor(method);\n\t\tconst existing = bucket.get(key);\n\t\tif (existing?.kind === \"pending\") return existing.promise;\n\t\tif (existing?.kind === \"completed\") {\n\t\t\tif (Date.now() - existing.completedAt < REPLAY_TTL_MS) {\n\t\t\t\tthis.lru.delete(existing);\n\t\t\t\tthis.lru.add(existing);\n\t\t\t\treturn existing.outcome;\n\t\t\t}\n\t\t\tbucket.delete(key);\n\t\t\tthis.lru.delete(existing);\n\t\t}\n\t\tconst promise = run();\n\t\tbucket.set(key, {\n\t\t\tkind: \"pending\",\n\t\t\tpromise\n\t\t});\n\t\tlet result;\n\t\ttry {\n\t\t\tresult = await promise;\n\t\t} catch (err) {\n\t\t\tbucket.delete(key);\n\t\t\tthrow err;\n\t\t}\n\t\tif (result.status >= 500) bucket.delete(key);\n\t\telse {\n\t\t\tconst entry = {\n\t\t\t\tkind: \"completed\",\n\t\t\t\toutcome: result,\n\t\t\t\tcompletedAt: Date.now(),\n\t\t\t\tmethod,\n\t\t\t\tkey\n\t\t\t};\n\t\t\tbucket.set(key, entry);\n\t\t\tthis.lru.add(entry);\n\t\t\tthis.evictOverflow();\n\t\t}\n\t\treturn result;\n\t}\n\tbucketFor(method) {\n\t\tlet bucket = this.byMethod.get(method);\n\t\tif (bucket === void 0) {\n\t\t\tbucket = /* @__PURE__ */ new Map();\n\t\t\tthis.byMethod.set(method, bucket);\n\t\t}\n\t\treturn bucket;\n\t}\n\tevictOverflow() {\n\t\tif (this.lru.size <= 1e3) return;\n\t\tconst oldest = this.lru.values().next().value;\n\t\tif (oldest !== void 0) {\n\t\t\tthis.lru.delete(oldest);\n\t\t\tthis.byMethod.get(oldest.method)?.delete(oldest.key);\n\t\t}\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}\nconst IDEMPOTENCY_KEY_HEADER = \"Idempotency-Key\";\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>`: checks the service key, requires an\n* Idempotency-Key, single-flights/replays through `IdempotencyStore`, and —\n* per call — parses JSON within the body cap, validates input, calls the\n* handler with `service.load()`'s deps plus `{ idempotencyKey }`, validates\n* the output, and responds JSON. A handler or output-validation failure\n* masks its message behind a generic 500 and logs the real error; an\n* unknown method or invalid input is a 4xx. `load()` is called exactly\n* 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\tconst idempotency = new IdempotencyStore();\n\treturn async (req) => {\n\t\tconst accepted = acceptedKeys();\n\t\tif (accepted !== void 0 && !isAcceptedKey(bearerToken(req), accepted)) return toResponse(outcome({ 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 toResponse(outcome({ error: `Not found: ${pathname}` }, 404));\n\t\tconst method = table.get(methodName);\n\t\tif (method === void 0) return toResponse(outcome({ error: `Unknown RPC method \"${methodName}\"` }, 404));\n\t\tif (req.method !== \"POST\") return toResponse(outcome({ error: `Method \"${methodName}\" requires POST` }, 405));\n\t\tconst idempotencyKey = req.headers.get(IDEMPOTENCY_KEY_HEADER.toLowerCase()) || void 0;\n\t\tconst ctx = { idempotencyKey };\n\t\tconst run = async () => {\n\t\t\tlet bodyText;\n\t\t\ttry {\n\t\t\t\tbodyText = await readBoundedBody(req, MAX_BODY_BYTES);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof RequestBodyTooLargeError) return outcome({ error: `Request body exceeds the ${MAX_BODY_BYTES}-byte limit` }, 413);\n\t\t\t\tconsole.error(`serve(): reading the request body for \"${methodName}\" failed:`, err);\n\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t}\n\t\t\tlet body;\n\t\t\ttry {\n\t\t\t\tbody = JSON.parse(bodyText);\n\t\t\t} catch {\n\t\t\t\treturn outcome({ error: \"Request body must be JSON\" }, 400);\n\t\t\t}\n\t\t\tlet input;\n\t\t\ttry {\n\t\t\t\tinput = await standardValidate(method.input, body);\n\t\t\t} catch (err) {\n\t\t\t\treturn outcome({ error: err instanceof Error ? err.message : String(err) }, 400);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await method.handler(input, deps, ctx);\n\t\t\t\tlet output;\n\t\t\t\ttry {\n\t\t\t\t\toutput = await standardValidate(method.output, result);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(`serve(): handler for \"${methodName}\" returned output that failed schema validation — this is a provider bug:`, err);\n\t\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t\t}\n\t\t\t\treturn outcome(output);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`serve(): handler for \"${methodName}\" threw:`, err);\n\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t}\n\t\t};\n\t\treturn toResponse(idempotencyKey === void 0 ? await run() : await idempotency.dispatch(methodName, idempotencyKey, run));\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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"}
|
|
1
|
+
{"version":3,"file":"service-rpc.mjs","names":[],"sources":["../../../0-framework/2-authoring/service-rpc/dist/index.mjs"],"sourcesContent":["import { blindCast } from \"@internal/foundation/casts\";\nimport { dependency, provisionNeed, string } from \"@internal/core\";\n//#region src/client.ts\n/** Bounded jittered backoff for retrying a dropped call. `maxRetries` is retries after the first attempt. */\nconst RETRY = {\n\tinitialDelayMs: 250,\n\tmultiplier: 2,\n\tmaxDelayMs: 5e3,\n\tmaxRetries: 5\n};\nconst IDEMPOTENCY_KEY_HEADER$1 = \"Idempotency-Key\";\nfunction sleep(ms) {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n/** Whether a non-OK response is safe to retry: 429 or any 5xx, never another 4xx. */\nfunction isRetryableStatus(status) {\n\treturn status === 429 || status >= 500;\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}\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/**\n* Sends one call over `send`, retrying a thrown error, 429, or 5xx with\n* full-jitter backoff. `buildRequest` runs per attempt but carries the same\n* idempotency key each time — only the transport call repeats, not the key.\n*/\nasync function callWithRetry(send, buildRequest, method) {\n\tlet delay = RETRY.initialDelayMs;\n\tlet retries = 0;\n\tfor (;;) {\n\t\tlet res;\n\t\ttry {\n\t\t\tres = await send(buildRequest());\n\t\t} catch (err) {\n\t\t\tif (retries >= RETRY.maxRetries) throw err;\n\t\t\tretries += 1;\n\t\t\tawait sleep(Math.random() * delay);\n\t\t\tdelay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);\n\t\t\tcontinue;\n\t\t}\n\t\tif (res.ok) return res.json();\n\t\tif (!isRetryableStatus(res.status) || retries >= RETRY.maxRetries) {\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\tretries += 1;\n\t\tawait sleep(Math.random() * delay);\n\t\tdelay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);\n\t}\n}\nfunction makeClient(contract, url, opts) {\n\tconst send = opts?.fetch ?? fetch;\n\tconst baseHeaders = { \"content-type\": \"application/json\" };\n\tif (opts?.serviceKey !== void 0) baseHeaders[\"Authorization\"] = `Bearer ${opts.serviceKey}`;\n\tconst client = {};\n\tfor (const method of Object.keys(contract.__cmp)) client[method] = async (input) => {\n\t\tconst idempotencyKey = crypto.randomUUID();\n\t\tconst body = JSON.stringify(input);\n\t\treturn callWithRetry(send, () => new Request(methodUrl(url, method), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...baseHeaders,\n\t\t\t\t[IDEMPOTENCY_KEY_HEADER$1]: idempotencyKey\n\t\t\t},\n\t\t\tbody\n\t\t}), method);\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}\n/** True iff `value` is an RPC-kind Contract (a function-map `__cmp`, dispatchable by serve()). */\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/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/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 outcome(body, status = 200) {\n\treturn {\n\t\tstatus,\n\t\tbodyText: JSON.stringify(body)\n\t};\n}\nfunction toResponse(o) {\n\treturn new Response(o.bodyText, {\n\t\tstatus: o.status,\n\t\theaders: { \"content-type\": \"application/json\" }\n\t});\n}\n/** The generic message every caller-facing 500 carries — the real error goes to `console.error` instead. */\nconst INTERNAL_ERROR_MESSAGE = \"Internal server error\";\n/** Request body cap. Internal RPC payloads are small records, not uploads; 1 MiB bounds a slow request's memory. */\nconst MAX_BODY_BYTES = 1048576;\nvar RequestBodyTooLargeError = class extends Error {};\n/** Reads the body as text, aborting past `maxBytes` of bytes actually read — `content-length` is caller-supplied, so untrusted. */\nasync function readBoundedBody(req, maxBytes) {\n\tconst reader = req.body?.getReader();\n\tif (reader === void 0) return \"\";\n\tconst decoder = new TextDecoder();\n\tlet text = \"\";\n\tlet total = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\ttotal += value.byteLength;\n\t\tif (total > maxBytes) {\n\t\t\tawait reader.cancel();\n\t\t\tthrow new RequestBodyTooLargeError();\n\t\t}\n\t\ttext += decoder.decode(value, { stream: true });\n\t}\n\ttext += decoder.decode();\n\treturn text;\n}\n/** How long a completed 2xx/4xx answer stays replayable for a repeated key. */\nconst REPLAY_TTL_MS = 6e4;\n/**\n* Per-method, per-key deduplication. A duplicate arriving mid-execution\n* single-flights onto the same promise; a completed 2xx/4xx replays for\n* REPLAY_TTL_MS; a 5xx is never kept, since that is what a retry re-executes.\n* Keyed by method first, so a replay can never answer a different method.\n*/\nvar IdempotencyStore = class {\n\tbyMethod = /* @__PURE__ */ new Map();\n\tlru = /* @__PURE__ */ new Set();\n\tasync dispatch(method, key, run) {\n\t\tconst bucket = this.bucketFor(method);\n\t\tconst existing = bucket.get(key);\n\t\tif (existing?.kind === \"pending\") return existing.promise;\n\t\tif (existing?.kind === \"completed\") {\n\t\t\tif (Date.now() - existing.completedAt < REPLAY_TTL_MS) {\n\t\t\t\tthis.lru.delete(existing);\n\t\t\t\tthis.lru.add(existing);\n\t\t\t\treturn existing.outcome;\n\t\t\t}\n\t\t\tbucket.delete(key);\n\t\t\tthis.lru.delete(existing);\n\t\t}\n\t\tconst promise = run();\n\t\tbucket.set(key, {\n\t\t\tkind: \"pending\",\n\t\t\tpromise\n\t\t});\n\t\tlet result;\n\t\ttry {\n\t\t\tresult = await promise;\n\t\t} catch (err) {\n\t\t\tbucket.delete(key);\n\t\t\tthrow err;\n\t\t}\n\t\tif (result.status >= 500) bucket.delete(key);\n\t\telse {\n\t\t\tconst entry = {\n\t\t\t\tkind: \"completed\",\n\t\t\t\toutcome: result,\n\t\t\t\tcompletedAt: Date.now(),\n\t\t\t\tmethod,\n\t\t\t\tkey\n\t\t\t};\n\t\t\tbucket.set(key, entry);\n\t\t\tthis.lru.add(entry);\n\t\t\tthis.evictOverflow();\n\t\t}\n\t\treturn result;\n\t}\n\tbucketFor(method) {\n\t\tlet bucket = this.byMethod.get(method);\n\t\tif (bucket === void 0) {\n\t\t\tbucket = /* @__PURE__ */ new Map();\n\t\t\tthis.byMethod.set(method, bucket);\n\t\t}\n\t\treturn bucket;\n\t}\n\tevictOverflow() {\n\t\tif (this.lru.size <= 1e3) return;\n\t\tconst oldest = this.lru.values().next().value;\n\t\tif (oldest !== void 0) {\n\t\t\tthis.lru.delete(oldest);\n\t\t\tthis.byMethod.get(oldest.method)?.delete(oldest.key);\n\t\t}\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}\nconst IDEMPOTENCY_KEY_HEADER = \"Idempotency-Key\";\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\tif (!isRpcContract(contract)) continue;\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>`: checks the service key, requires an\n* Idempotency-Key, single-flights/replays through `IdempotencyStore`, and —\n* per call — parses JSON within the body cap, validates input, calls the\n* handler with `service.load()`'s deps plus `{ idempotencyKey }`, validates\n* the output, and responds JSON. A handler or output-validation failure\n* masks its message behind a generic 500 and logs the real error; an\n* unknown method or invalid input is a 4xx. `load()` is called exactly\n* 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\tconst idempotency = new IdempotencyStore();\n\treturn async (req) => {\n\t\tconst accepted = acceptedKeys();\n\t\tif (accepted !== void 0 && !isAcceptedKey(bearerToken(req), accepted)) return toResponse(outcome({ 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 toResponse(outcome({ error: `Not found: ${pathname}` }, 404));\n\t\tconst method = table.get(methodName);\n\t\tif (method === void 0) return toResponse(outcome({ error: `Unknown RPC method \"${methodName}\"` }, 404));\n\t\tif (req.method !== \"POST\") return toResponse(outcome({ error: `Method \"${methodName}\" requires POST` }, 405));\n\t\tconst idempotencyKey = req.headers.get(IDEMPOTENCY_KEY_HEADER.toLowerCase()) || void 0;\n\t\tconst ctx = { idempotencyKey };\n\t\tconst run = async () => {\n\t\t\tlet bodyText;\n\t\t\ttry {\n\t\t\t\tbodyText = await readBoundedBody(req, MAX_BODY_BYTES);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof RequestBodyTooLargeError) return outcome({ error: `Request body exceeds the ${MAX_BODY_BYTES}-byte limit` }, 413);\n\t\t\t\tconsole.error(`serve(): reading the request body for \"${methodName}\" failed:`, err);\n\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t}\n\t\t\tlet body;\n\t\t\ttry {\n\t\t\t\tbody = JSON.parse(bodyText);\n\t\t\t} catch {\n\t\t\t\treturn outcome({ error: \"Request body must be JSON\" }, 400);\n\t\t\t}\n\t\t\tlet input;\n\t\t\ttry {\n\t\t\t\tinput = await standardValidate(method.input, body);\n\t\t\t} catch (err) {\n\t\t\t\treturn outcome({ error: err instanceof Error ? err.message : String(err) }, 400);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await method.handler(input, deps, ctx);\n\t\t\t\tlet output;\n\t\t\t\ttry {\n\t\t\t\t\toutput = await standardValidate(method.output, result);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(`serve(): handler for \"${methodName}\" returned output that failed schema validation — this is a provider bug:`, err);\n\t\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t\t}\n\t\t\t\treturn outcome(output);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`serve(): handler for \"${methodName}\" threw:`, err);\n\t\t\t\treturn outcome({ error: INTERNAL_ERROR_MESSAGE }, 500);\n\t\t\t}\n\t\t};\n\t\treturn toResponse(idempotencyKey === void 0 ? await run() : await idempotency.dispatch(methodName, idempotencyKey, run));\n\t};\n}\n//#endregion\nexport { RPC_ACCEPTED_KEYS_ENV, RPC_PEER_KEY, contract, isRpcContract, makeClient, perBindingToken, rpc, serve };\n\n//# sourceMappingURL=index.mjs.map"],"mappings":"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"}
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as RunnableServiceNode, c as Deps, j as Params, m as HydratedDeps, u as Expose } from "./graph-types-C3FOaaXm-C6_rGzfn.mjs";
|
|
2
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
3
|
//#region ../../0-framework/1-core/core/dist/testing.d.mts
|
|
3
4
|
//#region src/testing.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* `mockService`'s override argument: every declared dependency, typed against
|
|
6
7
|
* 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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* for a resource dep) — a double of the wrong shape is a compile error — plus
|
|
9
|
+
* `input`, the already-validated input object, required exactly when the
|
|
10
|
+
* service declares an input schema (ADR-0042). A dependency literally named
|
|
11
|
+
* "input" shadows the input override — rename the dependency.
|
|
10
12
|
*/
|
|
11
|
-
type LoadOverrides<D extends Deps,
|
|
13
|
+
type LoadOverrides<D extends Deps, I extends StandardSchemaV1 | undefined> = HydratedDeps<D> & (I extends StandardSchemaV1 ? {
|
|
14
|
+
input: StandardSchemaV1.InferOutput<I>;
|
|
15
|
+
} : {
|
|
16
|
+
input?: never;
|
|
17
|
+
});
|
|
12
18
|
/**
|
|
13
19
|
* Returns a service node whose `load()` yields the dependency doubles and
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
20
|
+
* `input()` the given input object — everything else about the node (its
|
|
21
|
+
* deps, input schema, build, expose) is unchanged. The input double is NOT
|
|
22
|
+
* validated: the test supplies the typed, already-validated shape directly.
|
|
23
|
+
* `run()` is not meaningful on a mock (there is no boot, no environment) and
|
|
24
|
+
* throws if called.
|
|
19
25
|
*/
|
|
20
|
-
declare function mockService<D extends Deps, P extends Params, E extends Expose,
|
|
26
|
+
declare function mockService<D extends Deps, P extends Params, E extends Expose, I extends StandardSchemaV1 | undefined>(service: RunnableServiceNode<D, P, E, I>, overrides: LoadOverrides<D, I>): RunnableServiceNode<D, P, E, I>;
|
|
21
27
|
//#endregion
|
|
22
28
|
export { LoadOverrides, mockService };
|
|
23
29
|
//# sourceMappingURL=testing.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.mts","names":[],"sources":["../../../0-framework/1-core/core/dist/testing.d.mts"],"mappings":";;;;;;;;;;;;KAWK,cAAc,UAAU,MAAM,UAAU,gCAAgC,aAAa,MAAM,UAAU;EACxG,OAAO,iBAAiB,YAAY;;EAEpC;;;;;;;;;;iBAUe,YAAY,UAAU,MAAM,UAAU,QAAQ,UAAU,QAAQ,UAAU,8BAA8B,SAAS,oBAAoB,GAAG,GAAG,GAAG,IAAI,WAAW,cAAc,GAAG,KAAK,oBAAoB,GAAG,GAAG,GAAG"}
|
package/dist/testing.mjs
CHANGED
|
@@ -1,42 +1,34 @@
|
|
|
1
|
-
import { t as blindCast } from "./casts-
|
|
1
|
+
import { t as blindCast } from "./casts-DpaahrlC-C5XMnP9S.mjs";
|
|
2
2
|
//#region ../../0-framework/1-core/core/dist/testing.mjs
|
|
3
3
|
/**
|
|
4
4
|
* The unit-test seam (testing.md § Unit): `mockService` replaces a service
|
|
5
|
-
* node's `load()` and `
|
|
6
|
-
*
|
|
5
|
+
* node's `load()` and `input()` output so any code that pulls dependencies or
|
|
6
|
+
* input through them — a page, a server action, a helper — runs against typed
|
|
7
7
|
* doubles with no server and no environment. Target-agnostic: every service
|
|
8
|
-
* node has `load()`/`
|
|
8
|
+
* node has `load()`/`input()`. It does no module mocking; wiring the
|
|
9
9
|
* substitution into a test runner (`vi.mock`, `mock.module`) stays in the test.
|
|
10
10
|
* The integration seam (`bootstrapService`) is target-specific and lives in the
|
|
11
11
|
* target's own testing entry (e.g. `@prisma/composer-prisma-cloud/testing`).
|
|
12
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
13
|
/**
|
|
19
14
|
* Returns a service node whose `load()` yields the dependency doubles and
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* `input()` the given input object — everything else about the node (its
|
|
16
|
+
* deps, input schema, build, expose) is unchanged. The input double is NOT
|
|
17
|
+
* validated: the test supplies the typed, already-validated shape directly.
|
|
18
|
+
* `run()` is not meaningful on a mock (there is no boot, no environment) and
|
|
19
|
+
* throws if called.
|
|
25
20
|
*/
|
|
26
21
|
function mockService(service, overrides) {
|
|
27
22
|
const entries = Object.entries(overrides);
|
|
28
23
|
const deps = blindCast(Object.fromEntries(entries.filter(([name]) => name in service.inputs)));
|
|
29
|
-
const
|
|
30
|
-
...paramDefaults(service.params),
|
|
31
|
-
...Object.fromEntries(entries.filter(([name]) => name in service.params))
|
|
32
|
-
});
|
|
24
|
+
const input = blindCast(entries.find(([name]) => name === "input")?.[1]);
|
|
33
25
|
return Object.freeze({
|
|
34
26
|
...service,
|
|
35
27
|
run() {
|
|
36
|
-
throw new Error(`mockService(): "${service.name}" is a load()/
|
|
28
|
+
throw new Error(`mockService(): "${service.name}" is a load()/input()-only mock — it has no run() (no boot, no environment).`);
|
|
37
29
|
},
|
|
38
30
|
load: () => deps,
|
|
39
|
-
|
|
31
|
+
input: () => input
|
|
40
32
|
});
|
|
41
33
|
}
|
|
42
34
|
//#endregion
|