@prisma/composer-prisma-cloud 0.2.0-dev.11 → 0.2.0-dev.13
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/buckets-main.mjs +795 -0
- package/dist/buckets-main.mjs.map +1 -0
- package/dist/compute-main.mjs +532 -0
- package/dist/compute-main.mjs.map +1 -0
- package/dist/control.mjs +41 -1257
- package/dist/control.mjs.map +1 -1
- package/dist/cron/index.mjs +2 -3
- package/dist/cron/index.mjs.map +1 -1
- package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
- package/dist/cron/scheduler-service.mjs +2 -3
- package/dist/cron/scheduler-service.mjs.map +1 -1
- package/dist/email/email-entrypoint.mjs.map +1 -1
- package/dist/email/email-service.mjs +2 -3
- package/dist/email/email-service.mjs.map +1 -1
- package/dist/email/index.mjs +2 -3
- package/dist/email/index.mjs.map +1 -1
- package/dist/email/testing.mjs.map +1 -1
- package/dist/get-port-BYnpbW5H.mjs +111 -0
- package/dist/get-port-BYnpbW5H.mjs.map +1 -0
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/local-target.d.mts +12 -0
- package/dist/local-target.d.mts.map +1 -0
- package/dist/local-target.mjs +6129 -0
- package/dist/local-target.mjs.map +1 -0
- package/dist/param-BTzY0ne5-CW6ojZ3E.mjs +66 -0
- package/dist/param-BTzY0ne5-CW6ojZ3E.mjs.map +1 -0
- package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs +79 -0
- package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs.map +1 -0
- package/dist/postgres-main.mjs +402 -0
- package/dist/postgres-main.mjs.map +1 -0
- package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs +1252 -0
- package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs.map +1 -0
- package/dist/{prisma-next-BvBwGCIt-CSauMX4l.mjs → prisma-next-BvBwGCIt-DssgnctU.mjs} +3 -78
- package/dist/prisma-next-BvBwGCIt-DssgnctU.mjs.map +1 -0
- package/dist/prisma-next.mjs +1 -1
- package/dist/{provisioned-edges-B_XS1Mz--NFJgcXK7.mjs → provisioned-edges-C1wSO3cx-YC5C19f9.mjs} +5 -65
- package/dist/provisioned-edges-C1wSO3cx-YC5C19f9.mjs.map +1 -0
- package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs +47 -0
- package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs.map +1 -0
- package/dist/segments-NpKN-46R-B01DmCqy.mjs +1441 -0
- package/dist/segments-NpKN-46R-B01DmCqy.mjs.map +1 -0
- package/dist/{serializer-D8_84XWO-DA2B6YbX.mjs → serializer-CJiOhF2d-DJUjQj1i.mjs} +4 -44
- package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs.map +1 -0
- package/dist/storage/index.mjs +2 -3
- package/dist/storage/index.mjs.map +1 -1
- package/dist/storage/storage-entrypoint.mjs.map +1 -1
- package/dist/storage/storage-service.mjs +2 -3
- package/dist/storage/storage-service.mjs.map +1 -1
- package/dist/streams/index.mjs +2 -3
- package/dist/streams/index.mjs.map +1 -1
- package/dist/streams/streams-entrypoint.mjs.map +1 -1
- package/dist/streams/streams-service.mjs +2 -3
- package/dist/streams/streams-service.mjs.map +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +21 -15
- package/src/exports/local-target.ts +1 -0
- package/dist/prisma-next-BvBwGCIt-CSauMX4l.mjs.map +0 -1
- package/dist/provisioned-edges-B_XS1Mz--NFJgcXK7.mjs.map +0 -1
- package/dist/serializer-D8_84XWO-DA2B6YbX.mjs.map +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isParamSource, paramSource } from "@prisma/composer";
|
|
2
|
+
import { blindCast } from "@prisma/composer/casts";
|
|
3
|
+
//#region ../../1-prisma-cloud/1-extensions/target/dist/param-BTzY0ne5.mjs
|
|
4
|
+
/**
|
|
5
|
+
* Brands the payload `envParam` builds. Core's `paramSource()` is a public
|
|
6
|
+
* SPI, so a user could bypass `envParam` and bind a raw `paramSource('x')`;
|
|
7
|
+
* the brand lets `paramName` reject such a source (or another target's) with
|
|
8
|
+
* a clear error instead of reading an absent `.name`.
|
|
9
|
+
*/
|
|
10
|
+
const PRISMA_CLOUD_PARAM_SOURCE = blindCast(Symbol.for("prisma:prisma-cloud-param-source"));
|
|
11
|
+
const RESERVED_PARAM_PREFIX = "COMPOSER_";
|
|
12
|
+
const POISONED_PARAM_NAMES = /* @__PURE__ */ new Set(["DATABASE_URL", "DATABASE_URL_POOLED"]);
|
|
13
|
+
/**
|
|
14
|
+
* Binds a param slot to a named Prisma Cloud platform env var — the non-secret
|
|
15
|
+
* sibling of `envSecret` (spec: env-sourced config params). The platform
|
|
16
|
+
* injects the value into the running instance per stage; the param's own
|
|
17
|
+
* schema validates it at boot, unredacted. The name may not use the
|
|
18
|
+
* framework's reserved `COMPOSER_` prefix or the poisoned
|
|
19
|
+
* `DATABASE_URL(_POOLED)` keys — same parity as `envSecret`.
|
|
20
|
+
*/
|
|
21
|
+
function envParam(name) {
|
|
22
|
+
if (typeof name !== "string" || name.length === 0) throw new Error("envParam() requires a non-empty platform env-var name, e.g. envParam('APP_ORIGIN').");
|
|
23
|
+
if (name.startsWith(RESERVED_PARAM_PREFIX)) throw new Error(`envParam name "${name}" may not start with "${RESERVED_PARAM_PREFIX}" — that prefix is reserved for the framework's own generated config keys.`);
|
|
24
|
+
if (POISONED_PARAM_NAMES.has(name)) throw new Error(`envParam name "${name}" is reserved — ${[...POISONED_PARAM_NAMES].join(" and ")} are poisoned at project provision and cannot back a param.`);
|
|
25
|
+
return paramSource({
|
|
26
|
+
[PRISMA_CLOUD_PARAM_SOURCE]: true,
|
|
27
|
+
name
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** True only for a payload that `envParam` built — i.e. one carrying the brand. */
|
|
31
|
+
function isEnvParamPayload(payload) {
|
|
32
|
+
return typeof payload === "object" && payload !== null && blindCast(payload)[PRISMA_CLOUD_PARAM_SOURCE] === true;
|
|
33
|
+
}
|
|
34
|
+
/** True iff a resolved param value is an env-sourced pointer this target built (as opposed to a literal, or a foreign/raw `ParamSource`). */
|
|
35
|
+
function isEnvParamSource(value) {
|
|
36
|
+
return isParamSource(value) && isEnvParamPayload(value.payload);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reads the Prisma Cloud env-var name back out of a param binding's opaque
|
|
40
|
+
* source. A source not built by `envParam` (a raw `paramSource(...)` or
|
|
41
|
+
* another target's source) carries no name — reject it here. `paramName` runs
|
|
42
|
+
* in preflight and at serialize before any value ever crosses the wire, so a
|
|
43
|
+
* foreign source fails early and clearly rather than producing a broken
|
|
44
|
+
* deploy with an undefined name.
|
|
45
|
+
*/
|
|
46
|
+
function paramName(binding) {
|
|
47
|
+
const { binding: bound } = binding;
|
|
48
|
+
if (!isEnvParamSource(bound)) throw new Error(`param slot "${binding.slot}" of service "${binding.serviceAddress}" is bound to a source not created by envParam() — bind env-sourced params with envParam('NAME') from @prisma/composer-prisma-cloud.`);
|
|
49
|
+
return bound.payload.name;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Finds the manifest entry for one service param slot. `serialize` calls this
|
|
53
|
+
* only after confirming `buildConfig` resolved the slot to a `ParamSource`
|
|
54
|
+
* (`isParamSource(value)`), so a miss here means `graph.params` and the
|
|
55
|
+
* resolved `Config` have drifted — a Load invariant violation, surfaced
|
|
56
|
+
* loudly rather than producing a pointer row with an undefined name.
|
|
57
|
+
*/
|
|
58
|
+
function paramBindingFor(bindings, serviceAddress, slot) {
|
|
59
|
+
const binding = bindings.find((b) => b.serviceAddress === serviceAddress && b.slot === slot);
|
|
60
|
+
if (binding === void 0) throw new Error(`param slot "${slot}" of "${serviceAddress}" resolved to a source but has no bound entry in the manifest — Load should have recorded it.`);
|
|
61
|
+
return binding;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { paramName as i, isEnvParamSource as n, paramBindingFor as r, envParam as t };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=param-BTzY0ne5-CW6ojZ3E.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"param-BTzY0ne5-CW6ojZ3E.mjs","names":[],"sources":["../../../1-prisma-cloud/1-extensions/target/dist/param-BTzY0ne5.mjs"],"sourcesContent":["import { isParamSource, paramSource } from \"@internal/core\";\nimport { blindCast } from \"@internal/foundation/casts\";\n//#region src/param.ts\n/**\n* Brands the payload `envParam` builds. Core's `paramSource()` is a public\n* SPI, so a user could bypass `envParam` and bind a raw `paramSource('x')`;\n* the brand lets `paramName` reject such a source (or another target's) with\n* a clear error instead of reading an absent `.name`.\n*/\nconst PRISMA_CLOUD_PARAM_SOURCE = blindCast(Symbol.for(\"prisma:prisma-cloud-param-source\"));\nconst RESERVED_PARAM_PREFIX = \"COMPOSER_\";\nconst POISONED_PARAM_NAMES = /* @__PURE__ */ new Set([\"DATABASE_URL\", \"DATABASE_URL_POOLED\"]);\n/**\n* Binds a param slot to a named Prisma Cloud platform env var — the non-secret\n* sibling of `envSecret` (spec: env-sourced config params). The platform\n* injects the value into the running instance per stage; the param's own\n* schema validates it at boot, unredacted. The name may not use the\n* framework's reserved `COMPOSER_` prefix or the poisoned\n* `DATABASE_URL(_POOLED)` keys — same parity as `envSecret`.\n*/\nfunction envParam(name) {\n\tif (typeof name !== \"string\" || name.length === 0) throw new Error(\"envParam() requires a non-empty platform env-var name, e.g. envParam('APP_ORIGIN').\");\n\tif (name.startsWith(RESERVED_PARAM_PREFIX)) throw new Error(`envParam name \"${name}\" may not start with \"${RESERVED_PARAM_PREFIX}\" — that prefix is reserved for the framework's own generated config keys.`);\n\tif (POISONED_PARAM_NAMES.has(name)) throw new Error(`envParam name \"${name}\" is reserved — ${[...POISONED_PARAM_NAMES].join(\" and \")} are poisoned at project provision and cannot back a param.`);\n\treturn paramSource({\n\t\t[PRISMA_CLOUD_PARAM_SOURCE]: true,\n\t\tname\n\t});\n}\n/** True only for a payload that `envParam` built — i.e. one carrying the brand. */\nfunction isEnvParamPayload(payload) {\n\treturn typeof payload === \"object\" && payload !== null && blindCast(payload)[PRISMA_CLOUD_PARAM_SOURCE] === true;\n}\n/** True iff a resolved param value is an env-sourced pointer this target built (as opposed to a literal, or a foreign/raw `ParamSource`). */\nfunction isEnvParamSource(value) {\n\treturn isParamSource(value) && isEnvParamPayload(value.payload);\n}\n/**\n* Reads the Prisma Cloud env-var name back out of a param binding's opaque\n* source. A source not built by `envParam` (a raw `paramSource(...)` or\n* another target's source) carries no name — reject it here. `paramName` runs\n* in preflight and at serialize before any value ever crosses the wire, so a\n* foreign source fails early and clearly rather than producing a broken\n* deploy with an undefined name.\n*/\nfunction paramName(binding) {\n\tconst { binding: bound } = binding;\n\tif (!isEnvParamSource(bound)) throw new Error(`param slot \"${binding.slot}\" of service \"${binding.serviceAddress}\" is bound to a source not created by envParam() — bind env-sourced params with envParam('NAME') from @prisma/composer-prisma-cloud.`);\n\treturn bound.payload.name;\n}\n/**\n* Finds the manifest entry for one service param slot. `serialize` calls this\n* only after confirming `buildConfig` resolved the slot to a `ParamSource`\n* (`isParamSource(value)`), so a miss here means `graph.params` and the\n* resolved `Config` have drifted — a Load invariant violation, surfaced\n* loudly rather than producing a pointer row with an undefined name.\n*/\nfunction paramBindingFor(bindings, serviceAddress, slot) {\n\tconst binding = bindings.find((b) => b.serviceAddress === serviceAddress && b.slot === slot);\n\tif (binding === void 0) throw new Error(`param slot \"${slot}\" of \"${serviceAddress}\" resolved to a source but has no bound entry in the manifest — Load should have recorded it.`);\n\treturn binding;\n}\n//#endregion\nexport { paramName as i, isEnvParamSource as n, paramBindingFor as r, envParam as t };\n\n//# sourceMappingURL=param-BTzY0ne5.mjs.map"],"mappings":";;;;;;;;;AASA,MAAM,4BAA4B,UAAU,OAAO,IAAI,kCAAkC,CAAC;AAC1F,MAAM,wBAAwB;AAC9B,MAAM,uCAAuC,IAAI,IAAI,CAAC,gBAAgB,qBAAqB,CAAC;;;;;;;;;AAS5F,SAAS,SAAS,MAAM;CACvB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG,MAAM,IAAI,MAAM,qFAAqF;CACxJ,IAAI,KAAK,WAAW,qBAAqB,GAAG,MAAM,IAAI,MAAM,kBAAkB,KAAK,wBAAwB,sBAAsB,2EAA2E;CAC5M,IAAI,qBAAqB,IAAI,IAAI,GAAG,MAAM,IAAI,MAAM,kBAAkB,KAAK,kBAAkB,CAAC,GAAG,oBAAoB,CAAC,CAAC,KAAK,OAAO,EAAE,4DAA4D;CACjM,OAAO,YAAY;GACjB,4BAA4B;EAC7B;CACD,CAAC;AACF;;AAEA,SAAS,kBAAkB,SAAS;CACnC,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,OAAO,CAAC,CAAC,+BAA+B;AAC7G;;AAEA,SAAS,iBAAiB,OAAO;CAChC,OAAO,cAAc,KAAK,KAAK,kBAAkB,MAAM,OAAO;AAC/D;;;;;;;;;AASA,SAAS,UAAU,SAAS;CAC3B,MAAM,EAAE,SAAS,UAAU;CAC3B,IAAI,CAAC,iBAAiB,KAAK,GAAG,MAAM,IAAI,MAAM,eAAe,QAAQ,KAAK,gBAAgB,QAAQ,eAAe,qIAAqI;CACtP,OAAO,MAAM,QAAQ;AACtB;;;;;;;;AAQA,SAAS,gBAAgB,UAAU,gBAAgB,MAAM;CACxD,MAAM,UAAU,SAAS,MAAM,MAAM,EAAE,mBAAmB,kBAAkB,EAAE,SAAS,IAAI;CAC3F,IAAI,YAAY,KAAK,GAAG,MAAM,IAAI,MAAM,eAAe,KAAK,QAAQ,eAAe,8FAA8F;CACjL,OAAO;AACR"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region ../../1-prisma-cloud/1-extensions/target/dist/pg-connection-CadPZuEK.mjs
|
|
2
|
+
/** Connection resilience helpers for Prisma Postgres cold-starts (FT-5226); no heavy imports (no `effect`/`alchemy`/`pg`), so the deploy lowerings, the pnPostgres runtime client, and bun-runnable services (the storage store, via the pure `@internal/prisma-cloud/connection` subpath) all share one implementation. */
|
|
3
|
+
/** Network-level socket failures node-postgres surfaces as `err.code`. */
|
|
4
|
+
const TRANSIENT_CODES = /* @__PURE__ */ new Set([
|
|
5
|
+
"ECONNREFUSED",
|
|
6
|
+
"ECONNRESET",
|
|
7
|
+
"ETIMEDOUT",
|
|
8
|
+
"EPIPE",
|
|
9
|
+
"ENOTFOUND",
|
|
10
|
+
"EAI_AGAIN"
|
|
11
|
+
]);
|
|
12
|
+
/** Connection-establishment failure messages (no useful `err.code`). */
|
|
13
|
+
const TRANSIENT_MESSAGE_FRAGMENTS = [
|
|
14
|
+
"upstream database",
|
|
15
|
+
"connection terminated",
|
|
16
|
+
"connection refused",
|
|
17
|
+
"terminating connection",
|
|
18
|
+
"server closed the connection",
|
|
19
|
+
"connection timeout",
|
|
20
|
+
"timeout expired"
|
|
21
|
+
];
|
|
22
|
+
/** Whether an error is a transient connection failure worth retrying, as opposed to a real query error that must surface at once. */
|
|
23
|
+
function isTransientConnectionError(error) {
|
|
24
|
+
if (typeof error !== "object" || error === null) return false;
|
|
25
|
+
const code = "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
26
|
+
if (code !== void 0 && TRANSIENT_CODES.has(code)) return true;
|
|
27
|
+
const message = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
|
|
28
|
+
return TRANSIENT_MESSAGE_FRAGMENTS.some((fragment) => message.includes(fragment));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Rewrites a deprecating `sslmode` (`require`/`prefer`/`verify-ca`) to the
|
|
32
|
+
* explicit `verify-full` these already mean, silencing node-postgres's
|
|
33
|
+
* deprecation warning. `disable`/`no-verify`/unset are left untouched.
|
|
34
|
+
*/
|
|
35
|
+
function normalizeSslMode(url) {
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = new URL(url);
|
|
39
|
+
} catch {
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
const sslmode = parsed.searchParams.get("sslmode");
|
|
43
|
+
if (sslmode === "require" || sslmode === "prefer" || sslmode === "verify-ca") {
|
|
44
|
+
parsed.searchParams.set("sslmode", "verify-full");
|
|
45
|
+
return parsed.toString();
|
|
46
|
+
}
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Retries an operation past a transient connection failure, bounded (default
|
|
51
|
+
* ~1 min). `shouldRetry` decides what's transient — defaults to retrying
|
|
52
|
+
* everything; the runtime client passes {@link isTransientConnectionError}.
|
|
53
|
+
*/
|
|
54
|
+
async function withConnectionRetry(operation, opts = {}) {
|
|
55
|
+
const attempts = opts.attempts ?? 12;
|
|
56
|
+
const delayMs = opts.delayMs ?? 5e3;
|
|
57
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
58
|
+
const shouldRetry = opts.shouldRetry ?? (() => true);
|
|
59
|
+
let lastError;
|
|
60
|
+
for (let attempt = 1; attempt <= attempts; attempt++) try {
|
|
61
|
+
return await operation();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!shouldRetry(error)) throw error;
|
|
64
|
+
lastError = error;
|
|
65
|
+
if (attempt < attempts) await sleep(delayMs);
|
|
66
|
+
}
|
|
67
|
+
throw lastError;
|
|
68
|
+
}
|
|
69
|
+
/** Retries acquiring a connection past a transient cold-start; {@link withConnectionRetry} with {@link isTransientConnectionError} fixed as the predicate. */
|
|
70
|
+
function retryTransientConnect(acquire, opts = {}) {
|
|
71
|
+
return withConnectionRetry(acquire, {
|
|
72
|
+
...opts,
|
|
73
|
+
shouldRetry: isTransientConnectionError
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { retryTransientConnect as n, withConnectionRetry as r, normalizeSslMode as t };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=pg-connection-CadPZuEK-CF3FsSQu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-connection-CadPZuEK-CF3FsSQu.mjs","names":[],"sources":["../../../1-prisma-cloud/1-extensions/target/dist/pg-connection-CadPZuEK.mjs"],"sourcesContent":["//#region src/pg-connection.ts\n/** Connection resilience helpers for Prisma Postgres cold-starts (FT-5226); no heavy imports (no `effect`/`alchemy`/`pg`), so the deploy lowerings, the pnPostgres runtime client, and bun-runnable services (the storage store, via the pure `@internal/prisma-cloud/connection` subpath) all share one implementation. */\n/** Network-level socket failures node-postgres surfaces as `err.code`. */\nconst TRANSIENT_CODES = /* @__PURE__ */ new Set([\n\t\"ECONNREFUSED\",\n\t\"ECONNRESET\",\n\t\"ETIMEDOUT\",\n\t\"EPIPE\",\n\t\"ENOTFOUND\",\n\t\"EAI_AGAIN\"\n]);\n/** Connection-establishment failure messages (no useful `err.code`). */\nconst TRANSIENT_MESSAGE_FRAGMENTS = [\n\t\"upstream database\",\n\t\"connection terminated\",\n\t\"connection refused\",\n\t\"terminating connection\",\n\t\"server closed the connection\",\n\t\"connection timeout\",\n\t\"timeout expired\"\n];\n/** Whether an error is a transient connection failure worth retrying, as opposed to a real query error that must surface at once. */\nfunction isTransientConnectionError(error) {\n\tif (typeof error !== \"object\" || error === null) return false;\n\tconst code = \"code\" in error && typeof error.code === \"string\" ? error.code : void 0;\n\tif (code !== void 0 && TRANSIENT_CODES.has(code)) return true;\n\tconst message = \"message\" in error && typeof error.message === \"string\" ? error.message.toLowerCase() : \"\";\n\treturn TRANSIENT_MESSAGE_FRAGMENTS.some((fragment) => message.includes(fragment));\n}\n/**\n* Rewrites a deprecating `sslmode` (`require`/`prefer`/`verify-ca`) to the\n* explicit `verify-full` these already mean, silencing node-postgres's\n* deprecation warning. `disable`/`no-verify`/unset are left untouched.\n*/\nfunction normalizeSslMode(url) {\n\tlet parsed;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\treturn url;\n\t}\n\tconst sslmode = parsed.searchParams.get(\"sslmode\");\n\tif (sslmode === \"require\" || sslmode === \"prefer\" || sslmode === \"verify-ca\") {\n\t\tparsed.searchParams.set(\"sslmode\", \"verify-full\");\n\t\treturn parsed.toString();\n\t}\n\treturn url;\n}\n/**\n* Retries an operation past a transient connection failure, bounded (default\n* ~1 min). `shouldRetry` decides what's transient — defaults to retrying\n* everything; the runtime client passes {@link isTransientConnectionError}.\n*/\nasync function withConnectionRetry(operation, opts = {}) {\n\tconst attempts = opts.attempts ?? 12;\n\tconst delayMs = opts.delayMs ?? 5e3;\n\tconst sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));\n\tconst shouldRetry = opts.shouldRetry ?? (() => true);\n\tlet lastError;\n\tfor (let attempt = 1; attempt <= attempts; attempt++) try {\n\t\treturn await operation();\n\t} catch (error) {\n\t\tif (!shouldRetry(error)) throw error;\n\t\tlastError = error;\n\t\tif (attempt < attempts) await sleep(delayMs);\n\t}\n\tthrow lastError;\n}\n/** Retries acquiring a connection past a transient cold-start; {@link withConnectionRetry} with {@link isTransientConnectionError} fixed as the predicate. */\nfunction retryTransientConnect(acquire, opts = {}) {\n\treturn withConnectionRetry(acquire, {\n\t\t...opts,\n\t\tshouldRetry: isTransientConnectionError\n\t});\n}\n//#endregion\nexport { withConnectionRetry as i, normalizeSslMode as n, retryTransientConnect as r, isTransientConnectionError as t };\n\n//# sourceMappingURL=pg-connection-CadPZuEK.mjs.map"],"mappings":";;;AAGA,MAAM,kCAAkC,IAAI,IAAI;CAC/C;CACA;CACA;CACA;CACA;CACA;AACD,CAAC;;AAED,MAAM,8BAA8B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;AAEA,SAAS,2BAA2B,OAAO;CAC1C,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,OAAO,UAAU,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,KAAK;CACnF,IAAI,SAAS,KAAK,KAAK,gBAAgB,IAAI,IAAI,GAAG,OAAO;CACzD,MAAM,UAAU,aAAa,SAAS,OAAO,MAAM,YAAY,WAAW,MAAM,QAAQ,YAAY,IAAI;CACxG,OAAO,4BAA4B,MAAM,aAAa,QAAQ,SAAS,QAAQ,CAAC;AACjF;;;;;;AAMA,SAAS,iBAAiB,KAAK;CAC9B,IAAI;CACJ,IAAI;EACH,SAAS,IAAI,IAAI,GAAG;CACrB,QAAQ;EACP,OAAO;CACR;CACA,MAAM,UAAU,OAAO,aAAa,IAAI,SAAS;CACjD,IAAI,YAAY,aAAa,YAAY,YAAY,YAAY,aAAa;EAC7E,OAAO,aAAa,IAAI,WAAW,aAAa;EAChD,OAAO,OAAO,SAAS;CACxB;CACA,OAAO;AACR;;;;;;AAMA,eAAe,oBAAoB,WAAW,OAAO,CAAC,GAAG;CACxD,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,QAAQ,KAAK,WAAW,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;CACrF,MAAM,cAAc,KAAK,sBAAsB;CAC/C,IAAI;CACJ,KAAK,IAAI,UAAU,GAAG,WAAW,UAAU,WAAW,IAAI;EACzD,OAAO,MAAM,UAAU;CACxB,SAAS,OAAO;EACf,IAAI,CAAC,YAAY,KAAK,GAAG,MAAM;EAC/B,YAAY;EACZ,IAAI,UAAU,UAAU,MAAM,MAAM,OAAO;CAC5C;CACA,MAAM;AACP;;AAEA,SAAS,sBAAsB,SAAS,OAAO,CAAC,GAAG;CAClD,OAAO,oBAAoB,SAAS;EACnC,GAAG;EACH,aAAa;CACd,CAAC;AACF"}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { i as readOwnVersion, n as isValidSegment, r as readJsonFile, t as StateFile } from "./segments-NpKN-46R-B01DmCqy.mjs";
|
|
2
|
+
import { n as portNumbers, t as getPorts } from "./get-port-BYnpbW5H.mjs";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import * as http from "node:http";
|
|
7
|
+
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/instance-name-BF6J_weL.mjs
|
|
8
|
+
/**
|
|
9
|
+
* `pcdev-<app>-<database-id>` instance-name derivation (local-dev spec § 2
|
|
10
|
+
* `postgres-main.ts`): each half lowercased, every char outside `[a-z0-9]`
|
|
11
|
+
* replaced by `-`, runs collapsed, the combined name trimmed to 63 chars.
|
|
12
|
+
*
|
|
13
|
+
* A separate, side-effect-free module (not defined inline in
|
|
14
|
+
* `postgres-main.ts`) so it can be imported directly by tests without also
|
|
15
|
+
* running that file's own `main()` — `postgres-main.ts` is a daemon
|
|
16
|
+
* entrypoint script, always invoked as a subprocess, and calls `main()`
|
|
17
|
+
* unconditionally at module load.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Deliberately linear, no ambiguous quantifiers: a per-character replace
|
|
21
|
+
* (no `+`, so no run-length backtracking surface), a bounded-quantifier
|
|
22
|
+
* collapse (`{2,}`, not alternation-with-quantifiers), and plain
|
|
23
|
+
* index-walking for the leading/trailing trim instead of a regex — a
|
|
24
|
+
* combined `+`-plus-alternation trim (`/^-+|-+$/g`) is exactly the shape
|
|
25
|
+
* CodeQL's polynomial-ReDoS check flags, whether or not this particular
|
|
26
|
+
* instance is provably safe.
|
|
27
|
+
*/
|
|
28
|
+
function slug(segment) {
|
|
29
|
+
const collapsed = segment.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-{2,}/g, "-");
|
|
30
|
+
let start = 0;
|
|
31
|
+
let end = collapsed.length;
|
|
32
|
+
while (start < end && collapsed[start] === "-") start++;
|
|
33
|
+
while (end > start && collapsed[end - 1] === "-") end--;
|
|
34
|
+
return collapsed.slice(start, end);
|
|
35
|
+
}
|
|
36
|
+
function instanceNameFor(app, id) {
|
|
37
|
+
return `pcdev-${slug(app)}-${slug(id)}`.slice(0, 63);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/postgres-main.mjs
|
|
41
|
+
/**
|
|
42
|
+
* The Postgres emulator daemon (local-dev spec § 2 `postgres-main.ts`): a
|
|
43
|
+
* small local counterpart of hosted Postgres, hosting `@prisma/dev`'s
|
|
44
|
+
* `startPrismaDevServer()` — one named, persistent server per `Database`
|
|
45
|
+
* resource, several servers in this one daemon process. Loopback
|
|
46
|
+
* `node:http` JSON admin API; state under its `--state-dir`.
|
|
47
|
+
*
|
|
48
|
+
* `@prisma/dev` is imported dynamically from a CALLER-RESOLVED path (each
|
|
49
|
+
* admin request that needs it carries `prismaDevModulePath`) so the app
|
|
50
|
+
* owns its own Prisma version — this daemon has no `@prisma/dev` dependency
|
|
51
|
+
* of its own.
|
|
52
|
+
*
|
|
53
|
+
* Runs as its own OS process, started by `daemon.ts`'s `ensureDaemon` via
|
|
54
|
+
* `process.execPath <this file> --port <n> --state-dir <dir>`.
|
|
55
|
+
*/
|
|
56
|
+
const MIN_DATABASE_PORT = 51300;
|
|
57
|
+
const MAX_DATABASE_PORT = 65535;
|
|
58
|
+
const APPS_STATE_MODE = 384;
|
|
59
|
+
/** Spec § 2 step 5's pattern, applied to a fresh database-port allocation. */
|
|
60
|
+
const MAX_FRESH_PORT_CANDIDATES = 5;
|
|
61
|
+
const NOT_INSTALLED_MESSAGE = "local dev needs @prisma/dev for its local Postgres emulator — add \"prisma\" to your app's devDependencies.";
|
|
62
|
+
function maskCredentials(text) {
|
|
63
|
+
return text.replace(/:\/\/([^:@/\s]+):[^@/\s]+@/g, "://$1:***@");
|
|
64
|
+
}
|
|
65
|
+
function firstLine(text) {
|
|
66
|
+
return (text.split("\n")[0] ?? text).trim();
|
|
67
|
+
}
|
|
68
|
+
function isDatabaseRecord(value) {
|
|
69
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "instanceName" in value && typeof value.instanceName === "string" && "databasePort" in value && typeof value.databasePort === "number" && "url" in value && typeof value.url === "string";
|
|
70
|
+
}
|
|
71
|
+
function isAppRecord(value) {
|
|
72
|
+
return typeof value === "object" && value !== null && "databases" in value && typeof value.databases === "object" && value.databases !== null && Object.values(value.databases).every(isDatabaseRecord);
|
|
73
|
+
}
|
|
74
|
+
function isAppsState(value) {
|
|
75
|
+
return typeof value === "object" && value !== null && Object.values(value).every(isAppRecord);
|
|
76
|
+
}
|
|
77
|
+
function isDatabaseBody(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && "prismaDevModulePath" in value && typeof value.prismaDevModulePath === "string";
|
|
79
|
+
}
|
|
80
|
+
function isPrismaDevModule(value) {
|
|
81
|
+
return typeof value === "object" && value !== null && "startPrismaDevServer" in value && typeof value.startPrismaDevServer === "function";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `@prisma/dev` exports a `PortNotAvailableError` class with a `readonly
|
|
85
|
+
* port: number`, but the daemon dynamically imports the module from a
|
|
86
|
+
* caller-given path while the class identity checked here would come from
|
|
87
|
+
* whatever module graph THIS file's own bundle produced — two separate
|
|
88
|
+
* instantiations of the same logical class, so `instanceof` doesn't match
|
|
89
|
+
* across them (confirmed empirically: a real port conflict's `err
|
|
90
|
+
* instanceof mod.PortNotAvailableError` is `false` even though the error IS
|
|
91
|
+
* the port-conflict one). Duck-typing the documented shape — a `port`
|
|
92
|
+
* number matching the candidate we just tried — is what actually survives
|
|
93
|
+
* that boundary.
|
|
94
|
+
*/
|
|
95
|
+
function isPortConflict(err, port) {
|
|
96
|
+
if (typeof err !== "object" || err === null || !("port" in err)) return false;
|
|
97
|
+
return err.port === port;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A dynamic `import()` failure's own message routinely names a SECOND path
|
|
101
|
+
* (e.g. bun's "Cannot find module '<target>' from '<importer>'") — the
|
|
102
|
+
* importer half is this daemon's own internal location, not anything the
|
|
103
|
+
* caller gave us, and has no business in a response body. Rather than try
|
|
104
|
+
* to scrub an arbitrary underlying message for every runtime's own error
|
|
105
|
+
* phrasing, the resolution-failure case names only what the caller
|
|
106
|
+
* supplied — the pinned message plus the given `prismaDevModulePath`, and
|
|
107
|
+
* nothing else. (A DIFFERENT case — a module that resolves fine but whose
|
|
108
|
+
* `startPrismaDevServer()` call itself fails — still surfaces its
|
|
109
|
+
* underlying text verbatim, credential-masked, per spec § 2.)
|
|
110
|
+
*/
|
|
111
|
+
async function importPrismaDev(prismaDevModulePath) {
|
|
112
|
+
let mod;
|
|
113
|
+
try {
|
|
114
|
+
mod = await import(pathToFileURL(prismaDevModulePath).href);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error(`${NOT_INSTALLED_MESSAGE} (could not resolve "${prismaDevModulePath}")`);
|
|
117
|
+
}
|
|
118
|
+
if (!isPrismaDevModule(mod)) throw new Error(`${NOT_INSTALLED_MESSAGE} ("${prismaDevModulePath}" is not a @prisma/dev module)`);
|
|
119
|
+
return mod;
|
|
120
|
+
}
|
|
121
|
+
function isStringKeyedRecord(value) {
|
|
122
|
+
return typeof value === "object" && value !== null;
|
|
123
|
+
}
|
|
124
|
+
function isPrismaDevInternalStateModule(value) {
|
|
125
|
+
return isStringKeyedRecord(value) && typeof value["deleteServer"] === "function";
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `@prisma/dev`'s own package.json declares `./internal/state`, whose
|
|
129
|
+
* `deleteServer(name)` actually removes a stateful server's persisted PGlite
|
|
130
|
+
* data — `startPrismaDevServer`'s public surface only starts/closes a live
|
|
131
|
+
* server, never deletes what a closed one left on disk. Resolved by reading
|
|
132
|
+
* the SAME package's own `exports` map (walked up from the caller-given
|
|
133
|
+
* entry path to find `@prisma/dev`'s `package.json`), never by guessing at
|
|
134
|
+
* `dist/` file layout.
|
|
135
|
+
*/
|
|
136
|
+
async function importPrismaDevInternalState(prismaDevModulePath) {
|
|
137
|
+
let dir = path.dirname(prismaDevModulePath);
|
|
138
|
+
for (;;) {
|
|
139
|
+
const pkgPath = path.join(dir, "package.json");
|
|
140
|
+
if (fs.existsSync(pkgPath)) {
|
|
141
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
142
|
+
if (isStringKeyedRecord(pkg) && pkg["name"] === "@prisma/dev" && isStringKeyedRecord(pkg["exports"])) {
|
|
143
|
+
const entry = pkg["exports"]["./internal/state"];
|
|
144
|
+
const target = resolveExportTarget(entry);
|
|
145
|
+
if (target === void 0) throw new Error(`"@prisma/dev" at "${dir}" does not declare an "./internal/state" export — cannot delete its persisted database data.`);
|
|
146
|
+
const mod = await import(pathToFileURL(path.join(dir, target)).href);
|
|
147
|
+
if (isPrismaDevInternalStateModule(mod)) return mod;
|
|
148
|
+
throw new Error(`"@prisma/dev"'s "./internal/state" export at "${dir}" has no deleteServer.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const parent = path.dirname(dir);
|
|
152
|
+
if (parent === dir) throw new Error(`could not find "@prisma/dev"'s package.json above "${prismaDevModulePath}".`);
|
|
153
|
+
dir = parent;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function resolveExportTarget(entry) {
|
|
157
|
+
if (typeof entry === "string") return entry;
|
|
158
|
+
if (!isStringKeyedRecord(entry)) return void 0;
|
|
159
|
+
const nested = entry["import"] ?? entry["require"] ?? entry["default"];
|
|
160
|
+
if (typeof nested === "string") return nested;
|
|
161
|
+
if (isStringKeyedRecord(nested)) {
|
|
162
|
+
const def = nested["default"];
|
|
163
|
+
if (typeof def === "string") return def;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function parseArgs(argv) {
|
|
167
|
+
let port;
|
|
168
|
+
let stateDir;
|
|
169
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === "--port") port = Number(argv[i + 1]);
|
|
170
|
+
else if (argv[i] === "--state-dir") stateDir = argv[i + 1];
|
|
171
|
+
if (port === void 0 || Number.isNaN(port)) throw new Error("postgres-main: --port <n> is required");
|
|
172
|
+
if (stateDir === void 0) throw new Error("postgres-main: --state-dir <dir> is required");
|
|
173
|
+
return {
|
|
174
|
+
port,
|
|
175
|
+
stateDir
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function readBody(req) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const chunks = [];
|
|
181
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
182
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
183
|
+
req.on("error", reject);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function main() {
|
|
187
|
+
const { port, stateDir } = parseArgs(process.argv.slice(2));
|
|
188
|
+
const ownVersion = readOwnVersion();
|
|
189
|
+
const appsJsonPath = path.join(stateDir, "apps.json");
|
|
190
|
+
const stateFile = new StateFile(appsJsonPath, APPS_STATE_MODE);
|
|
191
|
+
let state = {};
|
|
192
|
+
const runtimes = /* @__PURE__ */ new Map();
|
|
193
|
+
const liveAuxPorts = /* @__PURE__ */ new Set();
|
|
194
|
+
const auxPortsByInstance = /* @__PURE__ */ new Map();
|
|
195
|
+
function releaseAuxPorts(instanceName) {
|
|
196
|
+
for (const p of auxPortsByInstance.get(instanceName) ?? []) liveAuxPorts.delete(p);
|
|
197
|
+
auxPortsByInstance.delete(instanceName);
|
|
198
|
+
}
|
|
199
|
+
function schedulePersist() {
|
|
200
|
+
stateFile.write(state);
|
|
201
|
+
}
|
|
202
|
+
function getOrCreateApp(app) {
|
|
203
|
+
let appRec = state[app];
|
|
204
|
+
if (!appRec) {
|
|
205
|
+
appRec = { databases: {} };
|
|
206
|
+
state[app] = appRec;
|
|
207
|
+
}
|
|
208
|
+
return appRec;
|
|
209
|
+
}
|
|
210
|
+
function usedDatabasePorts() {
|
|
211
|
+
const ports = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const appRec of Object.values(state)) for (const db of Object.values(appRec.databases)) ports.add(db.databasePort);
|
|
213
|
+
return ports;
|
|
214
|
+
}
|
|
215
|
+
async function smallestUnusedDatabasePort(min) {
|
|
216
|
+
return getPorts({
|
|
217
|
+
port: portNumbers(min, MAX_DATABASE_PORT),
|
|
218
|
+
exclude: usedDatabasePorts()
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Three fresh, mutually distinct ports for a server's own HTTP/shadow-database/streams listeners — never persisted, only unique right now. */
|
|
222
|
+
async function allocateAuxPorts(excluding) {
|
|
223
|
+
const taken = /* @__PURE__ */ new Set([
|
|
224
|
+
...usedDatabasePorts(),
|
|
225
|
+
...liveAuxPorts,
|
|
226
|
+
...excluding
|
|
227
|
+
]);
|
|
228
|
+
const httpPort = await getPorts({
|
|
229
|
+
port: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),
|
|
230
|
+
exclude: taken
|
|
231
|
+
});
|
|
232
|
+
taken.add(httpPort);
|
|
233
|
+
const shadowDatabasePort = await getPorts({
|
|
234
|
+
port: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),
|
|
235
|
+
exclude: taken
|
|
236
|
+
});
|
|
237
|
+
taken.add(shadowDatabasePort);
|
|
238
|
+
return {
|
|
239
|
+
httpPort,
|
|
240
|
+
shadowDatabasePort,
|
|
241
|
+
streamsPort: await getPorts({
|
|
242
|
+
port: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),
|
|
243
|
+
exclude: taken
|
|
244
|
+
})
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function ensureDatabase(app, id, prismaDevModulePath) {
|
|
248
|
+
const appRec = getOrCreateApp(app);
|
|
249
|
+
const instanceName = instanceNameFor(app, id);
|
|
250
|
+
const existingRecord = appRec.databases[id];
|
|
251
|
+
const runningServer = runtimes.get(instanceName);
|
|
252
|
+
if (existingRecord && runningServer) return { url: existingRecord.url };
|
|
253
|
+
const prismaDev = await importPrismaDev(prismaDevModulePath);
|
|
254
|
+
const isFreshAllocation = existingRecord === void 0;
|
|
255
|
+
const maxAttempts = isFreshAllocation ? MAX_FRESH_PORT_CANDIDATES : 1;
|
|
256
|
+
let dbPort = existingRecord?.databasePort ?? await smallestUnusedDatabasePort(MIN_DATABASE_PORT);
|
|
257
|
+
for (let attempt = 1;; attempt++) {
|
|
258
|
+
const aux = await allocateAuxPorts(/* @__PURE__ */ new Set([dbPort]));
|
|
259
|
+
try {
|
|
260
|
+
const server = await prismaDev.startPrismaDevServer({
|
|
261
|
+
name: instanceName,
|
|
262
|
+
databasePort: dbPort,
|
|
263
|
+
port: aux.httpPort,
|
|
264
|
+
shadowDatabasePort: aux.shadowDatabasePort,
|
|
265
|
+
streamsPort: aux.streamsPort,
|
|
266
|
+
persistenceMode: "stateful"
|
|
267
|
+
});
|
|
268
|
+
const url = server.database.connectionString;
|
|
269
|
+
runtimes.set(instanceName, server);
|
|
270
|
+
liveAuxPorts.add(aux.httpPort);
|
|
271
|
+
liveAuxPorts.add(aux.shadowDatabasePort);
|
|
272
|
+
liveAuxPorts.add(aux.streamsPort);
|
|
273
|
+
auxPortsByInstance.set(instanceName, [
|
|
274
|
+
aux.httpPort,
|
|
275
|
+
aux.shadowDatabasePort,
|
|
276
|
+
aux.streamsPort
|
|
277
|
+
]);
|
|
278
|
+
appRec.databases[id] = {
|
|
279
|
+
id,
|
|
280
|
+
instanceName,
|
|
281
|
+
databasePort: dbPort,
|
|
282
|
+
url
|
|
283
|
+
};
|
|
284
|
+
schedulePersist();
|
|
285
|
+
return { url };
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (!(isFreshAllocation && isPortConflict(err, dbPort) && attempt < maxAttempts)) {
|
|
288
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
289
|
+
throw new Error(`postgres emulator failed to start database "${instanceName}" on port ${String(dbPort)}: ${maskCredentials(firstLine(reason))}`);
|
|
290
|
+
}
|
|
291
|
+
dbPort = await smallestUnusedDatabasePort(dbPort + 1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function deleteApp(app, prismaDevModulePathHint) {
|
|
296
|
+
const appRec = state[app];
|
|
297
|
+
if (!appRec) return;
|
|
298
|
+
const entries = Object.values(appRec.databases);
|
|
299
|
+
await Promise.all(entries.map(async (db) => {
|
|
300
|
+
const server = runtimes.get(db.instanceName);
|
|
301
|
+
if (server) {
|
|
302
|
+
await server.close();
|
|
303
|
+
runtimes.delete(db.instanceName);
|
|
304
|
+
releaseAuxPorts(db.instanceName);
|
|
305
|
+
}
|
|
306
|
+
}));
|
|
307
|
+
if (entries.length > 0 && prismaDevModulePathHint) {
|
|
308
|
+
const { deleteServer } = await importPrismaDevInternalState(prismaDevModulePathHint);
|
|
309
|
+
await Promise.all(entries.map((db) => deleteServer(db.instanceName)));
|
|
310
|
+
}
|
|
311
|
+
delete state[app];
|
|
312
|
+
schedulePersist();
|
|
313
|
+
}
|
|
314
|
+
function json(res, status, body) {
|
|
315
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
316
|
+
res.end(JSON.stringify(body));
|
|
317
|
+
}
|
|
318
|
+
function text(res, status, body) {
|
|
319
|
+
res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
320
|
+
res.end(body);
|
|
321
|
+
}
|
|
322
|
+
function badSegment(res, segment) {
|
|
323
|
+
text(res, 400, `invalid path segment "${segment}": must match /^[a-z0-9][a-z0-9-]*$/ and be at most 63 characters`);
|
|
324
|
+
}
|
|
325
|
+
function databaseView(db) {
|
|
326
|
+
return {
|
|
327
|
+
id: db.id,
|
|
328
|
+
url: db.url,
|
|
329
|
+
instanceName: db.instanceName,
|
|
330
|
+
databasePort: db.databasePort
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/** The most recently observed `prismaDevModulePath` for any database of `app` — DELETE has no body of its own to carry one. */
|
|
334
|
+
const recentPrismaDevModulePath = /* @__PURE__ */ new Map();
|
|
335
|
+
function lastKnownPrismaDevModulePath(app) {
|
|
336
|
+
return recentPrismaDevModulePath.get(app);
|
|
337
|
+
}
|
|
338
|
+
async function handleRequest(req, res) {
|
|
339
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
|
|
340
|
+
const method = req.method ?? "GET";
|
|
341
|
+
const segments = url.pathname.split("/").filter((s) => s.length > 0).map((s) => decodeURIComponent(s));
|
|
342
|
+
if (method === "GET" && segments.length === 1 && segments[0] === "health") return json(res, 200, { version: ownVersion });
|
|
343
|
+
if (segments[0] === "apps" && segments.length >= 2) {
|
|
344
|
+
const app = segments[1];
|
|
345
|
+
if (app === void 0 || !isValidSegment(app)) return badSegment(res, app ?? "");
|
|
346
|
+
if (method === "PUT" && segments.length === 4 && segments[2] === "databases") {
|
|
347
|
+
const id = segments[3];
|
|
348
|
+
if (id === void 0 || !isValidSegment(id)) return badSegment(res, id ?? "");
|
|
349
|
+
const raw = await readBody(req);
|
|
350
|
+
let parsed;
|
|
351
|
+
try {
|
|
352
|
+
parsed = JSON.parse(raw.toString("utf8"));
|
|
353
|
+
} catch {
|
|
354
|
+
return text(res, 400, "malformed JSON body");
|
|
355
|
+
}
|
|
356
|
+
if (!isDatabaseBody(parsed)) return text(res, 400, "malformed database body: expected { \"prismaDevModulePath\": string }");
|
|
357
|
+
recentPrismaDevModulePath.set(app, parsed.prismaDevModulePath);
|
|
358
|
+
return json(res, 200, await ensureDatabase(app, id, parsed.prismaDevModulePath));
|
|
359
|
+
}
|
|
360
|
+
if (method === "GET" && segments.length === 3 && segments[2] === "databases") {
|
|
361
|
+
const appRec = state[app];
|
|
362
|
+
return json(res, 200, appRec ? Object.values(appRec.databases).map(databaseView) : []);
|
|
363
|
+
}
|
|
364
|
+
if (method === "DELETE" && segments.length === 2) {
|
|
365
|
+
await deleteApp(app, lastKnownPrismaDevModulePath(app));
|
|
366
|
+
res.writeHead(204);
|
|
367
|
+
res.end();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
res.writeHead(404);
|
|
372
|
+
res.end();
|
|
373
|
+
}
|
|
374
|
+
const server = http.createServer((req, res) => {
|
|
375
|
+
handleRequest(req, res).catch((err) => {
|
|
376
|
+
if (!res.headersSent) res.writeHead(500, { "content-type": "text/plain" });
|
|
377
|
+
res.end(err instanceof Error ? maskCredentials(err.message) : String(err));
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
async function shutdown() {
|
|
381
|
+
await Promise.all([...runtimes.values()].map((server) => server.close().catch(() => void 0)));
|
|
382
|
+
await stateFile.flush();
|
|
383
|
+
server.close();
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
process.on("SIGTERM", () => void shutdown());
|
|
387
|
+
process.on("SIGINT", () => void shutdown());
|
|
388
|
+
readJsonFile(appsJsonPath, isAppsState).then((loaded) => {
|
|
389
|
+
if (loaded) state = loaded;
|
|
390
|
+
server.listen(port, "127.0.0.1", () => {
|
|
391
|
+
console.log(`[dev-emulators] postgres-main listening on 127.0.0.1:${String(port)}`);
|
|
392
|
+
});
|
|
393
|
+
}).catch((err) => {
|
|
394
|
+
console.error("postgres-main: failed to load state", err);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
main();
|
|
399
|
+
//#endregion
|
|
400
|
+
export {};
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=postgres-main.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-main.mjs","names":["getPort"],"sources":["../../../1-prisma-cloud/0-lowering/dev-emulators/dist/instance-name-BF6J_weL.mjs","../../../1-prisma-cloud/0-lowering/dev-emulators/dist/postgres-main.mjs"],"sourcesContent":["//#region src/instance-name.ts\n/**\n* `pcdev-<app>-<database-id>` instance-name derivation (local-dev spec § 2\n* `postgres-main.ts`): each half lowercased, every char outside `[a-z0-9]`\n* replaced by `-`, runs collapsed, the combined name trimmed to 63 chars.\n*\n* A separate, side-effect-free module (not defined inline in\n* `postgres-main.ts`) so it can be imported directly by tests without also\n* running that file's own `main()` — `postgres-main.ts` is a daemon\n* entrypoint script, always invoked as a subprocess, and calls `main()`\n* unconditionally at module load.\n*/\n/**\n* Deliberately linear, no ambiguous quantifiers: a per-character replace\n* (no `+`, so no run-length backtracking surface), a bounded-quantifier\n* collapse (`{2,}`, not alternation-with-quantifiers), and plain\n* index-walking for the leading/trailing trim instead of a regex — a\n* combined `+`-plus-alternation trim (`/^-+|-+$/g`) is exactly the shape\n* CodeQL's polynomial-ReDoS check flags, whether or not this particular\n* instance is provably safe.\n*/\nfunction slug(segment) {\n\tconst collapsed = segment.toLowerCase().replace(/[^a-z0-9]/g, \"-\").replace(/-{2,}/g, \"-\");\n\tlet start = 0;\n\tlet end = collapsed.length;\n\twhile (start < end && collapsed[start] === \"-\") start++;\n\twhile (end > start && collapsed[end - 1] === \"-\") end--;\n\treturn collapsed.slice(start, end);\n}\nfunction instanceNameFor(app, id) {\n\treturn `pcdev-${slug(app)}-${slug(id)}`.slice(0, 63);\n}\n//#endregion\nexport { slug as n, instanceNameFor as t };\n\n//# sourceMappingURL=instance-name-BF6J_weL.mjs.map","import { c as readOwnVersion, f as StateFile, p as readJsonFile, t as isValidSegment } from \"./segments-NpKN-46R.mjs\";\nimport { t as instanceNameFor } from \"./instance-name-BF6J_weL.mjs\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport getPort, { portNumbers } from \"get-port\";\nimport * as http from \"node:http\";\n//#region src/postgres-main.ts\n/**\n* The Postgres emulator daemon (local-dev spec § 2 `postgres-main.ts`): a\n* small local counterpart of hosted Postgres, hosting `@prisma/dev`'s\n* `startPrismaDevServer()` — one named, persistent server per `Database`\n* resource, several servers in this one daemon process. Loopback\n* `node:http` JSON admin API; state under its `--state-dir`.\n*\n* `@prisma/dev` is imported dynamically from a CALLER-RESOLVED path (each\n* admin request that needs it carries `prismaDevModulePath`) so the app\n* owns its own Prisma version — this daemon has no `@prisma/dev` dependency\n* of its own.\n*\n* Runs as its own OS process, started by `daemon.ts`'s `ensureDaemon` via\n* `process.execPath <this file> --port <n> --state-dir <dir>`.\n*/\nconst MIN_DATABASE_PORT = 51300;\nconst MAX_DATABASE_PORT = 65535;\nconst APPS_STATE_MODE = 384;\n/** Spec § 2 step 5's pattern, applied to a fresh database-port allocation. */\nconst MAX_FRESH_PORT_CANDIDATES = 5;\nconst NOT_INSTALLED_MESSAGE = \"local dev needs @prisma/dev for its local Postgres emulator — add \\\"prisma\\\" to your app's devDependencies.\";\nfunction maskCredentials(text) {\n\treturn text.replace(/:\\/\\/([^:@/\\s]+):[^@/\\s]+@/g, \"://$1:***@\");\n}\nfunction firstLine(text) {\n\treturn (text.split(\"\\n\")[0] ?? text).trim();\n}\nfunction isDatabaseRecord(value) {\n\treturn typeof value === \"object\" && value !== null && \"id\" in value && typeof value.id === \"string\" && \"instanceName\" in value && typeof value.instanceName === \"string\" && \"databasePort\" in value && typeof value.databasePort === \"number\" && \"url\" in value && typeof value.url === \"string\";\n}\nfunction isAppRecord(value) {\n\treturn typeof value === \"object\" && value !== null && \"databases\" in value && typeof value.databases === \"object\" && value.databases !== null && Object.values(value.databases).every(isDatabaseRecord);\n}\nfunction isAppsState(value) {\n\treturn typeof value === \"object\" && value !== null && Object.values(value).every(isAppRecord);\n}\nfunction isDatabaseBody(value) {\n\treturn typeof value === \"object\" && value !== null && \"prismaDevModulePath\" in value && typeof value.prismaDevModulePath === \"string\";\n}\nfunction isPrismaDevModule(value) {\n\treturn typeof value === \"object\" && value !== null && \"startPrismaDevServer\" in value && typeof value.startPrismaDevServer === \"function\";\n}\n/**\n* `@prisma/dev` exports a `PortNotAvailableError` class with a `readonly\n* port: number`, but the daemon dynamically imports the module from a\n* caller-given path while the class identity checked here would come from\n* whatever module graph THIS file's own bundle produced — two separate\n* instantiations of the same logical class, so `instanceof` doesn't match\n* across them (confirmed empirically: a real port conflict's `err\n* instanceof mod.PortNotAvailableError` is `false` even though the error IS\n* the port-conflict one). Duck-typing the documented shape — a `port`\n* number matching the candidate we just tried — is what actually survives\n* that boundary.\n*/\nfunction isPortConflict(err, port) {\n\tif (typeof err !== \"object\" || err === null || !(\"port\" in err)) return false;\n\treturn err.port === port;\n}\n/**\n* A dynamic `import()` failure's own message routinely names a SECOND path\n* (e.g. bun's \"Cannot find module '<target>' from '<importer>'\") — the\n* importer half is this daemon's own internal location, not anything the\n* caller gave us, and has no business in a response body. Rather than try\n* to scrub an arbitrary underlying message for every runtime's own error\n* phrasing, the resolution-failure case names only what the caller\n* supplied — the pinned message plus the given `prismaDevModulePath`, and\n* nothing else. (A DIFFERENT case — a module that resolves fine but whose\n* `startPrismaDevServer()` call itself fails — still surfaces its\n* underlying text verbatim, credential-masked, per spec § 2.)\n*/\nasync function importPrismaDev(prismaDevModulePath) {\n\tlet mod;\n\ttry {\n\t\tmod = await import(pathToFileURL(prismaDevModulePath).href);\n\t} catch {\n\t\tthrow new Error(`${NOT_INSTALLED_MESSAGE} (could not resolve \"${prismaDevModulePath}\")`);\n\t}\n\tif (!isPrismaDevModule(mod)) throw new Error(`${NOT_INSTALLED_MESSAGE} (\"${prismaDevModulePath}\" is not a @prisma/dev module)`);\n\treturn mod;\n}\nfunction isStringKeyedRecord(value) {\n\treturn typeof value === \"object\" && value !== null;\n}\nfunction isPrismaDevInternalStateModule(value) {\n\treturn isStringKeyedRecord(value) && typeof value[\"deleteServer\"] === \"function\";\n}\n/**\n* `@prisma/dev`'s own package.json declares `./internal/state`, whose\n* `deleteServer(name)` actually removes a stateful server's persisted PGlite\n* data — `startPrismaDevServer`'s public surface only starts/closes a live\n* server, never deletes what a closed one left on disk. Resolved by reading\n* the SAME package's own `exports` map (walked up from the caller-given\n* entry path to find `@prisma/dev`'s `package.json`), never by guessing at\n* `dist/` file layout.\n*/\nasync function importPrismaDevInternalState(prismaDevModulePath) {\n\tlet dir = path.dirname(prismaDevModulePath);\n\tfor (;;) {\n\t\tconst pkgPath = path.join(dir, \"package.json\");\n\t\tif (fs.existsSync(pkgPath)) {\n\t\t\tconst pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n\t\t\tif (isStringKeyedRecord(pkg) && pkg[\"name\"] === \"@prisma/dev\" && isStringKeyedRecord(pkg[\"exports\"])) {\n\t\t\t\tconst entry = pkg[\"exports\"][\"./internal/state\"];\n\t\t\t\tconst target = resolveExportTarget(entry);\n\t\t\t\tif (target === void 0) throw new Error(`\"@prisma/dev\" at \"${dir}\" does not declare an \"./internal/state\" export — cannot delete its persisted database data.`);\n\t\t\t\tconst mod = await import(pathToFileURL(path.join(dir, target)).href);\n\t\t\t\tif (isPrismaDevInternalStateModule(mod)) return mod;\n\t\t\t\tthrow new Error(`\"@prisma/dev\"'s \"./internal/state\" export at \"${dir}\" has no deleteServer.`);\n\t\t\t}\n\t\t}\n\t\tconst parent = path.dirname(dir);\n\t\tif (parent === dir) throw new Error(`could not find \"@prisma/dev\"'s package.json above \"${prismaDevModulePath}\".`);\n\t\tdir = parent;\n\t}\n}\nfunction resolveExportTarget(entry) {\n\tif (typeof entry === \"string\") return entry;\n\tif (!isStringKeyedRecord(entry)) return void 0;\n\tconst nested = entry[\"import\"] ?? entry[\"require\"] ?? entry[\"default\"];\n\tif (typeof nested === \"string\") return nested;\n\tif (isStringKeyedRecord(nested)) {\n\t\tconst def = nested[\"default\"];\n\t\tif (typeof def === \"string\") return def;\n\t}\n}\nfunction parseArgs(argv) {\n\tlet port;\n\tlet stateDir;\n\tfor (let i = 0; i < argv.length; i++) if (argv[i] === \"--port\") port = Number(argv[i + 1]);\n\telse if (argv[i] === \"--state-dir\") stateDir = argv[i + 1];\n\tif (port === void 0 || Number.isNaN(port)) throw new Error(\"postgres-main: --port <n> is required\");\n\tif (stateDir === void 0) throw new Error(\"postgres-main: --state-dir <dir> is required\");\n\treturn {\n\t\tport,\n\t\tstateDir\n\t};\n}\nfunction readBody(req) {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks = [];\n\t\treq.on(\"data\", (chunk) => chunks.push(chunk));\n\t\treq.on(\"end\", () => resolve(Buffer.concat(chunks)));\n\t\treq.on(\"error\", reject);\n\t});\n}\nfunction main() {\n\tconst { port, stateDir } = parseArgs(process.argv.slice(2));\n\tconst ownVersion = readOwnVersion();\n\tconst appsJsonPath = path.join(stateDir, \"apps.json\");\n\tconst stateFile = new StateFile(appsJsonPath, APPS_STATE_MODE);\n\tlet state = {};\n\tconst runtimes = /* @__PURE__ */ new Map();\n\tconst liveAuxPorts = /* @__PURE__ */ new Set();\n\tconst auxPortsByInstance = /* @__PURE__ */ new Map();\n\tfunction releaseAuxPorts(instanceName) {\n\t\tfor (const p of auxPortsByInstance.get(instanceName) ?? []) liveAuxPorts.delete(p);\n\t\tauxPortsByInstance.delete(instanceName);\n\t}\n\tfunction schedulePersist() {\n\t\tstateFile.write(state);\n\t}\n\tfunction getOrCreateApp(app) {\n\t\tlet appRec = state[app];\n\t\tif (!appRec) {\n\t\t\tappRec = { databases: {} };\n\t\t\tstate[app] = appRec;\n\t\t}\n\t\treturn appRec;\n\t}\n\tfunction usedDatabasePorts() {\n\t\tconst ports = /* @__PURE__ */ new Set();\n\t\tfor (const appRec of Object.values(state)) for (const db of Object.values(appRec.databases)) ports.add(db.databasePort);\n\t\treturn ports;\n\t}\n\tasync function smallestUnusedDatabasePort(min) {\n\t\treturn getPort({\n\t\t\tport: portNumbers(min, MAX_DATABASE_PORT),\n\t\t\texclude: usedDatabasePorts()\n\t\t});\n\t}\n\t/** Three fresh, mutually distinct ports for a server's own HTTP/shadow-database/streams listeners — never persisted, only unique right now. */\n\tasync function allocateAuxPorts(excluding) {\n\t\tconst taken = /* @__PURE__ */ new Set([\n\t\t\t...usedDatabasePorts(),\n\t\t\t...liveAuxPorts,\n\t\t\t...excluding\n\t\t]);\n\t\tconst httpPort = await getPort({\n\t\t\tport: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),\n\t\t\texclude: taken\n\t\t});\n\t\ttaken.add(httpPort);\n\t\tconst shadowDatabasePort = await getPort({\n\t\t\tport: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),\n\t\t\texclude: taken\n\t\t});\n\t\ttaken.add(shadowDatabasePort);\n\t\treturn {\n\t\t\thttpPort,\n\t\t\tshadowDatabasePort,\n\t\t\tstreamsPort: await getPort({\n\t\t\t\tport: portNumbers(MIN_DATABASE_PORT, MAX_DATABASE_PORT),\n\t\t\t\texclude: taken\n\t\t\t})\n\t\t};\n\t}\n\tasync function ensureDatabase(app, id, prismaDevModulePath) {\n\t\tconst appRec = getOrCreateApp(app);\n\t\tconst instanceName = instanceNameFor(app, id);\n\t\tconst existingRecord = appRec.databases[id];\n\t\tconst runningServer = runtimes.get(instanceName);\n\t\tif (existingRecord && runningServer) return { url: existingRecord.url };\n\t\tconst prismaDev = await importPrismaDev(prismaDevModulePath);\n\t\tconst isFreshAllocation = existingRecord === void 0;\n\t\tconst maxAttempts = isFreshAllocation ? MAX_FRESH_PORT_CANDIDATES : 1;\n\t\tlet dbPort = existingRecord?.databasePort ?? await smallestUnusedDatabasePort(MIN_DATABASE_PORT);\n\t\tfor (let attempt = 1;; attempt++) {\n\t\t\tconst aux = await allocateAuxPorts(/* @__PURE__ */ new Set([dbPort]));\n\t\t\ttry {\n\t\t\t\tconst server = await prismaDev.startPrismaDevServer({\n\t\t\t\t\tname: instanceName,\n\t\t\t\t\tdatabasePort: dbPort,\n\t\t\t\t\tport: aux.httpPort,\n\t\t\t\t\tshadowDatabasePort: aux.shadowDatabasePort,\n\t\t\t\t\tstreamsPort: aux.streamsPort,\n\t\t\t\t\tpersistenceMode: \"stateful\"\n\t\t\t\t});\n\t\t\t\tconst url = server.database.connectionString;\n\t\t\t\truntimes.set(instanceName, server);\n\t\t\t\tliveAuxPorts.add(aux.httpPort);\n\t\t\t\tliveAuxPorts.add(aux.shadowDatabasePort);\n\t\t\t\tliveAuxPorts.add(aux.streamsPort);\n\t\t\t\tauxPortsByInstance.set(instanceName, [\n\t\t\t\t\taux.httpPort,\n\t\t\t\t\taux.shadowDatabasePort,\n\t\t\t\t\taux.streamsPort\n\t\t\t\t]);\n\t\t\t\tappRec.databases[id] = {\n\t\t\t\t\tid,\n\t\t\t\t\tinstanceName,\n\t\t\t\t\tdatabasePort: dbPort,\n\t\t\t\t\turl\n\t\t\t\t};\n\t\t\t\tschedulePersist();\n\t\t\t\treturn { url };\n\t\t\t} catch (err) {\n\t\t\t\tif (!(isFreshAllocation && isPortConflict(err, dbPort) && attempt < maxAttempts)) {\n\t\t\t\t\tconst reason = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthrow new Error(`postgres emulator failed to start database \"${instanceName}\" on port ${String(dbPort)}: ${maskCredentials(firstLine(reason))}`);\n\t\t\t\t}\n\t\t\t\tdbPort = await smallestUnusedDatabasePort(dbPort + 1);\n\t\t\t}\n\t\t}\n\t}\n\tasync function deleteApp(app, prismaDevModulePathHint) {\n\t\tconst appRec = state[app];\n\t\tif (!appRec) return;\n\t\tconst entries = Object.values(appRec.databases);\n\t\tawait Promise.all(entries.map(async (db) => {\n\t\t\tconst server = runtimes.get(db.instanceName);\n\t\t\tif (server) {\n\t\t\t\tawait server.close();\n\t\t\t\truntimes.delete(db.instanceName);\n\t\t\t\treleaseAuxPorts(db.instanceName);\n\t\t\t}\n\t\t}));\n\t\tif (entries.length > 0 && prismaDevModulePathHint) {\n\t\t\tconst { deleteServer } = await importPrismaDevInternalState(prismaDevModulePathHint);\n\t\t\tawait Promise.all(entries.map((db) => deleteServer(db.instanceName)));\n\t\t}\n\t\tdelete state[app];\n\t\tschedulePersist();\n\t}\n\tfunction json(res, status, body) {\n\t\tres.writeHead(status, { \"content-type\": \"application/json\" });\n\t\tres.end(JSON.stringify(body));\n\t}\n\tfunction text(res, status, body) {\n\t\tres.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\" });\n\t\tres.end(body);\n\t}\n\tfunction badSegment(res, segment) {\n\t\ttext(res, 400, `invalid path segment \"${segment}\": must match /^[a-z0-9][a-z0-9-]*$/ and be at most 63 characters`);\n\t}\n\tfunction databaseView(db) {\n\t\treturn {\n\t\t\tid: db.id,\n\t\t\turl: db.url,\n\t\t\tinstanceName: db.instanceName,\n\t\t\tdatabasePort: db.databasePort\n\t\t};\n\t}\n\t/** The most recently observed `prismaDevModulePath` for any database of `app` — DELETE has no body of its own to carry one. */\n\tconst recentPrismaDevModulePath = /* @__PURE__ */ new Map();\n\tfunction lastKnownPrismaDevModulePath(app) {\n\t\treturn recentPrismaDevModulePath.get(app);\n\t}\n\tasync function handleRequest(req, res) {\n\t\tconst url = new URL(req.url ?? \"/\", `http://127.0.0.1:${String(port)}`);\n\t\tconst method = req.method ?? \"GET\";\n\t\tconst segments = url.pathname.split(\"/\").filter((s) => s.length > 0).map((s) => decodeURIComponent(s));\n\t\tif (method === \"GET\" && segments.length === 1 && segments[0] === \"health\") return json(res, 200, { version: ownVersion });\n\t\tif (segments[0] === \"apps\" && segments.length >= 2) {\n\t\t\tconst app = segments[1];\n\t\t\tif (app === void 0 || !isValidSegment(app)) return badSegment(res, app ?? \"\");\n\t\t\tif (method === \"PUT\" && segments.length === 4 && segments[2] === \"databases\") {\n\t\t\t\tconst id = segments[3];\n\t\t\t\tif (id === void 0 || !isValidSegment(id)) return badSegment(res, id ?? \"\");\n\t\t\t\tconst raw = await readBody(req);\n\t\t\t\tlet parsed;\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(raw.toString(\"utf8\"));\n\t\t\t\t} catch {\n\t\t\t\t\treturn text(res, 400, \"malformed JSON body\");\n\t\t\t\t}\n\t\t\t\tif (!isDatabaseBody(parsed)) return text(res, 400, \"malformed database body: expected { \\\"prismaDevModulePath\\\": string }\");\n\t\t\t\trecentPrismaDevModulePath.set(app, parsed.prismaDevModulePath);\n\t\t\t\treturn json(res, 200, await ensureDatabase(app, id, parsed.prismaDevModulePath));\n\t\t\t}\n\t\t\tif (method === \"GET\" && segments.length === 3 && segments[2] === \"databases\") {\n\t\t\t\tconst appRec = state[app];\n\t\t\t\treturn json(res, 200, appRec ? Object.values(appRec.databases).map(databaseView) : []);\n\t\t\t}\n\t\t\tif (method === \"DELETE\" && segments.length === 2) {\n\t\t\t\tawait deleteApp(app, lastKnownPrismaDevModulePath(app));\n\t\t\t\tres.writeHead(204);\n\t\t\t\tres.end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tres.writeHead(404);\n\t\tres.end();\n\t}\n\tconst server = http.createServer((req, res) => {\n\t\thandleRequest(req, res).catch((err) => {\n\t\t\tif (!res.headersSent) res.writeHead(500, { \"content-type\": \"text/plain\" });\n\t\t\tres.end(err instanceof Error ? maskCredentials(err.message) : String(err));\n\t\t});\n\t});\n\tasync function shutdown() {\n\t\tawait Promise.all([...runtimes.values()].map((server) => server.close().catch(() => void 0)));\n\t\tawait stateFile.flush();\n\t\tserver.close();\n\t\tprocess.exit(0);\n\t}\n\tprocess.on(\"SIGTERM\", () => void shutdown());\n\tprocess.on(\"SIGINT\", () => void shutdown());\n\treadJsonFile(appsJsonPath, isAppsState).then((loaded) => {\n\t\tif (loaded) state = loaded;\n\t\tserver.listen(port, \"127.0.0.1\", () => {\n\t\t\tconsole.log(`[dev-emulators] postgres-main listening on 127.0.0.1:${String(port)}`);\n\t\t});\n\t}).catch((err) => {\n\t\tconsole.error(\"postgres-main: failed to load state\", err);\n\t\tprocess.exit(1);\n\t});\n}\nmain();\n//#endregion\nexport {};\n\n//# sourceMappingURL=postgres-main.mjs.map"],"mappings":"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"}
|