@prisma/composer-prisma-cloud 0.2.0-dev.12 → 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.
Files changed (61) 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 +532 -0
  4. package/dist/compute-main.mjs.map +1 -0
  5. package/dist/control.mjs +41 -1257
  6. package/dist/control.mjs.map +1 -1
  7. package/dist/cron/index.mjs +2 -3
  8. package/dist/cron/index.mjs.map +1 -1
  9. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  10. package/dist/cron/scheduler-service.mjs +2 -3
  11. package/dist/cron/scheduler-service.mjs.map +1 -1
  12. package/dist/email/email-entrypoint.mjs.map +1 -1
  13. package/dist/email/email-service.mjs +2 -3
  14. package/dist/email/email-service.mjs.map +1 -1
  15. package/dist/email/index.mjs +2 -3
  16. package/dist/email/index.mjs.map +1 -1
  17. package/dist/email/testing.mjs.map +1 -1
  18. package/dist/get-port-BYnpbW5H.mjs +111 -0
  19. package/dist/get-port-BYnpbW5H.mjs.map +1 -0
  20. package/dist/index.mjs +4 -2
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/local-target.d.mts +12 -0
  23. package/dist/local-target.d.mts.map +1 -0
  24. package/dist/local-target.mjs +6129 -0
  25. package/dist/local-target.mjs.map +1 -0
  26. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs +66 -0
  27. package/dist/param-BTzY0ne5-CW6ojZ3E.mjs.map +1 -0
  28. package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs +79 -0
  29. package/dist/pg-connection-CadPZuEK-CF3FsSQu.mjs.map +1 -0
  30. package/dist/postgres-main.mjs +402 -0
  31. package/dist/postgres-main.mjs.map +1 -0
  32. package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs +1252 -0
  33. package/dist/preflight-names-BPL-qsZF-BfoRgbgb.mjs.map +1 -0
  34. package/dist/{prisma-next-BvBwGCIt-CSauMX4l.mjs → prisma-next-BvBwGCIt-DssgnctU.mjs} +3 -78
  35. package/dist/prisma-next-BvBwGCIt-DssgnctU.mjs.map +1 -0
  36. package/dist/prisma-next.mjs +1 -1
  37. package/dist/{provisioned-edges-B_XS1Mz--NFJgcXK7.mjs → provisioned-edges-C1wSO3cx-YC5C19f9.mjs} +5 -65
  38. package/dist/provisioned-edges-C1wSO3cx-YC5C19f9.mjs.map +1 -0
  39. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs +47 -0
  40. package/dist/secret-rz_ZqNeq-_xd97Jd9.mjs.map +1 -0
  41. package/dist/segments-NpKN-46R-B01DmCqy.mjs +1441 -0
  42. package/dist/segments-NpKN-46R-B01DmCqy.mjs.map +1 -0
  43. package/dist/{serializer-D8_84XWO-DA2B6YbX.mjs → serializer-CJiOhF2d-DJUjQj1i.mjs} +4 -44
  44. package/dist/serializer-CJiOhF2d-DJUjQj1i.mjs.map +1 -0
  45. package/dist/storage/index.mjs +2 -3
  46. package/dist/storage/index.mjs.map +1 -1
  47. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  48. package/dist/storage/storage-service.mjs +2 -3
  49. package/dist/storage/storage-service.mjs.map +1 -1
  50. package/dist/streams/index.mjs +2 -3
  51. package/dist/streams/index.mjs.map +1 -1
  52. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  53. package/dist/streams/streams-service.mjs +2 -3
  54. package/dist/streams/streams-service.mjs.map +1 -1
  55. package/dist/testing.mjs +1 -1
  56. package/dist/testing.mjs.map +1 -1
  57. package/package.json +21 -15
  58. package/src/exports/local-target.ts +1 -0
  59. package/dist/prisma-next-BvBwGCIt-CSauMX4l.mjs.map +0 -1
  60. package/dist/provisioned-edges-B_XS1Mz--NFJgcXK7.mjs.map +0 -1
  61. package/dist/serializer-D8_84XWO-DA2B6YbX.mjs.map +0 -1
