@prisma/composer-prisma-cloud 0.2.0 → 0.3.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/buckets-main.mjs +795 -0
  2. package/dist/buckets-main.mjs.map +1 -0
  3. package/dist/compute-main.mjs +559 -0
  4. package/dist/compute-main.mjs.map +1 -0
  5. package/dist/control.d.mts +34 -5
  6. package/dist/control.d.mts.map +1 -0
  7. package/dist/control.mjs +98 -1280
  8. package/dist/control.mjs.map +1 -1
  9. package/dist/cron/index.d.mts +54 -4
  10. package/dist/cron/index.d.mts.map +1 -0
  11. package/dist/cron/index.mjs +145 -42
  12. package/dist/cron/index.mjs.map +1 -1
  13. package/dist/cron/scheduler-entrypoint.mjs +637 -529
  14. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  15. package/dist/cron/scheduler-service.mjs +145 -42
  16. package/dist/cron/scheduler-service.mjs.map +1 -1
  17. package/dist/email/email-entrypoint.mjs +19738 -0
  18. package/dist/email/email-entrypoint.mjs.map +1 -0
  19. package/dist/email/email-service.mjs +565 -0
  20. package/dist/email/email-service.mjs.map +1 -0
  21. package/dist/email/index.d.mts +282 -0
  22. package/dist/email/index.d.mts.map +1 -0
  23. package/dist/email/index.mjs +652 -0
  24. package/dist/email/index.mjs.map +1 -0
  25. package/dist/email/testing.d.mts +22 -0
  26. package/dist/email/testing.d.mts.map +1 -0
  27. package/dist/email/testing.mjs +1321 -0
  28. package/dist/email/testing.mjs.map +1 -0
  29. package/dist/get-port-BYnpbW5H.mjs +111 -0
  30. package/dist/get-port-BYnpbW5H.mjs.map +1 -0
  31. package/dist/index.d.mts +37 -6
  32. package/dist/index.d.mts.map +1 -0
  33. package/dist/index.mjs +70 -45
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/local-target.d.mts +12 -0
  36. package/dist/local-target.d.mts.map +1 -0
  37. package/dist/local-target.mjs +6185 -0
  38. package/dist/local-target.mjs.map +1 -0
  39. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs +66 -0
  40. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs.map +1 -0
  41. package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs +79 -0
  42. package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs.map +1 -0
  43. package/dist/postgres-main.mjs +602 -0
  44. package/dist/postgres-main.mjs.map +1 -0
  45. package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs +1252 -0
  46. package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs.map +1 -0
  47. package/dist/{prisma-next-DlU01kXJ-2sP5mQb6.mjs → prisma-next-BvBwGCIt-DssgnctU.mjs} +21 -80
  48. package/dist/prisma-next-BvBwGCIt-DssgnctU.mjs.map +1 -0
  49. package/dist/prisma-next.d.mts +14 -5
  50. package/dist/prisma-next.d.mts.map +1 -0
  51. package/dist/prisma-next.mjs +1 -1
  52. package/dist/provisioned-edges-C1wSO3cx-YC5C19f9.mjs +151 -0
  53. package/dist/provisioned-edges-C1wSO3cx-YC5C19f9.mjs.map +1 -0
  54. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs +47 -0
  55. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs.map +1 -0
  56. package/dist/segments-NpKN-46R-C_LQ2urs.mjs +1454 -0
  57. package/dist/segments-NpKN-46R-C_LQ2urs.mjs.map +1 -0
  58. package/dist/{serializer-CX4VYdf_-KKGoAxfx.mjs → serializer-CJiOhF2d-DJUjQj1i.mjs} +34 -44
  59. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs.map +1 -0
  60. package/dist/serializer-Cx5slrV4-xJfH6EWS.d.mts.map +1 -0
  61. package/dist/storage/index.d.mts +52 -5
  62. package/dist/storage/index.d.mts.map +1 -0
  63. package/dist/storage/index.mjs +158 -47
  64. package/dist/storage/index.mjs.map +1 -1
  65. package/dist/storage/storage-entrypoint.mjs +370 -271
  66. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  67. package/dist/storage/storage-service.mjs +158 -47
  68. package/dist/storage/storage-service.mjs.map +1 -1
  69. package/dist/storage/testing.d.mts +7 -6
  70. package/dist/storage/testing.d.mts.map +1 -0
  71. package/dist/storage/testing.mjs +4 -21
  72. package/dist/storage/testing.mjs.map +1 -1
  73. package/dist/streams/index.d.mts +56 -84
  74. package/dist/streams/index.d.mts.map +1 -0
  75. package/dist/streams/index.mjs +158 -47
  76. package/dist/streams/index.mjs.map +1 -1
  77. package/dist/streams/streams-entrypoint.mjs +366 -250
  78. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  79. package/dist/streams/streams-service.mjs +158 -47
  80. package/dist/streams/streams-service.mjs.map +1 -1
  81. package/dist/streams/testing.d.mts.map +1 -0
  82. package/dist/testing.d.mts.map +1 -0
  83. package/dist/testing.mjs +1 -1
  84. package/dist/testing.mjs.map +1 -1
  85. package/package.json +34 -20
  86. package/src/exports/control.ts +1 -0
  87. package/src/exports/cron.ts +1 -0
  88. package/src/exports/email-testing.ts +1 -0
  89. package/src/exports/email.ts +1 -0
  90. package/src/exports/index.ts +1 -0
  91. package/src/exports/local-target.ts +1 -0
  92. package/src/exports/prisma-next.ts +1 -0
  93. package/src/exports/storage-testing.ts +1 -0
  94. package/src/exports/storage.ts +1 -0
  95. package/src/exports/streams-testing.ts +1 -0
  96. package/src/exports/streams.ts +1 -0
  97. package/src/exports/testing.ts +1 -0
  98. package/dist/prisma-next-DlU01kXJ-2sP5mQb6.mjs.map +0 -1
  99. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs +0 -151
  100. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs.map +0 -1
  101. package/dist/serializer-CX4VYdf_-KKGoAxfx.mjs.map +0 -1
@@ -0,0 +1,602 @@
1
+ import { a as readOwnVersion, i as readJsonFile, n as isPidAlive, r as isValidSegment, t as StateFile } from "./segments-NpKN-46R-C_LQ2urs.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
+ /**
62
+ * A name refused as "already running" is retried a few times, each attempt
63
+ * first waiting out the holder: a cold server boot takes seconds, and a
64
+ * crashed holder's lock is only released once proper-lockfile's ~10s stale
65
+ * threshold passes. Bounded so a genuinely stuck name still fails visibly
66
+ * inside the dev command's own startup budget.
67
+ */
68
+ const MAX_ALREADY_RUNNING_RETRIES = 2;
69
+ const ALREADY_RUNNING_POLLS = 24;
70
+ const ALREADY_RUNNING_POLL_MS = 500;
71
+ const NOT_INSTALLED_MESSAGE = "local dev needs @prisma/dev for its local Postgres emulator — add \"prisma\" to your app's devDependencies.";
72
+ function maskCredentials(text) {
73
+ return text.replace(/:\/\/([^:@/\s]+):[^@/\s]+@/g, "://$1:***@");
74
+ }
75
+ function firstLine(text) {
76
+ return (text.split("\n")[0] ?? text).trim();
77
+ }
78
+ function isDatabaseRecord(value) {
79
+ 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";
80
+ }
81
+ function isAppRecord(value) {
82
+ return typeof value === "object" && value !== null && "databases" in value && typeof value.databases === "object" && value.databases !== null && Object.values(value.databases).every(isDatabaseRecord);
83
+ }
84
+ function isAppsState(value) {
85
+ return typeof value === "object" && value !== null && Object.values(value).every(isAppRecord);
86
+ }
87
+ function isDatabaseBody(value) {
88
+ return typeof value === "object" && value !== null && "prismaDevModulePath" in value && typeof value.prismaDevModulePath === "string";
89
+ }
90
+ function isPrismaDevModule(value) {
91
+ return typeof value === "object" && value !== null && "startPrismaDevServer" in value && typeof value.startPrismaDevServer === "function";
92
+ }
93
+ /**
94
+ * `@prisma/dev` exports a `PortNotAvailableError` class with a `readonly
95
+ * port: number`, but the daemon dynamically imports the module from a
96
+ * caller-given path while the class identity checked here would come from
97
+ * whatever module graph THIS file's own bundle produced — two separate
98
+ * instantiations of the same logical class, so `instanceof` doesn't match
99
+ * across them (confirmed empirically: a real port conflict's `err
100
+ * instanceof mod.PortNotAvailableError` is `false` even though the error IS
101
+ * the port-conflict one). Duck-typing the documented shape — a `port`
102
+ * number matching the candidate we just tried — is what actually survives
103
+ * that boundary.
104
+ */
105
+ /**
106
+ * The port a start refusal is about, when it is one of THIS attempt's ports.
107
+ * Duck-typed (`instanceof` fails across the dynamic-import boundary): every
108
+ * `@prisma/dev` port refusal — not-available, requested-twice, and
109
+ * belongs-to-another-server (its registry can claim a port no bind probe
110
+ * sees) — carries the offending port as `.port`, and any of the four ports
111
+ * we requested (database + the three aux listeners) can be the one refused.
112
+ */
113
+ function portConflictOf(err, ports) {
114
+ if (typeof err !== "object" || err === null || !("port" in err)) return void 0;
115
+ const port = err.port;
116
+ return typeof port === "number" && ports.includes(port) ? port : void 0;
117
+ }
118
+ function isNameAlreadyTaken(err) {
119
+ return isStringKeyedRecord(err) && (err["name"] === "ServerAlreadyRunningError" || err["name"] === "ServerStateAlreadyExistsError");
120
+ }
121
+ /** The port of a postgres connection URL, when it parses as one. */
122
+ function databasePortOf(connectionString) {
123
+ try {
124
+ const port = Number(new URL(connectionString).port);
125
+ return Number.isInteger(port) && port > 0 ? port : void 0;
126
+ } catch {
127
+ return;
128
+ }
129
+ }
130
+ /**
131
+ * A dynamic `import()` failure's own message routinely names a SECOND path
132
+ * (e.g. bun's "Cannot find module '<target>' from '<importer>'") — the
133
+ * importer half is this daemon's own internal location, not anything the
134
+ * caller gave us, and has no business in a response body. Rather than try
135
+ * to scrub an arbitrary underlying message for every runtime's own error
136
+ * phrasing, the resolution-failure case names only what the caller
137
+ * supplied — the pinned message plus the given `prismaDevModulePath`, and
138
+ * nothing else. (A DIFFERENT case — a module that resolves fine but whose
139
+ * `startPrismaDevServer()` call itself fails — still surfaces its
140
+ * underlying text verbatim, credential-masked, per spec § 2.)
141
+ */
142
+ async function importPrismaDev(prismaDevModulePath) {
143
+ let mod;
144
+ try {
145
+ mod = await import(pathToFileURL(prismaDevModulePath).href);
146
+ } catch {
147
+ throw new Error(`${NOT_INSTALLED_MESSAGE} (could not resolve "${prismaDevModulePath}")`);
148
+ }
149
+ if (!isPrismaDevModule(mod)) throw new Error(`${NOT_INSTALLED_MESSAGE} ("${prismaDevModulePath}" is not a @prisma/dev module)`);
150
+ return mod;
151
+ }
152
+ function isStringKeyedRecord(value) {
153
+ return typeof value === "object" && value !== null;
154
+ }
155
+ function isPrismaDevInternalStateModule(value) {
156
+ return isStringKeyedRecord(value) && typeof value["deleteServer"] === "function" && typeof value["killServer"] === "function" && typeof value["getServerStatus"] === "function";
157
+ }
158
+ /**
159
+ * Every port any `@prisma/dev` server RECORD on this machine claims —
160
+ * database, http, shadow, and streams. `startPrismaDevServer` validates a
161
+ * requested port against these records (`ServerState.scan`) and refuses one
162
+ * that any record claims, even when nothing has it bound — and a record's
163
+ * aux ports are picked by `@prisma/dev`'s own walking-upward picker, which
164
+ * over enough servers climbs into this daemon's database range. A fresh
165
+ * database-port pick that only probed the OS would collide with such a
166
+ * claim, so fresh picks exclude these too. Best-effort: `scan` is the same
167
+ * internal surface the rest of this module already binds to, but absent or
168
+ * failing it degrades to the OS probe alone.
169
+ */
170
+ async function registryClaimedPorts(internalState) {
171
+ const ports = /* @__PURE__ */ new Set();
172
+ const scanHost = isStringKeyedRecord(internalState) ? internalState["ServerState"] : void 0;
173
+ const scan = isStringKeyedRecord(scanHost) ? scanHost["scan"] : void 0;
174
+ if (!isStringKeyedRecord(scanHost) || typeof scan !== "function") return ports;
175
+ try {
176
+ const records = await scan.call(scanHost, { onlyMetadata: true });
177
+ if (!Array.isArray(records)) return ports;
178
+ for (const record of records) {
179
+ if (!isStringKeyedRecord(record)) continue;
180
+ for (const key of [
181
+ "databasePort",
182
+ "port",
183
+ "shadowDatabasePort",
184
+ "streamsPort"
185
+ ]) {
186
+ const port = record[key];
187
+ if (typeof port === "number" && Number.isInteger(port) && port > 0) ports.add(port);
188
+ }
189
+ const experimental = record["experimental"];
190
+ const streams = isStringKeyedRecord(experimental) ? experimental["streams"] : void 0;
191
+ const streamsUrl = isStringKeyedRecord(streams) ? streams["serverUrl"] : void 0;
192
+ if (typeof streamsUrl === "string") {
193
+ const port = databasePortOf(streamsUrl);
194
+ if (port !== void 0) ports.add(port);
195
+ }
196
+ }
197
+ } catch {}
198
+ return ports;
199
+ }
200
+ function readServerStatus(status) {
201
+ if (!isStringKeyedRecord(status)) return {
202
+ live: false,
203
+ recordExists: true,
204
+ pid: void 0,
205
+ url: void 0,
206
+ databasePort: void 0
207
+ };
208
+ const exports = status["exports"];
209
+ const database = isStringKeyedRecord(exports) ? exports["database"] : void 0;
210
+ const url = isStringKeyedRecord(database) ? database["connectionString"] : void 0;
211
+ const pid = status["pid"];
212
+ const databasePort = status["databasePort"];
213
+ return {
214
+ live: status["status"] === "running" || status["status"] === "starting_up",
215
+ recordExists: status["status"] !== "no_such_server",
216
+ pid: typeof pid === "number" ? pid : void 0,
217
+ url: typeof url === "string" ? url : void 0,
218
+ databasePort: typeof databasePort === "number" ? databasePort : void 0
219
+ };
220
+ }
221
+ async function serverStatusOf(internalState, instanceName) {
222
+ try {
223
+ return readServerStatus(await internalState.getServerStatus(instanceName));
224
+ } catch {
225
+ return {
226
+ live: false,
227
+ recordExists: true,
228
+ pid: void 0,
229
+ url: void 0,
230
+ databasePort: void 0
231
+ };
232
+ }
233
+ }
234
+ const CLOSE_SETTLE_ATTEMPTS = 40;
235
+ const CLOSE_SETTLE_DELAY_MS = 250;
236
+ /** True once the named server's persisted state no longer claims it is live — the precondition for `deleteServer`, whose kill path targets the pid in that state, which for an in-daemon server is THIS DAEMON's own pid. */
237
+ async function settledAfterClose(internalState, instanceName) {
238
+ for (let attempt = 1; attempt <= CLOSE_SETTLE_ATTEMPTS; attempt += 1) {
239
+ if (!(await serverStatusOf(internalState, instanceName)).live) return true;
240
+ if (attempt < CLOSE_SETTLE_ATTEMPTS) await new Promise((resolve) => setTimeout(resolve, CLOSE_SETTLE_DELAY_MS));
241
+ }
242
+ return false;
243
+ }
244
+ /**
245
+ * `@prisma/dev`'s own package.json declares `./internal/state`, whose
246
+ * `deleteServer(name)` actually removes a stateful server's persisted PGlite
247
+ * data — `startPrismaDevServer`'s public surface only starts/closes a live
248
+ * server, never deletes what a closed one left on disk. Resolved by reading
249
+ * the SAME package's own `exports` map (walked up from the caller-given
250
+ * entry path to find `@prisma/dev`'s `package.json`), never by guessing at
251
+ * `dist/` file layout.
252
+ */
253
+ async function importPrismaDevInternalState(prismaDevModulePath) {
254
+ let dir = path.dirname(prismaDevModulePath);
255
+ for (;;) {
256
+ const pkgPath = path.join(dir, "package.json");
257
+ if (fs.existsSync(pkgPath)) {
258
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
259
+ if (isStringKeyedRecord(pkg) && pkg["name"] === "@prisma/dev" && isStringKeyedRecord(pkg["exports"])) {
260
+ const entry = pkg["exports"]["./internal/state"];
261
+ const target = resolveExportTarget(entry);
262
+ if (target === void 0) throw new Error(`"@prisma/dev" at "${dir}" does not declare an "./internal/state" export — cannot delete its persisted database data.`);
263
+ const mod = await import(pathToFileURL(path.join(dir, target)).href);
264
+ if (isPrismaDevInternalStateModule(mod)) return mod;
265
+ throw new Error(`"@prisma/dev"'s "./internal/state" export at "${dir}" has no deleteServer.`);
266
+ }
267
+ }
268
+ const parent = path.dirname(dir);
269
+ if (parent === dir) throw new Error(`could not find "@prisma/dev"'s package.json above "${prismaDevModulePath}".`);
270
+ dir = parent;
271
+ }
272
+ }
273
+ function resolveExportTarget(entry) {
274
+ if (typeof entry === "string") return entry;
275
+ if (!isStringKeyedRecord(entry)) return void 0;
276
+ const nested = entry["import"] ?? entry["require"] ?? entry["default"];
277
+ if (typeof nested === "string") return nested;
278
+ if (isStringKeyedRecord(nested)) {
279
+ const def = nested["default"];
280
+ if (typeof def === "string") return def;
281
+ }
282
+ }
283
+ function parseArgs(argv) {
284
+ let port;
285
+ let stateDir;
286
+ for (let i = 0; i < argv.length; i++) if (argv[i] === "--port") port = Number(argv[i + 1]);
287
+ else if (argv[i] === "--state-dir") stateDir = argv[i + 1];
288
+ if (port === void 0 || Number.isNaN(port)) throw new Error("postgres-main: --port <n> is required");
289
+ if (stateDir === void 0) throw new Error("postgres-main: --state-dir <dir> is required");
290
+ return {
291
+ port,
292
+ stateDir
293
+ };
294
+ }
295
+ function readBody(req) {
296
+ return new Promise((resolve, reject) => {
297
+ const chunks = [];
298
+ req.on("data", (chunk) => chunks.push(chunk));
299
+ req.on("end", () => resolve(Buffer.concat(chunks)));
300
+ req.on("error", reject);
301
+ });
302
+ }
303
+ function main() {
304
+ const { port, stateDir } = parseArgs(process.argv.slice(2));
305
+ const ownVersion = readOwnVersion();
306
+ const appsJsonPath = path.join(stateDir, "apps.json");
307
+ const stateFile = new StateFile(appsJsonPath, APPS_STATE_MODE);
308
+ let state = {};
309
+ const runtimes = /* @__PURE__ */ new Map();
310
+ /**
311
+ * Server starts run ONE AT A TIME. `@prisma/dev`'s start is not
312
+ * concurrency-safe within a process: two simultaneous calls pick their
313
+ * ports without seeing each other and one fails with a port refusal
314
+ * (verified directly — two concurrent starts with distinct, pinned
315
+ * database ports fail; the same two started in sequence both succeed).
316
+ * The daemon issues concurrent starts whenever an app converges more than
317
+ * one database, which is what produced the port refusals, the retries
318
+ * whose half-started servers held their own name's lock, and every
319
+ * "already running" failure downstream of that.
320
+ */
321
+ let startQueue = Promise.resolve();
322
+ function serializeStart(run) {
323
+ const next = startQueue.then(run, run);
324
+ startQueue = next.then(() => void 0, () => void 0);
325
+ return next;
326
+ }
327
+ function schedulePersist() {
328
+ stateFile.write(state);
329
+ }
330
+ function getOrCreateApp(app) {
331
+ let appRec = state[app];
332
+ if (!appRec) {
333
+ appRec = { databases: {} };
334
+ state[app] = appRec;
335
+ }
336
+ return appRec;
337
+ }
338
+ function usedDatabasePorts() {
339
+ const ports = /* @__PURE__ */ new Set();
340
+ for (const appRec of Object.values(state)) for (const db of Object.values(appRec.databases)) ports.add(db.databasePort);
341
+ return ports;
342
+ }
343
+ async function smallestUnusedDatabasePort(min, alsoExclude = /* @__PURE__ */ new Set()) {
344
+ return getPorts({
345
+ port: portNumbers(min, MAX_DATABASE_PORT),
346
+ exclude: [...usedDatabasePorts(), ...alsoExclude]
347
+ });
348
+ }
349
+ const inflightEnsures = /* @__PURE__ */ new Map();
350
+ /**
351
+ * Names whose `startPrismaDevServer` call failed in THIS process for any
352
+ * reason other than "already running". `@prisma/dev` acquires the name's
353
+ * lock before it validates the requested ports, and a failure after that
354
+ * point (a port refusal) propagates WITHOUT releasing the lock — so the
355
+ * very next start of the same name in this process is refused as "already
356
+ * running" by a holder that is this daemon's own dead attempt, and no
357
+ * amount of waiting frees it. Membership here is what makes deleting that
358
+ * lock provably safe: a name this process leaked cannot have been
359
+ * acquired by anyone else since (the leak never releases).
360
+ */
361
+ const leakedStartNames = /* @__PURE__ */ new Set();
362
+ /** Concurrent PUTs for the same database coalesce onto one start — a second `startPrismaDevServer` for a name whose first start is still booting fails as "already running". */
363
+ function ensureDatabase(app, id, prismaDevModulePath) {
364
+ const instanceName = instanceNameFor(app, id);
365
+ const inflight = inflightEnsures.get(instanceName);
366
+ if (inflight) return inflight;
367
+ const run = ensureDatabaseSerialized(app, id, prismaDevModulePath, instanceName).finally(() => {
368
+ inflightEnsures.delete(instanceName);
369
+ });
370
+ inflightEnsures.set(instanceName, run);
371
+ return run;
372
+ }
373
+ async function ensureDatabaseSerialized(app, id, prismaDevModulePath, instanceName) {
374
+ const appRec = getOrCreateApp(app);
375
+ const existingRecord = appRec.databases[id];
376
+ const runningServer = runtimes.get(instanceName);
377
+ if (existingRecord && runningServer) return { url: existingRecord.url };
378
+ const prismaDev = await importPrismaDev(prismaDevModulePath);
379
+ const internalState = await importPrismaDevInternalState(prismaDevModulePath);
380
+ const recorded = await serverStatusOf(internalState, instanceName);
381
+ if (recorded.live && recorded.pid !== void 0 && isPidAlive(recorded.pid)) {
382
+ if (recorded.url !== void 0) {
383
+ appRec.databases[id] = {
384
+ id,
385
+ instanceName,
386
+ databasePort: recorded.databasePort ?? databasePortOf(recorded.url) ?? MIN_DATABASE_PORT,
387
+ url: recorded.url
388
+ };
389
+ schedulePersist();
390
+ return { url: recorded.url };
391
+ }
392
+ } else if (recorded.live) await internalState.killServer(instanceName).catch(() => void 0);
393
+ const isFreshAllocation = existingRecord === void 0;
394
+ const registryPorts = await registryClaimedPorts(internalState);
395
+ let dbPort = existingRecord?.databasePort ?? 0;
396
+ const conflicted = /* @__PURE__ */ new Set();
397
+ let alreadyRunningRetries = 0;
398
+ let blindRetries = 0;
399
+ for (let attempt = 1;; attempt++) {
400
+ const auxPorts = [];
401
+ try {
402
+ const server = await serializeStart(async () => {
403
+ if (isFreshAllocation) dbPort = await smallestUnusedDatabasePort(MIN_DATABASE_PORT, /* @__PURE__ */ new Set([...registryPorts, ...conflicted]));
404
+ const auxExclude = /* @__PURE__ */ new Set([
405
+ ...registryPorts,
406
+ ...conflicted,
407
+ dbPort
408
+ ]);
409
+ const pickAux = async () => {
410
+ const port = await smallestUnusedDatabasePort(MIN_DATABASE_PORT, auxExclude);
411
+ auxExclude.add(port);
412
+ auxPorts.push(port);
413
+ return port;
414
+ };
415
+ const httpPort = await pickAux();
416
+ const shadowPort = await pickAux();
417
+ const streamsPort = await pickAux();
418
+ return prismaDev.startPrismaDevServer({
419
+ name: instanceName,
420
+ databasePort: dbPort,
421
+ port: httpPort,
422
+ shadowDatabasePort: shadowPort,
423
+ streamsPort,
424
+ persistenceMode: "stateful"
425
+ });
426
+ });
427
+ const url = server.database.connectionString;
428
+ leakedStartNames.delete(instanceName);
429
+ runtimes.set(instanceName, server);
430
+ appRec.databases[id] = {
431
+ id,
432
+ instanceName,
433
+ databasePort: dbPort,
434
+ url
435
+ };
436
+ schedulePersist();
437
+ return { url };
438
+ } catch (err) {
439
+ if (isNameAlreadyTaken(err) && alreadyRunningRetries < MAX_ALREADY_RUNNING_RETRIES) {
440
+ alreadyRunningRetries += 1;
441
+ if (leakedStartNames.has(instanceName)) {
442
+ if (!(await serverStatusOf(internalState, instanceName)).recordExists) {
443
+ await internalState.deleteServer(instanceName).catch(() => void 0);
444
+ continue;
445
+ }
446
+ }
447
+ for (let poll = 1; poll <= ALREADY_RUNNING_POLLS; poll += 1) {
448
+ const now = await serverStatusOf(internalState, instanceName);
449
+ if (now.live && now.pid !== void 0 && isPidAlive(now.pid) && now.url !== void 0) {
450
+ appRec.databases[id] = {
451
+ id,
452
+ instanceName,
453
+ databasePort: now.databasePort ?? databasePortOf(now.url) ?? dbPort,
454
+ url: now.url
455
+ };
456
+ schedulePersist();
457
+ return { url: now.url };
458
+ }
459
+ if (now.live && (now.pid === void 0 || !isPidAlive(now.pid))) {
460
+ await internalState.killServer(instanceName).catch(() => void 0);
461
+ break;
462
+ }
463
+ await new Promise((resolve) => setTimeout(resolve, ALREADY_RUNNING_POLL_MS));
464
+ }
465
+ continue;
466
+ }
467
+ if (!isNameAlreadyTaken(err)) leakedStartNames.add(instanceName);
468
+ const conflictPort = portConflictOf(err, [dbPort, ...auxPorts]);
469
+ if (!(conflictPort !== void 0 && attempt < MAX_FRESH_PORT_CANDIDATES && (conflictPort !== dbPort || isFreshAllocation))) {
470
+ if (conflictPort === void 0 && !isNameAlreadyTaken(err) && blindRetries < 2) {
471
+ blindRetries += 1;
472
+ continue;
473
+ }
474
+ const reason = err instanceof Error ? err.message : String(err);
475
+ throw new Error(`postgres emulator failed to start database "${instanceName}" on port ${String(dbPort)}: ${maskCredentials(firstLine(reason))}`);
476
+ }
477
+ conflicted.add(conflictPort);
478
+ }
479
+ }
480
+ }
481
+ async function deleteApp(app, prismaDevModulePathHint) {
482
+ const appRec = state[app];
483
+ if (!appRec) return;
484
+ const entries = Object.values(appRec.databases);
485
+ for (const db of entries) {
486
+ const server = runtimes.get(db.instanceName);
487
+ if (server) {
488
+ await server.close().catch(() => void 0);
489
+ runtimes.delete(db.instanceName);
490
+ }
491
+ }
492
+ if (entries.length > 0 && prismaDevModulePathHint) {
493
+ const internalState = await importPrismaDevInternalState(prismaDevModulePathHint);
494
+ for (const db of entries) {
495
+ if (!runtimes.has(db.instanceName)) {
496
+ if ((await serverStatusOf(internalState, db.instanceName)).pid !== process.pid) await internalState.killServer(db.instanceName).catch(() => void 0);
497
+ }
498
+ if (!await settledAfterClose(internalState, db.instanceName)) {
499
+ console.error(`postgres-main: server "${db.instanceName}" still reports running after close — leaving its persisted data in place instead of risking a self-kill.`);
500
+ continue;
501
+ }
502
+ await internalState.deleteServer(db.instanceName);
503
+ }
504
+ }
505
+ delete state[app];
506
+ schedulePersist();
507
+ }
508
+ function json(res, status, body) {
509
+ res.writeHead(status, { "content-type": "application/json" });
510
+ res.end(JSON.stringify(body));
511
+ }
512
+ function text(res, status, body) {
513
+ res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
514
+ res.end(body);
515
+ }
516
+ function badSegment(res, segment) {
517
+ text(res, 400, `invalid path segment "${segment}": must match /^[a-z0-9][a-z0-9-]*$/ and be at most 63 characters`);
518
+ }
519
+ function databaseView(db) {
520
+ return {
521
+ id: db.id,
522
+ url: db.url,
523
+ instanceName: db.instanceName,
524
+ databasePort: db.databasePort
525
+ };
526
+ }
527
+ /** The most recently observed `prismaDevModulePath` for any database of `app` — DELETE has no body of its own to carry one. */
528
+ const recentPrismaDevModulePath = /* @__PURE__ */ new Map();
529
+ function lastKnownPrismaDevModulePath(app) {
530
+ return recentPrismaDevModulePath.get(app);
531
+ }
532
+ async function handleRequest(req, res) {
533
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
534
+ const method = req.method ?? "GET";
535
+ const segments = url.pathname.split("/").filter((s) => s.length > 0).map((s) => decodeURIComponent(s));
536
+ if (method === "GET" && segments.length === 1 && segments[0] === "health") return json(res, 200, { version: ownVersion });
537
+ if (segments[0] === "apps" && segments.length >= 2) {
538
+ const app = segments[1];
539
+ if (app === void 0 || !isValidSegment(app)) return badSegment(res, app ?? "");
540
+ if (method === "PUT" && segments.length === 4 && segments[2] === "databases") {
541
+ const id = segments[3];
542
+ if (id === void 0 || !isValidSegment(id)) return badSegment(res, id ?? "");
543
+ const raw = await readBody(req);
544
+ let parsed;
545
+ try {
546
+ parsed = JSON.parse(raw.toString("utf8"));
547
+ } catch {
548
+ return text(res, 400, "malformed JSON body");
549
+ }
550
+ if (!isDatabaseBody(parsed)) return text(res, 400, "malformed database body: expected { \"prismaDevModulePath\": string }");
551
+ recentPrismaDevModulePath.set(app, parsed.prismaDevModulePath);
552
+ return json(res, 200, await ensureDatabase(app, id, parsed.prismaDevModulePath));
553
+ }
554
+ if (method === "GET" && segments.length === 3 && segments[2] === "databases") {
555
+ const appRec = state[app];
556
+ return json(res, 200, appRec ? Object.values(appRec.databases).map(databaseView) : []);
557
+ }
558
+ if (method === "DELETE" && segments.length === 2) {
559
+ await deleteApp(app, lastKnownPrismaDevModulePath(app));
560
+ res.writeHead(204);
561
+ res.end();
562
+ return;
563
+ }
564
+ }
565
+ res.writeHead(404);
566
+ res.end();
567
+ }
568
+ const server = http.createServer((req, res) => {
569
+ handleRequest(req, res).catch((err) => {
570
+ if (!res.headersSent) res.writeHead(500, { "content-type": "text/plain" });
571
+ res.end(err instanceof Error ? maskCredentials(err.message) : String(err));
572
+ });
573
+ });
574
+ async function shutdown() {
575
+ for (const server of runtimes.values()) await server.close().catch(() => void 0);
576
+ await stateFile.flush();
577
+ server.close();
578
+ process.exit(0);
579
+ }
580
+ process.on("SIGTERM", () => void shutdown());
581
+ process.on("SIGINT", () => void shutdown());
582
+ process.on("unhandledRejection", (reason) => {
583
+ console.error("postgres-main: unhandled rejection from background work:", reason instanceof Error ? maskCredentials(reason.stack ?? reason.message) : reason);
584
+ });
585
+ process.on("uncaughtException", (err) => {
586
+ console.error("postgres-main: uncaught exception from background work:", err instanceof Error ? maskCredentials(err.stack ?? err.message) : err);
587
+ });
588
+ readJsonFile(appsJsonPath, isAppsState).then((loaded) => {
589
+ if (loaded) state = loaded;
590
+ server.listen(port, "127.0.0.1", () => {
591
+ console.log(`[dev-emulators] postgres-main listening on 127.0.0.1:${String(port)}`);
592
+ });
593
+ }).catch((err) => {
594
+ console.error("postgres-main: failed to load state", err);
595
+ process.exit(1);
596
+ });
597
+ }
598
+ main();
599
+ //#endregion
600
+ export {};
601
+
602
+ //# sourceMappingURL=postgres-main.mjs.map