@@ -0,0 +1,1252 @@
1
+ import { n as secretName } from "./secret-rz_ZqNeq-_xd97Jd9.mjs";
2
+ import { i as paramName, n as isEnvParamSource } from "./param-BTzY0ne5-CW6ojZ3E.mjs";
3
+ import { r as withConnectionRetry, t as normalizeSslMode } from "./pg-connection-CadPZuEK-CF3FsSQu.mjs";
4
+ import { paramManifest, provisionManifest } from "@prisma/composer";
5
+ import { createManagementApiClient } from "@prisma/management-api-sdk";
6
+ import * as Context from "effect/Context";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Layer from "effect/Layer";
9
+ import * as Redacted from "effect/Redacted";
10
+ import * as Config$1 from "effect/Config";
11
+ import * as Data from "effect/Data";
12
+ import * as Provider from "alchemy/Provider";
13
+ import { Resource } from "alchemy";
14
+ import * as Schedule from "effect/Schedule";
15
+ import * as fs from "node:fs";
16
+ import * as crypto$1 from "node:crypto";
17
+ import "node:crypto";
18
+ import * as os$1 from "node:os";
19
+ import * as path from "node:path";
20
+ import * as zlib from "node:zlib";
21
+ import pg from "pg";
22
+ import { readRef } from "@prisma-next/migration-tools/refs";
23
+ import { APP_SPACE_ID, readContractSpaceHeadRef, spaceMigrationDirectory, spaceRefsDirectory } from "@prisma-next/migration-tools/spaces";
24
+ import { createPostgresControlClient } from "@prisma-next/postgres/control";
25
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/http-CJM0E6rj.mjs
26
+ /**
27
+ * The Prisma service token used to authenticate Management API calls. Kept
28
+ * as a Redacted value so it never lands in logs or error output.
29
+ */
30
+ var PrismaCredentials = class extends Context.Service()("PrismaCredentials") {};
31
+ /** Resolve the token from the `PRISMA_SERVICE_TOKEN` environment variable. */
32
+ const fromEnv = () => Layer.effect(PrismaCredentials, Effect.gen(function* () {
33
+ return { token: yield* Config$1.redacted("PRISMA_SERVICE_TOKEN") };
34
+ }));
35
+ /**
36
+ * The typed Prisma Management API client, built once from the resolved
37
+ * credentials. Providers yield this in their outer Effect and call it inside
38
+ * `reconcile` / `delete`.
39
+ */
40
+ var ManagementClient = class extends Context.Service()("PrismaManagementClient") {};
41
+ const layer = () => Layer.effect(ManagementClient, Effect.gen(function* () {
42
+ const { token } = yield* PrismaCredentials;
43
+ return createManagementApiClient({ token: Redacted.value(token) });
44
+ }));
45
+ /** A non-2xx response from the Management API (or a transport failure). */
46
+ var PrismaApiError = class extends Data.TaggedError("PrismaApiError") {};
47
+ const attempt = (f) => Effect.tryPromise({
48
+ try: f,
49
+ catch: (cause) => new PrismaApiError({
50
+ status: 0,
51
+ message: String(cause)
52
+ })
53
+ });
54
+ const fail = (r) => Effect.fail(new PrismaApiError({
55
+ status: r.response.status,
56
+ message: JSON.stringify(r.error)
57
+ }));
58
+ /** Unwrap `data`, failing on any API error. Preserves the SDK's response type. */
59
+ const call = (f) => attempt(f).pipe(Effect.flatMap((r) => r.error !== void 0 || r.data === void 0 ? fail(r) : Effect.succeed(r.data)));
60
+ /** Unwrap `data`, mapping a 404 to `undefined` (resource gone / not found). */
61
+ const callOptional = (f) => attempt(f).pipe(Effect.flatMap((r) => r.response.status === 404 ? Effect.succeed(void 0) : r.error !== void 0 ? fail(r) : Effect.succeed(r.data)));
62
+ /** Fire-and-forget a call, tolerating a 404 (already deleted). */
63
+ const callVoid = (f) => attempt(f).pipe(Effect.flatMap((r) => r.response.status === 404 || r.error === void 0 ? Effect.void : fail(r)));
64
+ //#endregion
65
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/buckets.mjs
66
+ /** A Prisma **Object Store bucket** inside a project. */
67
+ const Bucket = Resource("Prisma.Bucket");
68
+ const BucketProvider = () => Provider.effect(Bucket, Effect.gen(function* () {
69
+ const client = yield* ManagementClient;
70
+ return {
71
+ stables: ["id"],
72
+ list: () => Effect.succeed([]),
73
+ reconcile: Effect.fn(function* ({ news, output }) {
74
+ const observed = output?.id ? yield* callOptional(() => client.GET("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } })) : void 0;
75
+ if (observed) return {
76
+ id: observed.data.id,
77
+ name: observed.data.name
78
+ };
79
+ const created = yield* call(() => client.POST("/v1/buckets", { body: {
80
+ projectId: news.projectId,
81
+ name: news.name,
82
+ ...news.branchId !== void 0 ? { branchId: news.branchId } : {}
83
+ } }));
84
+ return {
85
+ id: created.data.id,
86
+ name: created.data.name
87
+ };
88
+ }),
89
+ delete: Effect.fn(function* ({ output }) {
90
+ yield* callVoid(() => client.DELETE("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } }));
91
+ }),
92
+ read: Effect.fn(function* ({ output }) {
93
+ if (!output?.id) return void 0;
94
+ const b = yield* callOptional(() => client.GET("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } }));
95
+ return b ? {
96
+ id: b.data.id,
97
+ name: b.data.name
98
+ } : void 0;
99
+ })
100
+ };
101
+ }));
102
+ /** A **bucket access key** for a Prisma Object Store bucket — yields the S3 credentials. */
103
+ const BucketKey = Resource("Prisma.BucketKey");
104
+ const BucketKeyProvider = () => Provider.effect(BucketKey, Effect.gen(function* () {
105
+ const client = yield* ManagementClient;
106
+ return {
107
+ stables: [
108
+ "id",
109
+ "bucketId",
110
+ "secretAccessKey",
111
+ "accessKeyId",
112
+ "endpoint",
113
+ "bucketName"
114
+ ],
115
+ list: () => Effect.succeed([]),
116
+ reconcile: Effect.fn(function* ({ news, output }) {
117
+ if (output?.id) return output;
118
+ const created = yield* call(() => client.POST("/v1/buckets/{bucketId}/keys", {
119
+ params: { path: { bucketId: news.bucketId } },
120
+ body: {
121
+ name: news.name,
122
+ role: news.role
123
+ }
124
+ }));
125
+ return {
126
+ id: created.data.id,
127
+ bucketId: news.bucketId,
128
+ accessKeyId: created.data.accessKeyId,
129
+ secretAccessKey: Redacted.make(created.data.secretAccessKey),
130
+ endpoint: created.data.endpoint,
131
+ bucketName: created.data.bucketName
132
+ };
133
+ }),
134
+ delete: Effect.fn(function* ({ output }) {
135
+ yield* callVoid(() => client.DELETE("/v1/buckets/{bucketId}/keys/{keyId}", { params: { path: {
136
+ bucketId: output.bucketId,
137
+ keyId: output.id
138
+ } } }));
139
+ })
140
+ };
141
+ }));
142
+ //#endregion
143
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/compute.mjs
144
+ /**
145
+ * Stopping a deployment before the app that owns it can be
146
+ * deleted is asynchronous on the platform's side: DELETE can 409 with this
147
+ * message while the deployment is still winding down. Retrying blindly on
148
+ * every API error would mask real failures (bad auth, a genuinely conflicting
149
+ * state, etc.), so this only matches the platform's specific "not delete-safe
150
+ * yet" wording — everything else fails immediately, as before.
151
+ */
152
+ const isDeleteNotSafeYet = (error) => error.message.includes("did not reach a delete-safe state");
153
+ /**
154
+ * Backs off exponentially from 2s, capped at 5 minutes total — long enough
155
+ * for the platform to finish stopping the deployment, short enough to still
156
+ * fail loudly (rather than hang forever) if it never does.
157
+ */
158
+ const deleteSafeRetrySchedule = Schedule.both(Schedule.exponential("2 seconds", 2), Schedule.during("5 minutes"));
159
+ /** Every region Prisma Compute serves — the runtime source of truth; `ComputeRegion` is derived from it so the two can never drift. */
160
+ const COMPUTE_REGIONS = [
161
+ "us-east-1",
162
+ "us-west-1",
163
+ "eu-west-3",
164
+ "eu-central-1",
165
+ "ap-northeast-1",
166
+ "ap-southeast-1"
167
+ ];
168
+ /** A Prisma **Compute service** — the stable app identity behind a project. */
169
+ const ComputeService = Resource("Prisma.ComputeService");
170
+ const ComputeServiceProvider = () => Provider.effect(ComputeService, Effect.gen(function* () {
171
+ const client = yield* ManagementClient;
172
+ return {
173
+ stables: ["id"],
174
+ list: () => Effect.succeed([]),
175
+ reconcile: Effect.fn(function* ({ news, output }) {
176
+ const observed = output?.id ? yield* callOptional(() => client.GET("/v1/apps/{appId}", { params: { path: { appId: output.id } } })) : void 0;
177
+ if (observed) return {
178
+ id: observed.data.id,
179
+ name: observed.data.name,
180
+ endpointDomain: observed.data.appEndpointDomain
181
+ };
182
+ const created = yield* call(() => client.POST("/v1/apps", { body: {
183
+ displayName: news.name,
184
+ projectId: news.projectId,
185
+ ...news.region && { regionId: news.region },
186
+ ...news.branchId !== void 0 && { branchId: news.branchId }
187
+ } }));
188
+ return {
189
+ id: created.data.id,
190
+ name: created.data.name,
191
+ endpointDomain: created.data.appEndpointDomain
192
+ };
193
+ }),
194
+ delete: Effect.fn(function* ({ output }) {
195
+ yield* callVoid(() => client.DELETE("/v1/apps/{appId}", { params: { path: { appId: output.id } } })).pipe(Effect.retry({
196
+ schedule: deleteSafeRetrySchedule,
197
+ while: isDeleteNotSafeYet
198
+ }));
199
+ }),
200
+ read: Effect.fn(function* ({ output }) {
201
+ if (!output?.id) return void 0;
202
+ const s = yield* callOptional(() => client.GET("/v1/apps/{appId}", { params: { path: { appId: output.id } } }));
203
+ return s ? {
204
+ id: s.data.id,
205
+ name: s.data.name,
206
+ endpointDomain: s.data.appEndpointDomain
207
+ } : void 0;
208
+ })
209
+ };
210
+ }));
211
+ /**
212
+ * A **deployment** of a Prisma app — creates a deployment, uploads
213
+ * its artifact, starts the VM, waits for it to run, then promotes it to the
214
+ * app's stable endpoint.
215
+ */
216
+ const Deployment = Resource("Prisma.Deployment");
217
+ const DeploymentProvider = () => Provider.effect(Deployment, Effect.gen(function* () {
218
+ const client = yield* ManagementClient;
219
+ const waitForRunning = (deploymentId) => call(() => client.GET("/v1/deployments/{deploymentId}", { params: { path: { deploymentId } } })).pipe(Effect.flatMap((v) => v.data.status === "running" ? Effect.void : Effect.fail(new PrismaApiError({
220
+ status: 409,
221
+ message: `deployment ${deploymentId} is ${v.data.status}, not running`
222
+ }))), Effect.retry(Schedule.both(Schedule.spaced("2 seconds"), Schedule.during("2 minutes"))));
223
+ return {
224
+ stables: [],
225
+ list: () => Effect.succeed([]),
226
+ reconcile: Effect.fn(function* ({ news }) {
227
+ const created = yield* call(() => client.POST("/v1/apps/{appId}/deployments", {
228
+ params: { path: { appId: news.computeServiceId } },
229
+ body: news.port !== void 0 ? { portMapping: { http: news.port } } : {}
230
+ }));
231
+ const deploymentId = created.data.id;
232
+ if (created.data.uploadUrl) {
233
+ const uploadUrl = created.data.uploadUrl;
234
+ const artifact = yield* Effect.try({
235
+ try: () => fs.readFileSync(news.artifactPath),
236
+ catch: (cause) => new PrismaApiError({
237
+ status: 0,
238
+ message: `failed to read artifact ${news.artifactPath}: ${String(cause)}`
239
+ })
240
+ });
241
+ yield* Effect.tryPromise({
242
+ try: async () => {
243
+ const res = await fetch(uploadUrl, {
244
+ method: "PUT",
245
+ body: artifact
246
+ });
247
+ if (!res.ok) throw new PrismaApiError({
248
+ status: res.status,
249
+ message: `artifact upload failed: ${res.status} ${res.statusText}`
250
+ });
251
+ },
252
+ catch: (cause) => cause instanceof PrismaApiError ? cause : new PrismaApiError({
253
+ status: 0,
254
+ message: String(cause)
255
+ })
256
+ });
257
+ }
258
+ yield* call(() => client.POST("/v1/deployments/{deploymentId}/start", { params: { path: { deploymentId } } }));
259
+ yield* waitForRunning(deploymentId);
260
+ const deployedUrl = (yield* call(() => client.POST("/v1/apps/{appId}/promote", {
261
+ params: { path: { appId: news.computeServiceId } },
262
+ body: { deploymentId }
263
+ }))).data.appEndpointDomain;
264
+ return {
265
+ deploymentId,
266
+ ...deployedUrl !== void 0 && { deployedUrl }
267
+ };
268
+ }),
269
+ delete: Effect.fn(function* () {}),
270
+ read: Effect.fn(function* ({ output }) {
271
+ if (!output?.deploymentId) return void 0;
272
+ const v = yield* callOptional(() => client.GET("/v1/deployments/{deploymentId}", { params: { path: { deploymentId: output.deploymentId } } }));
273
+ return v ? {
274
+ deploymentId: v.data.id,
275
+ ...v.data.previewDomain && { deployedUrl: v.data.previewDomain }
276
+ } : void 0;
277
+ })
278
+ };
279
+ }));
280
+ /**
281
+ * A project-scoped **environment variable** that Compute injects into the
282
+ * project's services from their attached branch (e.g. wiring one module's URL into
283
+ * another).
284
+ */
285
+ const EnvironmentVariable = Resource("Prisma.EnvironmentVariable");
286
+ const EnvironmentVariableProvider = () => Provider.effect(EnvironmentVariable, Effect.gen(function* () {
287
+ const client = yield* ManagementClient;
288
+ return {
289
+ stables: ["id"],
290
+ list: () => Effect.succeed([]),
291
+ reconcile: Effect.fn(function* ({ news, output }) {
292
+ const cls = news.class ?? "production";
293
+ let id = output?.id;
294
+ if (id !== void 0) {
295
+ const priorId = id;
296
+ if (!(yield* callOptional(() => client.GET("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: priorId } } })))) id = void 0;
297
+ }
298
+ if (id === void 0) {
299
+ const matchId = (yield* call(() => client.GET("/v1/environment-variables", { params: { query: {
300
+ projectId: news.projectId,
301
+ class: cls,
302
+ key: news.key
303
+ } } }))).data?.[0]?.id;
304
+ if (matchId !== void 0) {
305
+ if (!(news.key === "DATABASE_URL" || news.key === "DATABASE_URL_POOLED")) throw new Error(`EnvironmentVariable "${news.key}" (project "${news.projectId}", class "${cls}") exists but is untracked in this deploy state — refusing to overwrite a reserved COMPOSER_ key. Restore this deploy's hosted state, or remove the variable to let this deploy recreate it.`);
306
+ id = matchId;
307
+ }
308
+ }
309
+ if (id !== void 0) {
310
+ const targetId = id;
311
+ yield* call(() => client.PATCH("/v1/environment-variables/{envVarId}", {
312
+ params: { path: { envVarId: targetId } },
313
+ body: { value: news.value }
314
+ }));
315
+ return {
316
+ id,
317
+ key: news.key
318
+ };
319
+ }
320
+ const created = yield* call(() => client.POST("/v1/environment-variables", { body: {
321
+ projectId: news.projectId,
322
+ class: cls,
323
+ key: news.key,
324
+ value: news.value,
325
+ ...news.branchId ? { branchId: news.branchId } : {}
326
+ } }));
327
+ return {
328
+ id: created.data.id,
329
+ key: created.data.key
330
+ };
331
+ }),
332
+ delete: Effect.fn(function* ({ output }) {
333
+ yield* callVoid(() => client.DELETE("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: output.id } } }));
334
+ }),
335
+ read: Effect.fn(function* ({ output }) {
336
+ if (!output?.id) return void 0;
337
+ const v = yield* callOptional(() => client.GET("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: output.id } } }));
338
+ return v ? {
339
+ id: v.data.id,
340
+ key: v.data.key
341
+ } : void 0;
342
+ })
343
+ };
344
+ }));
345
+ /**
346
+ * Assembles a Prisma Compute artifact: the app-built bundle plus the
347
+ * extension-printed bootstrap and manifest, tarred and gzipped deterministically
348
+ * (fixed mtimes, sorted entry order) so an unchanged service noops on
349
+ * redeploy — a rebuild is the only thing that changes the hash. Lives here
350
+ * (not in @prisma/composer-prisma-cloud/control) because it needs node:fs/node:zlib,
351
+ * which the extension's shipped src may never import (invariant 5).
352
+ */
353
+ const MANIFEST_VERSION = "1";
354
+ /** Finds main.js/main.mjs in a bundle dir when no explicit entry is given. */
355
+ function resolveEntry(bundleDir, entry) {
356
+ if (entry !== void 0) return entry;
357
+ const found = fs.readdirSync(bundleDir).find((f) => /^main\.m?js$/.test(f));
358
+ if (found === void 0) throw new Error(`no main.js/main.mjs found in bundle dir ${bundleDir}`);
359
+ return found;
360
+ }
361
+ /** All files under `dir`, as dir-relative POSIX paths, in sorted order. A
362
+ * symlink is a hard error: deploy bundles must be flat (ADR-0005), and the
363
+ * user's build owns flattening — dereferencing here would relink the tree and
364
+ * risk packaging files from outside it. */
365
+ function walkFiles(dir) {
366
+ const out = [];
367
+ const visit = (sub) => {
368
+ for (const entry of fs.readdirSync(path.join(dir, sub), { withFileTypes: true })) {
369
+ const rel = sub.length > 0 ? `${sub}/${entry.name}` : entry.name;
370
+ if (entry.isSymbolicLink()) throw new Error(`bundle contains a symlink at ${rel} — deploy bundles must be flat; materialize links in your build (e.g. cp -RL) so the tree is self-contained.`);
371
+ if (entry.isDirectory()) visit(rel);
372
+ else out.push(rel);
373
+ }
374
+ };
375
+ visit("");
376
+ return out.sort();
377
+ }
378
+ function octal(value, length) {
379
+ return `${value.toString(8).padStart(length - 1, "0")}\0`;
380
+ }
381
+ /** Splits a path into ustar's name (<=100 bytes) + prefix (<=155 bytes) fields. */
382
+ function splitUstarPath(relPath) {
383
+ if (Buffer.byteLength(relPath, "utf8") <= 100) return {
384
+ name: relPath,
385
+ prefix: ""
386
+ };
387
+ for (let i = relPath.length - 1; i >= 0; i--) {
388
+ if (relPath[i] !== "/") continue;
389
+ const prefix = relPath.slice(0, i);
390
+ const name = relPath.slice(i + 1);
391
+ if (Buffer.byteLength(prefix, "utf8") <= 155 && Buffer.byteLength(name, "utf8") <= 100) return {
392
+ name,
393
+ prefix
394
+ };
395
+ }
396
+ throw new Error(`path too long for a ustar tar entry: ${relPath}`);
397
+ }
398
+ function ustarHeader(relPath, size) {
399
+ const { name, prefix } = splitUstarPath(relPath);
400
+ const buf = Buffer.alloc(512);
401
+ buf.write(name, 0, 100, "utf8");
402
+ buf.write(octal(420, 8), 100, 8, "utf8");
403
+ buf.write(octal(0, 8), 108, 8, "utf8");
404
+ buf.write(octal(0, 8), 116, 8, "utf8");
405
+ buf.write(octal(size, 12), 124, 12, "utf8");
406
+ buf.write(octal(0, 12), 136, 12, "utf8");
407
+ buf.write(" ", 148, 8, "utf8");
408
+ buf.write("0", 156, 1, "utf8");
409
+ buf.write("ustar\0", 257, 6, "utf8");
410
+ buf.write("00", 263, 2, "utf8");
411
+ buf.write(prefix, 345, 155, "utf8");
412
+ let sum = 0;
413
+ for (const b of buf) sum += b;
414
+ buf.write(`${sum.toString(8).padStart(6, "0")}\0 `, 148, 8, "utf8");
415
+ return buf;
416
+ }
417
+ function createDeterministicTarGz(entries) {
418
+ const sorted = [...entries].sort((a, b) => a.relPath.localeCompare(b.relPath));
419
+ const chunks = [];
420
+ for (const entry of sorted) {
421
+ chunks.push(ustarHeader(entry.relPath, entry.content.length));
422
+ chunks.push(entry.content);
423
+ const pad = (512 - entry.content.length % 512) % 512;
424
+ if (pad > 0) chunks.push(Buffer.alloc(pad));
425
+ }
426
+ chunks.push(Buffer.alloc(1024));
427
+ return zlib.gzipSync(Buffer.concat(chunks));
428
+ }
429
+ /**
430
+ * Prints the bootstrap + manifest and tars them with the bundle into a
431
+ * deterministic artifact. If bundleDir doesn't exist (e.g. `alchemy destroy`
432
+ * run before any build), returns a placeholder rather than throwing — the
433
+ * artifact is never read on destroy.
434
+ */
435
+ function packageComputeArtifact(opts) {
436
+ if (!fs.existsSync(opts.bundleDir)) return {
437
+ path: "",
438
+ sha256: "absent"
439
+ };
440
+ const bootstrap = `import main from "./${resolveEntry(opts.bundleDir, opts.bundleEntry)}";\nawait main.run(${JSON.stringify(opts.address)}, () => import(${JSON.stringify(`./${opts.appEntry}`)}));\n`;
441
+ const manifest = `${JSON.stringify({
442
+ manifestVersion: MANIFEST_VERSION,
443
+ entrypoint: "bootstrap.js",
444
+ address: opts.address
445
+ }, null, 2)}\n`;
446
+ const files = walkFiles(opts.bundleDir).map((relPath) => ({
447
+ relPath,
448
+ content: fs.readFileSync(path.join(opts.bundleDir, relPath))
449
+ }));
450
+ files.push({
451
+ relPath: "bootstrap.js",
452
+ content: Buffer.from(bootstrap, "utf8")
453
+ });
454
+ files.push({
455
+ relPath: "compute.manifest.json",
456
+ content: Buffer.from(manifest, "utf8")
457
+ });
458
+ files.push({
459
+ relPath: "bunfig.toml",
460
+ content: Buffer.from("[install]\nauto = \"disable\"\n", "utf8")
461
+ });
462
+ const gz = createDeterministicTarGz(files);
463
+ const sha256 = crypto$1.createHash("sha256").update(gz).digest("hex");
464
+ const outDir = path.join(os$1.tmpdir(), `prisma-composer-compute-${String(os$1.userInfo().uid)}`, sha256.slice(0, 16));
465
+ fs.mkdirSync(outDir, { recursive: true });
466
+ const outPath = path.join(outDir, `${opts.id}.tar.gz`);
467
+ const tmpPath = path.join(outDir, `.${opts.id}.${crypto$1.randomUUID()}.tmp`);
468
+ fs.writeFileSync(tmpPath, gz);
469
+ fs.renameSync(tmpPath, outPath);
470
+ return {
471
+ path: outPath,
472
+ sha256
473
+ };
474
+ }
475
+ /**
476
+ * The `ServiceKey` Alchemy resource (ADR-0030) — mints a random 256-bit key
477
+ * ONCE at create and keeps it STABLE across deploys, so an unchanged edge
478
+ * no-ops on redeploy. Same mint-once-stable lifecycle as `S3Credentials`
479
+ * (`packages/1-prisma-cloud/1-extensions/target/src/s3-credentials-resource.ts`):
480
+ * Web Crypto only (`crypto.getRandomValues` — no `node:` import), persisted in
481
+ * Alchemy state; on every later apply the provider returns the persisted
482
+ * attributes (`reconcile`'s `output`) unchanged. One resource per RPC edge;
483
+ * rotation is destroy/recreate.
484
+ */
485
+ /** The `ServiceKey` resource constructor — `yield* Prisma.ServiceKey(id, {})` in the lowering. */
486
+ const ServiceKey = Resource("PrismaCloud.ServiceKey");
487
+ /** A fresh 256-bit key as 64 lowercase hex chars (Web Crypto — no node import). */
488
+ function mintServiceKey() {
489
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32));
490
+ return { value: Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("") };
491
+ }
492
+ /**
493
+ * The `ServiceKey` provider service. `reconcile` runs for create and update;
494
+ * it returns the persisted `output` when present (a redeploy reuses the
495
+ * stored key — the no-op property) and mints a fresh key only on first
496
+ * create. Nothing to enumerate (`list` → `[]`) or tear down (`delete` →
497
+ * no-op; the key lives only in state).
498
+ */
499
+ const serviceKeyProviderService = {
500
+ list: () => Effect.succeed([]),
501
+ reconcile: ({ output }) => Effect.sync(() => output ?? mintServiceKey()),
502
+ delete: () => Effect.void
503
+ };
504
+ /** The `ServiceKey` provider layer — merged into the Prisma Cloud extension's `providers()`. */
505
+ const ServiceKeyProvider = () => Provider.effect(ServiceKey, Effect.succeed(serviceKeyProviderService));
506
+ //#endregion
507
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/postgres.mjs
508
+ /** A **connection** to a Prisma Postgres database — yields the connection string. */
509
+ const Connection = Resource("Prisma.Connection");
510
+ const ConnectionProvider = () => Provider.effect(Connection, Effect.gen(function* () {
511
+ const client = yield* ManagementClient;
512
+ return {
513
+ stables: ["id", "connectionString"],
514
+ list: () => Effect.succeed([]),
515
+ reconcile: Effect.fn(function* ({ news, output }) {
516
+ if (output?.id) return output;
517
+ const created = yield* call(() => client.POST("/v1/databases/{databaseId}/connections", {
518
+ params: { path: { databaseId: news.databaseId } },
519
+ body: { name: news.name }
520
+ }));
521
+ const endpoints = created.data.endpoints;
522
+ const dsn = endpoints?.direct?.connectionString ?? endpoints?.pooled?.connectionString;
523
+ if (dsn === void 0) return yield* Effect.fail(new PrismaApiError({
524
+ status: 0,
525
+ message: `connection ${created.data.id} returned no direct/pooled connection string`
526
+ }));
527
+ return {
528
+ id: created.data.id,
529
+ connectionString: Redacted.make(dsn)
530
+ };
531
+ }),
532
+ delete: Effect.fn(function* ({ output }) {
533
+ yield* callVoid(() => client.DELETE("/v1/connections/{id}", { params: { path: { id: output.id } } }));
534
+ })
535
+ };
536
+ }));
537
+ /** A Prisma **Postgres database** inside a project. */
538
+ const Database = Resource("Prisma.Database");
539
+ const DatabaseProvider = () => Provider.effect(Database, Effect.gen(function* () {
540
+ const client = yield* ManagementClient;
541
+ return {
542
+ stables: ["id"],
543
+ list: () => Effect.succeed([]),
544
+ reconcile: Effect.fn(function* ({ news, output }) {
545
+ const observed = output?.id ? yield* callOptional(() => client.GET("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } })) : void 0;
546
+ if (!observed) {
547
+ const created = yield* call(() => client.POST("/v1/databases", { body: {
548
+ projectId: news.projectId,
549
+ name: news.name,
550
+ region: news.region,
551
+ ...news.isDefault !== void 0 && { isDefault: news.isDefault },
552
+ ...news.branchId !== void 0 && { branchId: news.branchId }
553
+ } }));
554
+ return {
555
+ id: created.data.id,
556
+ name: created.data.name
557
+ };
558
+ }
559
+ const result = {
560
+ id: observed.data.id,
561
+ name: observed.data.name
562
+ };
563
+ if (news.branchId !== void 0) {
564
+ const branchId = news.branchId;
565
+ yield* call(() => client.PATCH("/v1/databases/{databaseId}", {
566
+ params: { path: { databaseId: result.id } },
567
+ body: { branchId }
568
+ }));
569
+ }
570
+ return result;
571
+ }),
572
+ delete: Effect.fn(function* ({ output }) {
573
+ yield* callVoid(() => client.DELETE("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } }));
574
+ }),
575
+ read: Effect.fn(function* ({ output }) {
576
+ if (!output?.id) return void 0;
577
+ const d = yield* callOptional(() => client.GET("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } }));
578
+ return d ? {
579
+ id: d.data.id,
580
+ name: d.data.name
581
+ } : void 0;
582
+ })
583
+ };
584
+ }));
585
+ /** A Prisma Developer Platform **Project** — the container for databases and compute services. */
586
+ const Project = Resource("Prisma.Project");
587
+ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
588
+ const client = yield* ManagementClient;
589
+ return {
590
+ stables: ["id"],
591
+ list: () => Effect.succeed([]),
592
+ reconcile: Effect.fn(function* ({ news, output }) {
593
+ const observed = output?.id ? yield* callOptional(() => client.GET("/v1/projects/{id}", { params: { path: { id: output.id } } })) : void 0;
594
+ if (observed) return {
595
+ id: observed.data.id,
596
+ name: observed.data.name
597
+ };
598
+ const created = yield* call(() => client.POST("/v1/projects", { body: {
599
+ name: news.name,
600
+ workspaceId: news.workspaceId
601
+ } }));
602
+ return {
603
+ id: created.data.id,
604
+ name: created.data.name
605
+ };
606
+ }),
607
+ delete: Effect.fn(function* ({ output }) {
608
+ yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: output.id } } }));
609
+ }),
610
+ read: Effect.fn(function* ({ output }) {
611
+ if (!output?.id) return void 0;
612
+ const p = yield* callOptional(() => client.GET("/v1/projects/{id}", { params: { path: { id: output.id } } }));
613
+ return p ? {
614
+ id: p.data.id,
615
+ name: p.data.name
616
+ } : void 0;
617
+ })
618
+ };
619
+ }));
620
+ //#endregion
621
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/index.mjs
622
+ /** Raised with `ensure: false` when the app's Project (or a named stage's Branch) doesn't exist. */
623
+ var ContainerNotFoundError = class extends Data.TaggedError("ContainerNotFoundError") {};
624
+ const listAllProjects = (client) => Effect.gen(function* () {
625
+ const projects = [];
626
+ let cursor;
627
+ for (;;) {
628
+ const query = cursor === void 0 ? {} : { cursor };
629
+ const page = yield* call(() => client.GET("/v1/projects", { params: { query } }));
630
+ projects.push(...page.data);
631
+ if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
632
+ cursor = page.pagination.nextCursor;
633
+ }
634
+ return projects;
635
+ });
636
+ /**
637
+ * Workspace ids circulate in two shapes: `wksp_`-prefixed and bare. Compare
638
+ * bare-to-bare so a `wksp_`-prefixed API id still matches a bare configured
639
+ * one (the same normalization `state/bootstrap.ts` applies to the same
640
+ * `/v1/projects` listing).
641
+ */
642
+ const bareWorkspaceId = (id) => id.startsWith("wksp_") ? id.slice(5) : id;
643
+ /**
644
+ * Finds the app's Project by name in the workspace — PDP allows duplicate
645
+ * project names, so more than one can match; the oldest wins. Creates one
646
+ * if none match, unless `ensure` is `false` (find-only — `destroy`), in
647
+ * which case an absent Project fails with `ContainerNotFoundError`. No
648
+ * ownership marker and no `--project` override (both deferred — see
649
+ * ADR-0019).
650
+ */
651
+ const resolveProject = (client, workspaceId, appName, ensure) => Effect.gen(function* () {
652
+ const oldest = (yield* listAllProjects(client)).filter((p) => bareWorkspaceId(p.workspace.id) === bareWorkspaceId(workspaceId) && p.name === appName).sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
653
+ if (oldest !== void 0) return oldest.id;
654
+ if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({ appName }));
655
+ return (yield* call(() => client.POST("/v1/projects", { body: {
656
+ name: appName,
657
+ workspaceId
658
+ } }))).data.id;
659
+ });
660
+ const findBranchId = (client, projectId, gitName) => call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
661
+ path: { projectId },
662
+ query: { gitName }
663
+ } })).pipe(Effect.map((page) => page.data[0]?.id));
664
+ /**
665
+ * Finds the stage's Branch by its exact `gitName`, creating it if absent
666
+ * unless `ensure` is `false` (find-only — `destroy`), in which case an
667
+ * absent Branch fails with `ContainerNotFoundError`. The Management API has
668
+ * no server-side "create-or-return" idempotency (`POST
669
+ * /v1/projects/:id/branches` 409s on a duplicate `gitName`, with no request
670
+ * field to make that a no-op), so idempotency is client-side: observe
671
+ * first, and on a racing 409 from create, re-observe rather than fail.
672
+ */
673
+ const resolveBranch = (client, projectId, gitName, appName, ensure) => Effect.gen(function* () {
674
+ const existing = yield* findBranchId(client, projectId, gitName);
675
+ if (existing !== void 0) return existing;
676
+ if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({
677
+ appName,
678
+ stage: gitName
679
+ }));
680
+ return yield* call(() => client.POST("/v1/projects/{projectId}/branches", {
681
+ params: { path: { projectId } },
682
+ body: { gitName }
683
+ })).pipe(Effect.map((r) => r.data.id), Effect.catch((err) => err.status === 409 ? findBranchId(client, projectId, gitName).pipe(Effect.flatMap((id) => id === void 0 ? Effect.fail(err) : Effect.succeed(id))) : Effect.fail(err)));
684
+ });
685
+ /**
686
+ * Resolves the two containers a stage's deploy runs into (ADR-0019): the
687
+ * app's **Project**, found-or-created by name, and — for a named stage
688
+ * only — its **Branch**, found-or-created by `gitName`. The default stage
689
+ * (no `stage`) creates no Branch; `branchId` is omitted. With `ensure:
690
+ * false` (`destroy`), nothing is created — an absent Project or Branch
691
+ * fails with `ContainerNotFoundError` instead.
692
+ */
693
+ const resolveContainer = (opts) => Effect.gen(function* () {
694
+ const client = yield* ManagementClient;
695
+ const ensure = opts.ensure ?? true;
696
+ const projectId = yield* resolveProject(client, opts.workspaceId, opts.appName, ensure);
697
+ if (opts.stage === void 0) return { projectId };
698
+ return {
699
+ projectId,
700
+ branchId: yield* resolveBranch(client, projectId, opts.stage, opts.appName, ensure)
701
+ };
702
+ });
703
+ /**
704
+ * Soft-deletes a Branch. Tolerates a 404 (already gone). The API refuses if
705
+ * the Branch still has live members or is the production/default Branch —
706
+ * that surfaces as a `PrismaApiError`.
707
+ */
708
+ const deleteBranch = (branchId) => Effect.gen(function* () {
709
+ const client = yield* ManagementClient;
710
+ yield* callVoid(() => client.DELETE("/v1/branches/{branchId}", { params: { path: { branchId } } }));
711
+ });
712
+ /**
713
+ * Deletes a Project. Tolerates a 404 (already gone). The API refuses with a
714
+ * 400 if the Project still has live dependencies (e.g. another stage's
715
+ * Branch/resources) — that surfaces as a `PrismaApiError`.
716
+ */
717
+ const deleteProject = (projectId) => Effect.gen(function* () {
718
+ const client = yield* ManagementClient;
719
+ yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: projectId } } }));
720
+ });
721
+ /** The collection of Prisma resource providers. */
722
+ var Providers = class extends Provider.ProviderCollection()("Prisma") {};
723
+ /**
724
+ * The Prisma provider bundle: every resource provider, the Management API
725
+ * client, and env-based credentials. Plug into a stack with
726
+ * `{ providers: Prisma.providers() }`.
727
+ */
728
+ const providers = () => Layer.effect(Providers, Provider.collection([
729
+ Project,
730
+ Database,
731
+ Connection,
732
+ ComputeService,
733
+ Deployment,
734
+ EnvironmentVariable,
735
+ Bucket,
736
+ BucketKey
737
+ ])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DatabaseProvider(), ConnectionProvider(), ComputeServiceProvider(), DeploymentProvider(), EnvironmentVariableProvider(), BucketProvider(), BucketKeyProvider())), Layer.provideMerge(layer()), Layer.provideMerge(fromEnv()), Layer.orDie);
738
+ //#endregion
739
+ //#region ../../1-prisma-cloud/0-lowering/s3-protocol/dist/index.mjs
740
+ /**
741
+ * AWS SigV4 verification for the S3 wire protocol (spec § 2 auth). The payload
742
+ * hash comes from the client — `x-amz-content-sha256` (a real hash or
743
+ * `UNSIGNED-PAYLOAD`) for header auth, `UNSIGNED-PAYLOAD` for presign — and is
744
+ * never re-hashed; the verifier trusts what was signed, like a real S3 endpoint.
745
+ * Runtime engine code (`node:crypto`); not re-exported from the authoring barrel.
746
+ *
747
+ * Also owns `mintKeyPair` (local-dev spec § 1) — the one key-pair generator
748
+ * both the deploy-time `S3Credentials` resource and the local dev bucket
749
+ * emulator's credential provisioning use.
750
+ */
751
+ function randomBytes$1(n) {
752
+ return crypto.getRandomValues(new Uint8Array(n));
753
+ }
754
+ function toHexUpper(bytes) {
755
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("").toUpperCase();
756
+ }
757
+ /** A fresh SigV4 key pair: an AKIA-prefixed id and a 40-char base64 secret. */
758
+ function mintKeyPair() {
759
+ return {
760
+ accessKeyId: `AKIA${toHexUpper(randomBytes$1(8))}`,
761
+ secretAccessKey: btoa(String.fromCharCode(...randomBytes$1(30)))
762
+ };
763
+ }
764
+ //#endregion
765
+ //#region ../../1-prisma-cloud/1-extensions/target/dist/preflight-names-BPL-qsZF.mjs
766
+ const PRISMA_CLOUD_EXTENSION_ID = "@prisma/composer-prisma-cloud";
767
+ var PrismaCloudContainer = class {
768
+ input;
769
+ projectId;
770
+ branchId;
771
+ constructor(input, projectId, branchId) {
772
+ this.input = input;
773
+ this.projectId = projectId;
774
+ this.branchId = branchId;
775
+ }
776
+ serialize() {
777
+ return JSON.stringify({
778
+ input: this.input,
779
+ projectId: this.projectId,
780
+ ...this.branchId !== void 0 ? { branchId: this.branchId } : {}
781
+ });
782
+ }
783
+ };
784
+ /** `instanceof` — parent-side instances and child-side deserialized instances are both constructed by this module. */
785
+ function isPrismaCloudContainer(value) {
786
+ return value instanceof PrismaCloudContainer;
787
+ }
788
+ /** Narrow-or-throw for hook inputs. */
789
+ function prismaCloudContainerOf(value) {
790
+ if (!isPrismaCloudContainer(value)) throw new Error("the Prisma Cloud container was not resolved — the extension's container descriptor did not run.");
791
+ return value;
792
+ }
793
+ function isRecord(value) {
794
+ return typeof value === "object" && value !== null;
795
+ }
796
+ function invalidPayloadError(reason) {
797
+ return /* @__PURE__ */ new Error(`${PRISMA_CLOUD_EXTENSION_ID}: invalid container transport payload — ${reason}.`);
798
+ }
799
+ /**
800
+ * Reconstructs a `PrismaCloudContainer` from `serialize()`'s JSON output —
801
+ * real narrowing, no casts. Exported so `dev/container.ts`'s
802
+ * `devContainerDescriptor` can reuse it verbatim (local-dev spec § 5) — the
803
+ * dev and deploy container descriptors deserialize the identical wire shape.
804
+ */
805
+ function deserialize(serialized) {
806
+ let parsed;
807
+ try {
808
+ parsed = JSON.parse(serialized);
809
+ } catch (error) {
810
+ throw invalidPayloadError(`not valid JSON (${error instanceof Error ? error.message : String(error)})`);
811
+ }
812
+ if (!isRecord(parsed)) throw invalidPayloadError("not an object");
813
+ const input = parsed["input"];
814
+ if (!isRecord(input)) throw invalidPayloadError("\"input\" is not an object");
815
+ const appName = input["appName"];
816
+ if (typeof appName !== "string") throw invalidPayloadError("\"input.appName\" is not a string");
817
+ const stage = input["stage"];
818
+ if (stage !== void 0 && typeof stage !== "string") throw invalidPayloadError("\"input.stage\" is not a string or absent");
819
+ const projectId = parsed["projectId"];
820
+ if (typeof projectId !== "string") throw invalidPayloadError("\"projectId\" is not a string");
821
+ const branchId = parsed["branchId"];
822
+ if (branchId !== void 0 && typeof branchId !== "string") throw invalidPayloadError("\"branchId\" is not a string or absent");
823
+ return new PrismaCloudContainer({
824
+ appName,
825
+ stage
826
+ }, projectId, branchId);
827
+ }
828
+ const workspaceRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_WORKSPACE_ID is required.");
829
+ const tokenRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_SERVICE_TOKEN is required.");
830
+ function requireWorkspaceId() {
831
+ const workspaceId = process.env["PRISMA_WORKSPACE_ID"];
832
+ if (workspaceId === void 0 || workspaceId.length === 0) throw workspaceRequiredError();
833
+ return workspaceId;
834
+ }
835
+ function requireTokenUnlessInjected(deps) {
836
+ if (deps?.client === void 0 && (process.env["PRISMA_SERVICE_TOKEN"] ?? "").length === 0) throw tokenRequiredError();
837
+ }
838
+ async function ensureContainer(input, deps) {
839
+ const workspaceId = requireWorkspaceId();
840
+ requireTokenUnlessInjected(deps);
841
+ const program = resolveContainer({
842
+ workspaceId,
843
+ appName: input.appName,
844
+ ...input.stage !== void 0 ? { stage: input.stage } : {},
845
+ ensure: true
846
+ }).pipe(Effect.map((c) => ({
847
+ ok: true,
848
+ container: c
849
+ })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
850
+ ok: false,
851
+ message: `Prisma Management API error resolving containers: ${e.message}.`
852
+ })));
853
+ const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
854
+ const outcome = await Effect.runPromise(provided);
855
+ if (!outcome.ok) throw new Error(outcome.message);
856
+ return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
857
+ }
858
+ async function locateContainer(input, deps) {
859
+ const workspaceId = requireWorkspaceId();
860
+ requireTokenUnlessInjected(deps);
861
+ const program = resolveContainer({
862
+ workspaceId,
863
+ appName: input.appName,
864
+ ...input.stage !== void 0 ? { stage: input.stage } : {},
865
+ ensure: false
866
+ }).pipe(Effect.map((c) => ({
867
+ ok: true,
868
+ container: c
869
+ })), Effect.catchTag("ContainerNotFoundError", () => Effect.succeed({ ok: false })), Effect.catchTag("PrismaApiError", (e) => Effect.fail(/* @__PURE__ */ new Error(`Prisma Management API error resolving containers: ${e.message}.`))));
870
+ const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
871
+ const outcome = await Effect.runPromise(provided);
872
+ if (!outcome.ok) return void 0;
873
+ return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
874
+ }
875
+ /**
876
+ * Soft-deletes a named stage's Branch after a successful `alchemy destroy`
877
+ * has removed its members — the Management API refuses to delete a Branch
878
+ * that still has live members.
879
+ */
880
+ async function removeStageBranch(branchId, deps) {
881
+ requireTokenUnlessInjected(deps);
882
+ const program = deleteBranch(branchId).pipe(Effect.map(() => ({ ok: true })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
883
+ ok: false,
884
+ message: `Failed to delete the stage Branch: ${e.message}.`
885
+ })));
886
+ const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
887
+ const outcome = await Effect.runPromise(provided);
888
+ if (!outcome.ok) throw new Error(outcome.message);
889
+ }
890
+ /**
891
+ * Best-effort cleanup after a successful `--production` destroy: removes
892
+ * the app's Project so hand-run stacks don't accumulate as empty Projects
893
+ * (they eventually hit the workspace's plan limit). Unlike `removeStageBranch`,
894
+ * this never throws: the destroy itself already succeeded, and the API's own
895
+ * 400 ("still has dependencies") is the only check that matters — failing
896
+ * the command over a cleanup step would be worse than leaving a Project shell.
897
+ */
898
+ async function removeAppProject(projectId, deps) {
899
+ if (deps?.client === void 0 && (process.env["PRISMA_SERVICE_TOKEN"] ?? "").length === 0) {
900
+ console.warn(`Skipped removing the Project (${projectId}): PRISMA_SERVICE_TOKEN is not set.`);
901
+ return;
902
+ }
903
+ const program = deleteProject(projectId).pipe(Effect.map(() => ({ ok: true })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
904
+ ok: false,
905
+ error: e
906
+ })));
907
+ const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
908
+ const outcome = await Effect.runPromise(provided);
909
+ if (outcome.ok) {
910
+ console.log(`Removed the Project (${projectId}) — nothing was left in it.`);
911
+ return;
912
+ }
913
+ if (outcome.error.status === 400) {
914
+ console.log(`Kept the Project (${projectId}) — it still has another stage's resources.`);
915
+ return;
916
+ }
917
+ console.warn(`Could not remove the Project (${projectId}) after destroy: ${outcome.error.message}.`);
918
+ }
919
+ function containerDescriptor(deps) {
920
+ return {
921
+ ensure: (input) => ensureContainer(input, deps),
922
+ locate: (input) => locateContainer(input, deps),
923
+ remove: (instance) => instance.input.stage !== void 0 ? removeStageBranch(instance.branchId ?? missingBranchId(instance), deps) : removeAppProject(instance.projectId, deps),
924
+ deserialize
925
+ };
926
+ }
927
+ /** Defensive: a named-stage container always resolves a Branch together with its stage — `ensure`/`locate`/`deserialize` never produce one without the other. */
928
+ function missingBranchId(instance) {
929
+ throw new Error(`${PRISMA_CLOUD_EXTENSION_ID}: a named-stage ("${instance.input.stage}") container instance is missing its branchId — this is a bug in ensure/locate/deserialize.`);
930
+ }
931
+ /**
932
+ * The `PgWarm` Alchemy resource (slice 3, FT-5226) — warm a freshly-provisioned
933
+ * Prisma Postgres database at apply-time so it is ready by deploy-end, and the
934
+ * first real connection (a service's runtime client, or the migration) doesn't
935
+ * eat the cold-start reject.
936
+ *
937
+ * The DB `url` is a lazy `Output` at lowering time, so warming must be an
938
+ * apply-time tracked resource (same pattern as `PnMigration`): its `reconcile`
939
+ * receives the RESOLVED url and connects with `withConnectionRetry` + `select 1`,
940
+ * riding out the cold-start. Shared by BOTH the bare-`postgres` and the
941
+ * `prisma-next` lowerings; keyed on the connection `url`, so an unchanged
942
+ * redeploy is a no-op (warming is idempotent anyway).
943
+ *
944
+ * Deploy-time only: imports `pg` directly + `alchemy`. Imported by `control.ts`
945
+ * and tests, never by `index.ts` / the `./prisma-next` authoring entry — the
946
+ * isolation invariants hold.
947
+ */
948
+ /** The `PgWarm` resource constructor — `yield* PgWarm(id, { url })` in a lowering. */
949
+ const PgWarm = Resource("PrismaCloud.PgWarm");
950
+ /** Connect (retrying the cold-start) and run `select 1`, then release the connection. */
951
+ async function warmDatabase(url) {
952
+ await withConnectionRetry(async () => {
953
+ const client = new pg.Client({ connectionString: normalizeSslMode(url) });
954
+ await client.connect();
955
+ try {
956
+ await client.query("select 1");
957
+ } finally {
958
+ await client.end();
959
+ }
960
+ });
961
+ }
962
+ /**
963
+ * The `PgWarm` provider service. `reconcile` warms the DB (retrying the
964
+ * cold-start) and echoes the `url` so a downstream resource that reads
965
+ * `warm.url` runs only after the DB is warm. Idempotent — safe on redeploy;
966
+ * nothing to enumerate (`list` → `[]`) or tear down (`delete` → no-op; the DB's
967
+ * own deletion handles teardown). Exported so tests can drive it directly.
968
+ */
969
+ const pgWarmProviderService = {
970
+ list: () => Effect.succeed([]),
971
+ reconcile: ({ news }) => Effect.tryPromise({
972
+ try: () => warmDatabase(news.url),
973
+ catch: (error) => error
974
+ }).pipe(Effect.map(() => ({ url: news.url }))),
975
+ delete: () => Effect.void
976
+ };
977
+ /** The `PgWarm` provider layer — merged into the extension descriptor's `providers()`. */
978
+ const PgWarmProvider = () => Provider.effect(PgWarm, Effect.succeed(pgWarmProviderService));
979
+ /**
980
+ * The Prisma Next migration step of the deploy lowering (ADR-0022, slice 2) —
981
+ * the safety-critical decision that brings a live database to a target REF
982
+ * using ONLY Prisma Next's authored migrations.
983
+ *
984
+ * Deploy-time only: this module imports `@prisma-next/postgres/control` (which
985
+ * transitively pulls PN's control/migration machinery + `pg`). It is imported
986
+ * by the deploy descriptors and this package's tests, NEVER by `index.ts` / the
987
+ * `./prisma-next` authoring entry — so it never lands in an app runtime bundle
988
+ * (the index-isolation invariant holds).
989
+ *
990
+ * The target is a ref `{ hash, invariants }` — not a bare `storageHash`. A
991
+ * ref's `invariants` are named postconditions established by `data`-class
992
+ * migration steps (e.g. a backfill), recorded monotonically on the live
993
+ * marker. Keying on the hash alone would make a pure data-invariant change an
994
+ * A→A self-edge the deploy wrongly skips. The decision, given the live marker
995
+ * and the target ref (see {@link decideMigrationAction}):
996
+ * - marker at ref.hash AND ref.invariants ⊆ marker.invariants → no-op
997
+ * - no marker (fresh DB) AND no required invariants → `dbInit`
998
+ * - otherwise → `migrate`
999
+ *
1000
+ * `dbInit` is additive-only synthesis — it NEVER runs app-space data steps —
1001
+ * so it is only correct when the ref requires no invariants; a fresh DB whose
1002
+ * target carries invariants goes through `migrate`, which walks the AUTHORED
1003
+ * graph (including the invariant-bearing data migrations) from empty.
1004
+ *
1005
+ * Never `dbUpdate`: synthesized diff-and-apply plans are never run against a
1006
+ * deployed database. A no-authored-path (`MIGRATION_PATH_NOT_FOUND`) or a
1007
+ * runner failure fails the deploy as a typed `PnMigrationError` (not swallowed).
1008
+ * PN applies each migration in its own transaction, so a failed apply is atomic
1009
+ * and resume-safe — the marker and schema are left as the last committed step.
1010
+ */
1011
+ /** A deploy-failing migration error — surfaced, never swallowed. */
1012
+ var PnMigrationError = class extends Error {
1013
+ code;
1014
+ /** PN's structured explanation, when present. */
1015
+ why;
1016
+ constructor(code, summary, why) {
1017
+ super(`prisma-next migrate (${code}): ${summary}`);
1018
+ this.name = "PnMigrationError";
1019
+ this.code = code;
1020
+ this.why = why;
1021
+ }
1022
+ };
1023
+ /**
1024
+ * The target `storageHash` a contract heads to — `contractJson.storage.storageHash`.
1025
+ * Read defensively: `contractJson` crosses the boundary as `unknown`.
1026
+ */
1027
+ function targetStorageHash(contractJson) {
1028
+ if (typeof contractJson === "object" && contractJson !== null && "storage" in contractJson) {
1029
+ const storage = contractJson.storage;
1030
+ if (typeof storage === "object" && storage !== null && "storageHash" in storage) {
1031
+ const hash = storage.storageHash;
1032
+ if (typeof hash === "string" && hash.length > 0) return hash;
1033
+ }
1034
+ }
1035
+ throw new PnMigrationError("INIT_FAILED", "the contract has no storage.storageHash — cannot determine the target schema version");
1036
+ }
1037
+ /**
1038
+ * Resolve the deploy's target ref from the migrations dir.
1039
+ *
1040
+ * - `targetRef` named: read `migrations/app/refs/<name>.json` — fail loudly
1041
+ * (`TARGET_REF_NOT_FOUND`) when the ref doesn't exist or can't be parsed.
1042
+ * - Default: the app space's head. PN synthesizes the app head from the
1043
+ * emitted contract — `{ hash: contract.storage.storageHash, invariants: [] }`
1044
+ * (`contract emit` writes no app-space `refs/head.json` today; extension
1045
+ * spaces have one on disk). When a future PN version does emit one, the
1046
+ * on-disk `head.json` wins — read via `readContractSpaceHeadRef`, exactly
1047
+ * the loader PN's own migrate uses.
1048
+ */
1049
+ async function resolveTargetRef(migrationsDir, contractJson, targetRef) {
1050
+ if (targetRef !== void 0) {
1051
+ const refsDir = spaceRefsDirectory(spaceMigrationDirectory(migrationsDir, APP_SPACE_ID));
1052
+ try {
1053
+ const ref = await readRef(refsDir, targetRef);
1054
+ return {
1055
+ hash: ref.hash,
1056
+ invariants: ref.invariants
1057
+ };
1058
+ } catch (error) {
1059
+ throw new PnMigrationError("TARGET_REF_NOT_FOUND", `targetRef "${targetRef}" could not be read from ${refsDir}`, error instanceof Error ? error.message : String(error));
1060
+ }
1061
+ }
1062
+ const head = await readContractSpaceHeadRef(migrationsDir, APP_SPACE_ID);
1063
+ if (head !== null) return {
1064
+ hash: head.hash,
1065
+ invariants: head.invariants
1066
+ };
1067
+ return {
1068
+ hash: targetStorageHash(contractJson),
1069
+ invariants: []
1070
+ };
1071
+ }
1072
+ /**
1073
+ * The pure migration decision, mirroring PN's own verifier: the database is
1074
+ * AT the target when the marker's hash equals the ref's hash AND every ref
1075
+ * invariant is on the marker (marker invariants are monotonic). `dbInit` is
1076
+ * additive-only synthesis, so it is chosen only for a fresh DB whose
1077
+ * effective required invariants (`ref.invariants − marker.invariants`) are
1078
+ * empty; anything else — different hash, missing invariant (the A→A
1079
+ * data-only self-edge), or a fresh DB with required invariants — walks the
1080
+ * authored graph via `migrate`.
1081
+ */
1082
+ function decideMigrationAction(marker, ref) {
1083
+ const markerInvariants = new Set(marker?.invariants ?? []);
1084
+ const missing = ref.invariants.filter((id) => !markerInvariants.has(id));
1085
+ if (marker !== null && marker.storageHash === ref.hash && missing.length === 0) return "noop";
1086
+ if (marker === null && missing.length === 0) return "init";
1087
+ return "migrate";
1088
+ }
1089
+ /**
1090
+ * Bring the database at `url` to the target ref via PN's authored migrations.
1091
+ * Reads the live marker, decides no-op / init / migrate
1092
+ * ({@link decideMigrationAction}), applies, and throws a typed
1093
+ * {@link PnMigrationError} on a no-path or runner failure. `migrationsDir` is
1094
+ * the on-disk migrations root and `ref` the resolved target
1095
+ * ({@link resolveTargetRef} — both resolved by the lowering, which also keys
1096
+ * the PnMigration resource on them). `refName` (the resource's `targetRef`,
1097
+ * when set) is threaded into `migrate` so PN targets the named ref's hash and
1098
+ * plans an invariant-bearing path.
1099
+ */
1100
+ async function applyPnMigration(opts) {
1101
+ const connection = normalizeSslMode(opts.url);
1102
+ return withConnectionRetry(() => runMigration(connection, opts.contractJson, opts.migrationsDir, opts.ref, opts.refName), { shouldRetry: (error) => !(error instanceof PnMigrationError) });
1103
+ }
1104
+ async function runMigration(connection, contractJson, migrationsDir, ref, refName) {
1105
+ const client = createPostgresControlClient({ connection });
1106
+ await client.connect();
1107
+ try {
1108
+ const marker = await client.readMarker();
1109
+ const markerHashBefore = marker?.storageHash ?? null;
1110
+ const action = decideMigrationAction(marker, ref);
1111
+ if (action === "noop") return {
1112
+ action,
1113
+ targetHash: ref.hash,
1114
+ markerHashBefore
1115
+ };
1116
+ if (action === "init") {
1117
+ const result = await client.dbInit({
1118
+ contract: contractJson,
1119
+ mode: "apply",
1120
+ migrationsDir
1121
+ });
1122
+ if (!result.ok) throw new PnMigrationError("INIT_FAILED", result.failure.summary, result.failure.why);
1123
+ return {
1124
+ action,
1125
+ targetHash: ref.hash,
1126
+ markerHashBefore
1127
+ };
1128
+ }
1129
+ const result = await client.migrate({
1130
+ contract: contractJson,
1131
+ migrationsDir,
1132
+ ...refName !== void 0 ? {
1133
+ refHash: ref.hash,
1134
+ refInvariants: ref.invariants,
1135
+ refName
1136
+ } : {}
1137
+ });
1138
+ if (!result.ok) throw new PnMigrationError(result.failure.code === "MIGRATION_PATH_NOT_FOUND" ? "MIGRATION_PATH_NOT_FOUND" : "RUNNER_FAILED", result.failure.summary, result.failure.why);
1139
+ return {
1140
+ action,
1141
+ targetHash: ref.hash,
1142
+ markerHashBefore
1143
+ };
1144
+ } finally {
1145
+ await client.close();
1146
+ }
1147
+ }
1148
+ /**
1149
+ * The `PnMigration` Alchemy resource (ADR-0022, slice 2 D2) — the migration
1150
+ * step modeled as a tracked resource so it participates in deploy state: keyed
1151
+ * on the target REF identity (`targetHash` + sorted `invariants`), an
1152
+ * unchanged redeploy is an Alchemy-level no-op (on top of the marker read),
1153
+ * and a contract change — or a DATA-ONLY change that adds a ref invariant at
1154
+ * the same hash — re-runs the migration.
1155
+ *
1156
+ * Its provider's `reconcile` receives the RESOLVED props at apply-time — in
1157
+ * particular the concrete DB `url` (a lazy `Output` until the Connection
1158
+ * provisions) — and delegates to the proven `applyPnMigration` decision. The
1159
+ * provider is a standalone `Provider<PnMigration>` layer; the extension
1160
+ * descriptor merges it into its `providers()` (`Layer.merge(Prisma.providers(),
1161
+ * PnMigrationProvider())`), and Alchemy resolves it at apply via a direct
1162
+ * provider-tag lookup (`tryFindProviderByType`) — no change to `@internal/lowering`.
1163
+ *
1164
+ * Deploy-time only: imports `@prisma-next/postgres/control` (via the helper) +
1165
+ * `alchemy`. Imported by `control.ts` and tests, never by `index.ts` / the
1166
+ * `./prisma-next` authoring entry — index isolation holds.
1167
+ */
1168
+ /** The `PnMigration` resource constructor — `yield* PnMigration(id, props)` in the lowering. */
1169
+ const PnMigration = Resource("PrismaNext.Migration");
1170
+ /**
1171
+ * The `PnMigration` provider service. `reconcile` runs for both create and
1172
+ * update (Alchemy's unified lifecycle); `applyPnMigration` is idempotent via
1173
+ * the live marker read, so it is safe to run for either — the marker decides
1174
+ * no-op / init / migrate. A migration has nothing to enumerate (`list` → `[]`)
1175
+ * and nothing to tear down on its own (`delete` → no-op; the DB's own deletion
1176
+ * handles teardown). Exported so tests can drive `reconcile` directly, without
1177
+ * building an Effect layer.
1178
+ */
1179
+ const pnMigrationProviderService = {
1180
+ list: () => Effect.succeed([]),
1181
+ reconcile: ({ news }) => Effect.tryPromise({
1182
+ try: () => applyPnMigration({
1183
+ url: news.url,
1184
+ contractJson: news.contractJson,
1185
+ migrationsDir: news.migrationsDir,
1186
+ ref: {
1187
+ hash: news.targetHash,
1188
+ invariants: news.invariants
1189
+ },
1190
+ ...news.refName !== void 0 ? { refName: news.refName } : {}
1191
+ }),
1192
+ catch: (error) => error
1193
+ }).pipe(Effect.map((outcome) => ({
1194
+ storageHash: outcome.targetHash,
1195
+ invariants: news.invariants
1196
+ }))),
1197
+ delete: () => Effect.void
1198
+ };
1199
+ /** The `PnMigration` provider layer — merged into the extension descriptor's `providers()`. */
1200
+ const PnMigrationProvider = () => Provider.effect(PnMigration, Effect.succeed(pnMigrationProviderService));
1201
+ /**
1202
+ * The `S3Credentials` Alchemy resource (S5) — mints a random SigV4 key pair
1203
+ * ONCE at create and keeps it STABLE across deploys, so an unchanged module
1204
+ * no-ops on redeploy. The pair is generated by `@internal/s3-protocol`'s
1205
+ * `mintKeyPair` (moved there per the local-dev spec § 1, so deploy and local
1206
+ * dev credential minting share one implementation) and persisted in Alchemy
1207
+ * state; on every later apply the provider returns the persisted attributes
1208
+ * (`reconcile`'s `output`) unchanged — the same way the postgres resource
1209
+ * keeps a Connection stable. Rotation is destroy/recreate (a platform ask,
1210
+ * not solved here).
1211
+ *
1212
+ * Deploy-time only: imports `alchemy`. Imported by `control.ts` and tests,
1213
+ * never by `index.ts` / the authoring entry.
1214
+ */
1215
+ /** The `S3Credentials` resource constructor — `yield* S3Credentials(id, {})` in the lowering. */
1216
+ const S3Credentials = Resource("PrismaCloud.S3Credentials");
1217
+ /**
1218
+ * The `S3Credentials` provider service. `reconcile` runs for create and update;
1219
+ * it returns the persisted `output` when present (a redeploy reuses the stored
1220
+ * pair — the no-op property) and mints a fresh pair only on first create.
1221
+ * Nothing to enumerate (`list` → `[]`) or tear down (`delete` → no-op; the pair
1222
+ * lives only in state). Exported so tests can drive it directly.
1223
+ */
1224
+ const s3CredentialsProviderService = {
1225
+ list: () => Effect.succeed([]),
1226
+ reconcile: ({ output }) => Effect.sync(() => output ?? mintKeyPair()),
1227
+ delete: () => Effect.void
1228
+ };
1229
+ /** The `S3Credentials` provider layer — merged into the extension descriptor's `providers()`. */
1230
+ const S3CredentialsProvider = () => Provider.effect(S3Credentials, Effect.succeed(s3CredentialsProviderService));
1231
+ function dedupedNames(entries) {
1232
+ const byName = /* @__PURE__ */ new Map();
1233
+ for (const entry of entries) if (!byName.has(entry.name)) byName.set(entry.name, entry);
1234
+ return [...byName.values()];
1235
+ }
1236
+ /** Walks `graph.secrets`/`graph.params` exactly as `runPreflight` did before this extraction. */
1237
+ function collectPreflightNames(graph) {
1238
+ return {
1239
+ secrets: dedupedNames(provisionManifest(graph).map((binding) => ({
1240
+ name: secretName(binding),
1241
+ serviceAddress: binding.serviceAddress
1242
+ }))),
1243
+ envParams: dedupedNames(paramManifest(graph).filter((binding) => isEnvParamSource(binding.binding)).map((binding) => ({
1244
+ name: paramName(binding),
1245
+ serviceAddress: binding.serviceAddress
1246
+ })))
1247
+ };
1248
+ }
1249
+ //#endregion
1250
+ export { PrismaApiError as A, EnvironmentVariable as C, Bucket as D, packageComputeArtifact as E, callVoid as M, fromEnv as N, BucketKey as O, layer as P, Deployment as S, ServiceKeyProvider as T, Connection as _, PnMigrationProvider as a, COMPUTE_REGIONS as b, S3CredentialsProvider as c, deserialize as d, prismaCloudContainerOf as f, providers as g, Providers as h, PnMigration as i, call as j, ManagementClient as k, collectPreflightNames as l, mintKeyPair as m, PgWarm as n, PrismaCloudContainer as o, resolveTargetRef as p, PgWarmProvider as r, S3Credentials as s, PRISMA_CLOUD_EXTENSION_ID as t, containerDescriptor as u, Database as v, ServiceKey as w, ComputeService as x, Project as y };
1251
+
1252
+ //# sourceMappingURL=preflight-names-BPL-qsZF-BfoRgbgb.mjs.map