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

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
package/dist/control.mjs CHANGED
@@ -1,746 +1,22 @@
1
- import { a as encodeParamPointer, c as secretName, i as encode, l as secretPointerRows, s as paramEntries, t as configKey } from "./serializer-CX4VYdf_-KKGoAxfx.mjs";
2
- import { a as isEnvParamSource, c as provisionedEdges, n as STREAMS_API_KEY, o as paramBindingFor, s as paramName, t as RESERVED_PROVIDER_PARAMS } from "./provisioned-edges-DIQAR4q4-Bn9op-JG.mjs";
3
- import { a as normalizeSslMode, n as isPnPostgresResourceNode, o as withConnectionRetry } from "./prisma-next-DlU01kXJ-2sP5mQb6.mjs";
4
- import { isParamSource, paramManifest, provisionManifest } from "@prisma/composer";
1
+ import { a as encode, l as secretPointerRows, n as configKey, o as encodeParamPointer, s as paramEntries } from "./serializer-CJiOhF2d-DJUjQj1i.mjs";
2
+ import { a as provisionedEdges, n as SELF_ORIGIN, r as STREAMS_API_KEY, t as RESERVED_PROVIDER_PARAMS } from "./provisioned-edges-C1wSO3cx-YC5C19f9.mjs";
3
+ import { i as paramName, r as paramBindingFor } from "./param-BTzY0ne5-CW6ojZ3E.mjs";
4
+ import { A as PrismaApiError, C as EnvironmentVariable, D as Bucket, E as packageComputeArtifact, M as callVoid, N as fromEnv, O as BucketKey, P as layer, S as Deployment, T as ServiceKeyProvider, _ as Connection, a as PnMigrationProvider, b as COMPUTE_REGIONS, c as S3CredentialsProvider, f as prismaCloudContainerOf, g as providers, i as PnMigration, j as call, k as ManagementClient, l as collectPreflightNames, n as PgWarm, p as resolveTargetRef, r as PgWarmProvider, s as S3Credentials, t as PRISMA_CLOUD_EXTENSION_ID, u as containerDescriptor, v as Database, w as ServiceKey, x as ComputeService } from "./preflight-names-BPL-qsZF-BfoRgbgb.mjs";
5
+ import { n as isPnPostgresResourceNode } from "./prisma-next-BvBwGCIt-DssgnctU.mjs";
6
+ import { isParamSource } from "@prisma/composer";
5
7
  import { blindCast } from "@prisma/composer/casts";
6
8
  import { RPC_PEER_KEY } from "@prisma/composer/service-rpc";
7
- import pg from "pg";
8
- import { createManagementApiClient } from "@prisma/management-api-sdk";
9
- import * as Context from "effect/Context";
10
9
  import * as Effect from "effect/Effect";
11
10
  import * as Layer from "effect/Layer";
12
11
  import * as Redacted from "effect/Redacted";
13
- import * as Config$1 from "effect/Config";
14
12
  import * as Data from "effect/Data";
15
- import * as Provider from "alchemy/Provider";
16
- import { Resource, Stack } from "alchemy";
13
+ import { Stack } from "alchemy";
17
14
  import * as Schedule from "effect/Schedule";
18
- import * as fs from "node:fs";
19
- import * as crypto$1 from "node:crypto";
20
- import * as os from "node:os";
21
- import * as path from "node:path";
22
- import * as zlib from "node:zlib";
23
15
  import postgres from "postgres";
24
16
  import { STATE_STORE_VERSION, State, StateStoreError, encodeState, reviveStateRecursive } from "alchemy/State";
25
17
  import * as Output from "alchemy/Output";
26
18
  import { loadConfig } from "@prisma-next/config-loader";
27
19
  import { resolve } from "pathe";
28
- import { readRef } from "@prisma-next/migration-tools/refs";
29
- import { APP_SPACE_ID, readContractSpaceHeadRef, spaceMigrationDirectory, spaceRefsDirectory } from "@prisma-next/migration-tools/spaces";
30
- import { createPostgresControlClient } from "@prisma-next/postgres/control";
31
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/http-CJM0E6rj.mjs
32
- /**
33
- * The Prisma service token used to authenticate Management API calls. Kept
34
- * as a Redacted value so it never lands in logs or error output.
35
- */
36
- var PrismaCredentials = class extends Context.Service()("PrismaCredentials") {};
37
- /** Resolve the token from the `PRISMA_SERVICE_TOKEN` environment variable. */
38
- const fromEnv = () => Layer.effect(PrismaCredentials, Effect.gen(function* () {
39
- return { token: yield* Config$1.redacted("PRISMA_SERVICE_TOKEN") };
40
- }));
41
- /**
42
- * The typed Prisma Management API client, built once from the resolved
43
- * credentials. Providers yield this in their outer Effect and call it inside
44
- * `reconcile` / `delete`.
45
- */
46
- var ManagementClient = class extends Context.Service()("PrismaManagementClient") {};
47
- const layer = () => Layer.effect(ManagementClient, Effect.gen(function* () {
48
- const { token } = yield* PrismaCredentials;
49
- return createManagementApiClient({ token: Redacted.value(token) });
50
- }));
51
- /** A non-2xx response from the Management API (or a transport failure). */
52
- var PrismaApiError = class extends Data.TaggedError("PrismaApiError") {};
53
- const attempt$1 = (f) => Effect.tryPromise({
54
- try: f,
55
- catch: (cause) => new PrismaApiError({
56
- status: 0,
57
- message: String(cause)
58
- })
59
- });
60
- const fail = (r) => Effect.fail(new PrismaApiError({
61
- status: r.response.status,
62
- message: JSON.stringify(r.error)
63
- }));
64
- /** Unwrap `data`, failing on any API error. Preserves the SDK's response type. */
65
- const call = (f) => attempt$1(f).pipe(Effect.flatMap((r) => r.error !== void 0 || r.data === void 0 ? fail(r) : Effect.succeed(r.data)));
66
- /** Unwrap `data`, mapping a 404 to `undefined` (resource gone / not found). */
67
- const callOptional = (f) => attempt$1(f).pipe(Effect.flatMap((r) => r.response.status === 404 ? Effect.succeed(void 0) : r.error !== void 0 ? fail(r) : Effect.succeed(r.data)));
68
- /** Fire-and-forget a call, tolerating a 404 (already deleted). */
69
- const callVoid = (f) => attempt$1(f).pipe(Effect.flatMap((r) => r.response.status === 404 || r.error === void 0 ? Effect.void : fail(r)));
70
- //#endregion
71
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/buckets.mjs
72
- /** A Prisma **Object Store bucket** inside a project. */
73
- const Bucket = Resource("Prisma.Bucket");
74
- const BucketProvider = () => Provider.effect(Bucket, Effect.gen(function* () {
75
- const client = yield* ManagementClient;
76
- return {
77
- stables: ["id"],
78
- list: () => Effect.succeed([]),
79
- reconcile: Effect.fn(function* ({ news, output }) {
80
- const observed = output?.id ? yield* callOptional(() => client.GET("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } })) : void 0;
81
- if (observed) return {
82
- id: observed.data.id,
83
- name: observed.data.name
84
- };
85
- const created = yield* call(() => client.POST("/v1/buckets", { body: {
86
- projectId: news.projectId,
87
- name: news.name,
88
- ...news.branchId !== void 0 ? { branchId: news.branchId } : {}
89
- } }));
90
- return {
91
- id: created.data.id,
92
- name: created.data.name
93
- };
94
- }),
95
- delete: Effect.fn(function* ({ output }) {
96
- yield* callVoid(() => client.DELETE("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } }));
97
- }),
98
- read: Effect.fn(function* ({ output }) {
99
- if (!output?.id) return void 0;
100
- const b = yield* callOptional(() => client.GET("/v1/buckets/{bucketId}", { params: { path: { bucketId: output.id } } }));
101
- return b ? {
102
- id: b.data.id,
103
- name: b.data.name
104
- } : void 0;
105
- })
106
- };
107
- }));
108
- /** A **bucket access key** for a Prisma Object Store bucket — yields the S3 credentials. */
109
- const BucketKey = Resource("Prisma.BucketKey");
110
- const BucketKeyProvider = () => Provider.effect(BucketKey, Effect.gen(function* () {
111
- const client = yield* ManagementClient;
112
- return {
113
- stables: [
114
- "id",
115
- "bucketId",
116
- "secretAccessKey",
117
- "accessKeyId",
118
- "endpoint",
119
- "bucketName"
120
- ],
121
- list: () => Effect.succeed([]),
122
- reconcile: Effect.fn(function* ({ news, output }) {
123
- if (output?.id) return output;
124
- const created = yield* call(() => client.POST("/v1/buckets/{bucketId}/keys", {
125
- params: { path: { bucketId: news.bucketId } },
126
- body: {
127
- name: news.name,
128
- role: news.role
129
- }
130
- }));
131
- return {
132
- id: created.data.id,
133
- bucketId: news.bucketId,
134
- accessKeyId: created.data.accessKeyId,
135
- secretAccessKey: Redacted.make(created.data.secretAccessKey),
136
- endpoint: created.data.endpoint,
137
- bucketName: created.data.bucketName
138
- };
139
- }),
140
- delete: Effect.fn(function* ({ output }) {
141
- yield* callVoid(() => client.DELETE("/v1/buckets/{bucketId}/keys/{keyId}", { params: { path: {
142
- bucketId: output.bucketId,
143
- keyId: output.id
144
- } } }));
145
- })
146
- };
147
- }));
148
- //#endregion
149
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/compute.mjs
150
- /**
151
- * Stopping a deployment before the app that owns it can be
152
- * deleted is asynchronous on the platform's side: DELETE can 409 with this
153
- * message while the deployment is still winding down. Retrying blindly on
154
- * every API error would mask real failures (bad auth, a genuinely conflicting
155
- * state, etc.), so this only matches the platform's specific "not delete-safe
156
- * yet" wording — everything else fails immediately, as before.
157
- */
158
- const isDeleteNotSafeYet = (error) => error.message.includes("did not reach a delete-safe state");
159
- /**
160
- * Backs off exponentially from 2s, capped at 5 minutes total — long enough
161
- * for the platform to finish stopping the deployment, short enough to still
162
- * fail loudly (rather than hang forever) if it never does.
163
- */
164
- const deleteSafeRetrySchedule = Schedule.both(Schedule.exponential("2 seconds", 2), Schedule.during("5 minutes"));
165
- /** Every region Prisma Compute serves — the runtime source of truth; `ComputeRegion` is derived from it so the two can never drift. */
166
- const COMPUTE_REGIONS = [
167
- "us-east-1",
168
- "us-west-1",
169
- "eu-west-3",
170
- "eu-central-1",
171
- "ap-northeast-1",
172
- "ap-southeast-1"
173
- ];
174
- /** A Prisma **Compute service** — the stable app identity behind a project. */
175
- const ComputeService = Resource("Prisma.ComputeService");
176
- const ComputeServiceProvider = () => Provider.effect(ComputeService, Effect.gen(function* () {
177
- const client = yield* ManagementClient;
178
- return {
179
- stables: ["id"],
180
- list: () => Effect.succeed([]),
181
- reconcile: Effect.fn(function* ({ news, output }) {
182
- const observed = output?.id ? yield* callOptional(() => client.GET("/v1/apps/{appId}", { params: { path: { appId: output.id } } })) : void 0;
183
- if (observed) return {
184
- id: observed.data.id,
185
- name: observed.data.name,
186
- endpointDomain: observed.data.appEndpointDomain
187
- };
188
- const created = yield* call(() => client.POST("/v1/apps", { body: {
189
- displayName: news.name,
190
- projectId: news.projectId,
191
- ...news.region && { regionId: news.region },
192
- ...news.branchId !== void 0 && { branchId: news.branchId }
193
- } }));
194
- return {
195
- id: created.data.id,
196
- name: created.data.name,
197
- endpointDomain: created.data.appEndpointDomain
198
- };
199
- }),
200
- delete: Effect.fn(function* ({ output }) {
201
- yield* callVoid(() => client.DELETE("/v1/apps/{appId}", { params: { path: { appId: output.id } } })).pipe(Effect.retry({
202
- schedule: deleteSafeRetrySchedule,
203
- while: isDeleteNotSafeYet
204
- }));
205
- }),
206
- read: Effect.fn(function* ({ output }) {
207
- if (!output?.id) return void 0;
208
- const s = yield* callOptional(() => client.GET("/v1/apps/{appId}", { params: { path: { appId: output.id } } }));
209
- return s ? {
210
- id: s.data.id,
211
- name: s.data.name,
212
- endpointDomain: s.data.appEndpointDomain
213
- } : void 0;
214
- })
215
- };
216
- }));
217
- /**
218
- * A **deployment** of a Prisma app — creates a deployment, uploads
219
- * its artifact, starts the VM, waits for it to run, then promotes it to the
220
- * app's stable endpoint.
221
- */
222
- const Deployment = Resource("Prisma.Deployment");
223
- const DeploymentProvider = () => Provider.effect(Deployment, Effect.gen(function* () {
224
- const client = yield* ManagementClient;
225
- 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({
226
- status: 409,
227
- message: `deployment ${deploymentId} is ${v.data.status}, not running`
228
- }))), Effect.retry(Schedule.both(Schedule.spaced("2 seconds"), Schedule.during("2 minutes"))));
229
- return {
230
- stables: [],
231
- list: () => Effect.succeed([]),
232
- reconcile: Effect.fn(function* ({ news }) {
233
- const created = yield* call(() => client.POST("/v1/apps/{appId}/deployments", {
234
- params: { path: { appId: news.computeServiceId } },
235
- body: news.port !== void 0 ? { portMapping: { http: news.port } } : {}
236
- }));
237
- const deploymentId = created.data.id;
238
- if (created.data.uploadUrl) {
239
- const uploadUrl = created.data.uploadUrl;
240
- const artifact = yield* Effect.try({
241
- try: () => fs.readFileSync(news.artifactPath),
242
- catch: (cause) => new PrismaApiError({
243
- status: 0,
244
- message: `failed to read artifact ${news.artifactPath}: ${String(cause)}`
245
- })
246
- });
247
- yield* Effect.tryPromise({
248
- try: async () => {
249
- const res = await fetch(uploadUrl, {
250
- method: "PUT",
251
- body: artifact
252
- });
253
- if (!res.ok) throw new PrismaApiError({
254
- status: res.status,
255
- message: `artifact upload failed: ${res.status} ${res.statusText}`
256
- });
257
- },
258
- catch: (cause) => cause instanceof PrismaApiError ? cause : new PrismaApiError({
259
- status: 0,
260
- message: String(cause)
261
- })
262
- });
263
- }
264
- yield* call(() => client.POST("/v1/deployments/{deploymentId}/start", { params: { path: { deploymentId } } }));
265
- yield* waitForRunning(deploymentId);
266
- const deployedUrl = (yield* call(() => client.POST("/v1/apps/{appId}/promote", {
267
- params: { path: { appId: news.computeServiceId } },
268
- body: { deploymentId }
269
- }))).data.appEndpointDomain;
270
- return {
271
- deploymentId,
272
- ...deployedUrl !== void 0 && { deployedUrl }
273
- };
274
- }),
275
- delete: Effect.fn(function* () {}),
276
- read: Effect.fn(function* ({ output }) {
277
- if (!output?.deploymentId) return void 0;
278
- const v = yield* callOptional(() => client.GET("/v1/deployments/{deploymentId}", { params: { path: { deploymentId: output.deploymentId } } }));
279
- return v ? {
280
- deploymentId: v.data.id,
281
- ...v.data.previewDomain && { deployedUrl: v.data.previewDomain }
282
- } : void 0;
283
- })
284
- };
285
- }));
286
- /**
287
- * A project-scoped **environment variable** that Compute injects into the
288
- * project's services from their attached branch (e.g. wiring one module's URL into
289
- * another).
290
- */
291
- const EnvironmentVariable = Resource("Prisma.EnvironmentVariable");
292
- const EnvironmentVariableProvider = () => Provider.effect(EnvironmentVariable, Effect.gen(function* () {
293
- const client = yield* ManagementClient;
294
- return {
295
- stables: ["id"],
296
- list: () => Effect.succeed([]),
297
- reconcile: Effect.fn(function* ({ news, output }) {
298
- const cls = news.class ?? "production";
299
- let id = output?.id;
300
- if (id !== void 0) {
301
- const priorId = id;
302
- if (!(yield* callOptional(() => client.GET("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: priorId } } })))) id = void 0;
303
- }
304
- if (id === void 0) {
305
- const matchId = (yield* call(() => client.GET("/v1/environment-variables", { params: { query: {
306
- projectId: news.projectId,
307
- class: cls,
308
- key: news.key
309
- } } }))).data?.[0]?.id;
310
- if (matchId !== void 0) {
311
- 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.`);
312
- id = matchId;
313
- }
314
- }
315
- if (id !== void 0) {
316
- const targetId = id;
317
- yield* call(() => client.PATCH("/v1/environment-variables/{envVarId}", {
318
- params: { path: { envVarId: targetId } },
319
- body: { value: news.value }
320
- }));
321
- return {
322
- id,
323
- key: news.key
324
- };
325
- }
326
- const created = yield* call(() => client.POST("/v1/environment-variables", { body: {
327
- projectId: news.projectId,
328
- class: cls,
329
- key: news.key,
330
- value: news.value,
331
- ...news.branchId ? { branchId: news.branchId } : {}
332
- } }));
333
- return {
334
- id: created.data.id,
335
- key: created.data.key
336
- };
337
- }),
338
- delete: Effect.fn(function* ({ output }) {
339
- yield* callVoid(() => client.DELETE("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: output.id } } }));
340
- }),
341
- read: Effect.fn(function* ({ output }) {
342
- if (!output?.id) return void 0;
343
- const v = yield* callOptional(() => client.GET("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: output.id } } }));
344
- return v ? {
345
- id: v.data.id,
346
- key: v.data.key
347
- } : void 0;
348
- })
349
- };
350
- }));
351
- /**
352
- * Assembles a Prisma Compute artifact: the app-built bundle plus the
353
- * extension-printed bootstrap and manifest, tarred and gzipped deterministically
354
- * (fixed mtimes, sorted entry order) so an unchanged service noops on
355
- * redeploy — a rebuild is the only thing that changes the hash. Lives here
356
- * (not in @prisma/composer-prisma-cloud/control) because it needs node:fs/node:zlib,
357
- * which the extension's shipped src may never import (invariant 5).
358
- */
359
- const MANIFEST_VERSION = "1";
360
- /** Finds main.js/main.mjs in a bundle dir when no explicit entry is given. */
361
- function resolveEntry(bundleDir, entry) {
362
- if (entry !== void 0) return entry;
363
- const found = fs.readdirSync(bundleDir).find((f) => /^main\.m?js$/.test(f));
364
- if (found === void 0) throw new Error(`no main.js/main.mjs found in bundle dir ${bundleDir}`);
365
- return found;
366
- }
367
- /** All files under `dir`, as dir-relative POSIX paths, in sorted order. A
368
- * symlink is a hard error: deploy bundles must be flat (ADR-0005), and the
369
- * user's build owns flattening — dereferencing here would relink the tree and
370
- * risk packaging files from outside it. */
371
- function walkFiles(dir) {
372
- const out = [];
373
- const visit = (sub) => {
374
- for (const entry of fs.readdirSync(path.join(dir, sub), { withFileTypes: true })) {
375
- const rel = sub.length > 0 ? `${sub}/${entry.name}` : entry.name;
376
- 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.`);
377
- if (entry.isDirectory()) visit(rel);
378
- else out.push(rel);
379
- }
380
- };
381
- visit("");
382
- return out.sort();
383
- }
384
- function octal(value, length) {
385
- return `${value.toString(8).padStart(length - 1, "0")}\0`;
386
- }
387
- /** Splits a path into ustar's name (<=100 bytes) + prefix (<=155 bytes) fields. */
388
- function splitUstarPath(relPath) {
389
- if (Buffer.byteLength(relPath, "utf8") <= 100) return {
390
- name: relPath,
391
- prefix: ""
392
- };
393
- for (let i = relPath.length - 1; i >= 0; i--) {
394
- if (relPath[i] !== "/") continue;
395
- const prefix = relPath.slice(0, i);
396
- const name = relPath.slice(i + 1);
397
- if (Buffer.byteLength(prefix, "utf8") <= 155 && Buffer.byteLength(name, "utf8") <= 100) return {
398
- name,
399
- prefix
400
- };
401
- }
402
- throw new Error(`path too long for a ustar tar entry: ${relPath}`);
403
- }
404
- function ustarHeader(relPath, size) {
405
- const { name, prefix } = splitUstarPath(relPath);
406
- const buf = Buffer.alloc(512);
407
- buf.write(name, 0, 100, "utf8");
408
- buf.write(octal(420, 8), 100, 8, "utf8");
409
- buf.write(octal(0, 8), 108, 8, "utf8");
410
- buf.write(octal(0, 8), 116, 8, "utf8");
411
- buf.write(octal(size, 12), 124, 12, "utf8");
412
- buf.write(octal(0, 12), 136, 12, "utf8");
413
- buf.write(" ", 148, 8, "utf8");
414
- buf.write("0", 156, 1, "utf8");
415
- buf.write("ustar\0", 257, 6, "utf8");
416
- buf.write("00", 263, 2, "utf8");
417
- buf.write(prefix, 345, 155, "utf8");
418
- let sum = 0;
419
- for (const b of buf) sum += b;
420
- buf.write(`${sum.toString(8).padStart(6, "0")}\0 `, 148, 8, "utf8");
421
- return buf;
422
- }
423
- function createDeterministicTarGz(entries) {
424
- const sorted = [...entries].sort((a, b) => a.relPath.localeCompare(b.relPath));
425
- const chunks = [];
426
- for (const entry of sorted) {
427
- chunks.push(ustarHeader(entry.relPath, entry.content.length));
428
- chunks.push(entry.content);
429
- const pad = (512 - entry.content.length % 512) % 512;
430
- if (pad > 0) chunks.push(Buffer.alloc(pad));
431
- }
432
- chunks.push(Buffer.alloc(1024));
433
- return zlib.gzipSync(Buffer.concat(chunks));
434
- }
435
- /**
436
- * Prints the bootstrap + manifest and tars them with the bundle into a
437
- * deterministic artifact. If bundleDir doesn't exist (e.g. `alchemy destroy`
438
- * run before any build), returns a placeholder rather than throwing — the
439
- * artifact is never read on destroy.
440
- */
441
- function packageComputeArtifact(opts) {
442
- if (!fs.existsSync(opts.bundleDir)) return {
443
- path: "",
444
- sha256: "absent"
445
- };
446
- const bootstrap = `import main from "./${resolveEntry(opts.bundleDir, opts.bundleEntry)}";\nawait main.run(${JSON.stringify(opts.address)}, () => import(${JSON.stringify(`./${opts.appEntry}`)}));\n`;
447
- const manifest = `${JSON.stringify({
448
- manifestVersion: MANIFEST_VERSION,
449
- entrypoint: "bootstrap.js"
450
- }, null, 2)}\n`;
451
- const files = walkFiles(opts.bundleDir).map((relPath) => ({
452
- relPath,
453
- content: fs.readFileSync(path.join(opts.bundleDir, relPath))
454
- }));
455
- files.push({
456
- relPath: "bootstrap.js",
457
- content: Buffer.from(bootstrap, "utf8")
458
- });
459
- files.push({
460
- relPath: "compute.manifest.json",
461
- content: Buffer.from(manifest, "utf8")
462
- });
463
- files.push({
464
- relPath: "bunfig.toml",
465
- content: Buffer.from("[install]\nauto = \"disable\"\n", "utf8")
466
- });
467
- const gz = createDeterministicTarGz(files);
468
- const sha256 = crypto$1.createHash("sha256").update(gz).digest("hex");
469
- const outDir = path.join(os.tmpdir(), `prisma-composer-compute-${String(os.userInfo().uid)}`, sha256.slice(0, 16));
470
- fs.mkdirSync(outDir, { recursive: true });
471
- const outPath = path.join(outDir, `${opts.id}.tar.gz`);
472
- const tmpPath = path.join(outDir, `.${opts.id}.${crypto$1.randomUUID()}.tmp`);
473
- fs.writeFileSync(tmpPath, gz);
474
- fs.renameSync(tmpPath, outPath);
475
- return {
476
- path: outPath,
477
- sha256
478
- };
479
- }
480
- /**
481
- * The `ServiceKey` Alchemy resource (ADR-0030) — mints a random 256-bit key
482
- * ONCE at create and keeps it STABLE across deploys, so an unchanged edge
483
- * no-ops on redeploy. Same mint-once-stable lifecycle as `S3Credentials`
484
- * (`packages/1-prisma-cloud/1-extensions/target/src/s3-credentials-resource.ts`):
485
- * Web Crypto only (`crypto.getRandomValues` — no `node:` import), persisted in
486
- * Alchemy state; on every later apply the provider returns the persisted
487
- * attributes (`reconcile`'s `output`) unchanged. One resource per RPC edge;
488
- * rotation is destroy/recreate.
489
- */
490
- /** The `ServiceKey` resource constructor — `yield* Prisma.ServiceKey(id, {})` in the lowering. */
491
- const ServiceKey = Resource("PrismaCloud.ServiceKey");
492
- /** A fresh 256-bit key as 64 lowercase hex chars (Web Crypto — no node import). */
493
- function mintServiceKey() {
494
- const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32));
495
- return { value: Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("") };
496
- }
497
- /**
498
- * The `ServiceKey` provider service. `reconcile` runs for create and update;
499
- * it returns the persisted `output` when present (a redeploy reuses the
500
- * stored key — the no-op property) and mints a fresh key only on first
501
- * create. Nothing to enumerate (`list` → `[]`) or tear down (`delete` →
502
- * no-op; the key lives only in state).
503
- */
504
- const serviceKeyProviderService = {
505
- list: () => Effect.succeed([]),
506
- reconcile: ({ output }) => Effect.sync(() => output ?? mintServiceKey()),
507
- delete: () => Effect.void
508
- };
509
- /** The `ServiceKey` provider layer — merged into the Prisma Cloud extension's `providers()`. */
510
- const ServiceKeyProvider = () => Provider.effect(ServiceKey, Effect.succeed(serviceKeyProviderService));
511
- //#endregion
512
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/postgres.mjs
513
- /** A **connection** to a Prisma Postgres database — yields the connection string. */
514
- const Connection = Resource("Prisma.Connection");
515
- const ConnectionProvider = () => Provider.effect(Connection, Effect.gen(function* () {
516
- const client = yield* ManagementClient;
517
- return {
518
- stables: ["id", "connectionString"],
519
- list: () => Effect.succeed([]),
520
- reconcile: Effect.fn(function* ({ news, output }) {
521
- if (output?.id) return output;
522
- const created = yield* call(() => client.POST("/v1/databases/{databaseId}/connections", {
523
- params: { path: { databaseId: news.databaseId } },
524
- body: { name: news.name }
525
- }));
526
- const endpoints = created.data.endpoints;
527
- const dsn = endpoints?.direct?.connectionString ?? endpoints?.pooled?.connectionString;
528
- if (dsn === void 0) return yield* Effect.fail(new PrismaApiError({
529
- status: 0,
530
- message: `connection ${created.data.id} returned no direct/pooled connection string`
531
- }));
532
- return {
533
- id: created.data.id,
534
- connectionString: Redacted.make(dsn)
535
- };
536
- }),
537
- delete: Effect.fn(function* ({ output }) {
538
- yield* callVoid(() => client.DELETE("/v1/connections/{id}", { params: { path: { id: output.id } } }));
539
- })
540
- };
541
- }));
542
- /** A Prisma **Postgres database** inside a project. */
543
- const Database = Resource("Prisma.Database");
544
- const DatabaseProvider = () => Provider.effect(Database, Effect.gen(function* () {
545
- const client = yield* ManagementClient;
546
- return {
547
- stables: ["id"],
548
- list: () => Effect.succeed([]),
549
- reconcile: Effect.fn(function* ({ news, output }) {
550
- const observed = output?.id ? yield* callOptional(() => client.GET("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } })) : void 0;
551
- if (!observed) {
552
- const created = yield* call(() => client.POST("/v1/databases", { body: {
553
- projectId: news.projectId,
554
- name: news.name,
555
- region: news.region,
556
- ...news.isDefault !== void 0 && { isDefault: news.isDefault },
557
- ...news.branchId !== void 0 && { branchId: news.branchId }
558
- } }));
559
- return {
560
- id: created.data.id,
561
- name: created.data.name
562
- };
563
- }
564
- const result = {
565
- id: observed.data.id,
566
- name: observed.data.name
567
- };
568
- if (news.branchId !== void 0) {
569
- const branchId = news.branchId;
570
- yield* call(() => client.PATCH("/v1/databases/{databaseId}", {
571
- params: { path: { databaseId: result.id } },
572
- body: { branchId }
573
- }));
574
- }
575
- return result;
576
- }),
577
- delete: Effect.fn(function* ({ output }) {
578
- yield* callVoid(() => client.DELETE("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } }));
579
- }),
580
- read: Effect.fn(function* ({ output }) {
581
- if (!output?.id) return void 0;
582
- const d = yield* callOptional(() => client.GET("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } }));
583
- return d ? {
584
- id: d.data.id,
585
- name: d.data.name
586
- } : void 0;
587
- })
588
- };
589
- }));
590
- /** A Prisma Developer Platform **Project** — the container for databases and compute services. */
591
- const Project = Resource("Prisma.Project");
592
- const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
593
- const client = yield* ManagementClient;
594
- return {
595
- stables: ["id"],
596
- list: () => Effect.succeed([]),
597
- reconcile: Effect.fn(function* ({ news, output }) {
598
- const observed = output?.id ? yield* callOptional(() => client.GET("/v1/projects/{id}", { params: { path: { id: output.id } } })) : void 0;
599
- if (observed) return {
600
- id: observed.data.id,
601
- name: observed.data.name
602
- };
603
- const created = yield* call(() => client.POST("/v1/projects", { body: {
604
- name: news.name,
605
- workspaceId: news.workspaceId
606
- } }));
607
- return {
608
- id: created.data.id,
609
- name: created.data.name
610
- };
611
- }),
612
- delete: Effect.fn(function* ({ output }) {
613
- yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: output.id } } }));
614
- }),
615
- read: Effect.fn(function* ({ output }) {
616
- if (!output?.id) return void 0;
617
- const p = yield* callOptional(() => client.GET("/v1/projects/{id}", { params: { path: { id: output.id } } }));
618
- return p ? {
619
- id: p.data.id,
620
- name: p.data.name
621
- } : void 0;
622
- })
623
- };
624
- }));
625
- //#endregion
626
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/index.mjs
627
- /** Raised with `ensure: false` when the app's Project (or a named stage's Branch) doesn't exist. */
628
- var ContainerNotFoundError = class extends Data.TaggedError("ContainerNotFoundError") {};
629
- const listAllProjects = (client) => Effect.gen(function* () {
630
- const projects = [];
631
- let cursor;
632
- for (;;) {
633
- const query = cursor === void 0 ? {} : { cursor };
634
- const page = yield* call(() => client.GET("/v1/projects", { params: { query } }));
635
- projects.push(...page.data);
636
- if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
637
- cursor = page.pagination.nextCursor;
638
- }
639
- return projects;
640
- });
641
- /**
642
- * Workspace ids circulate in two shapes: `wksp_`-prefixed and bare. Compare
643
- * bare-to-bare so a `wksp_`-prefixed API id still matches a bare configured
644
- * one (the same normalization `state/bootstrap.ts` applies to the same
645
- * `/v1/projects` listing).
646
- */
647
- const bareWorkspaceId = (id) => id.startsWith("wksp_") ? id.slice(5) : id;
648
- /**
649
- * Finds the app's Project by name in the workspace — PDP allows duplicate
650
- * project names, so more than one can match; the oldest wins. Creates one
651
- * if none match, unless `ensure` is `false` (find-only — `destroy`), in
652
- * which case an absent Project fails with `ContainerNotFoundError`. No
653
- * ownership marker and no `--project` override (both deferred — see
654
- * ADR-0019).
655
- */
656
- const resolveProject = (client, workspaceId, appName, ensure) => Effect.gen(function* () {
657
- 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];
658
- if (oldest !== void 0) return oldest.id;
659
- if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({ appName }));
660
- return (yield* call(() => client.POST("/v1/projects", { body: {
661
- name: appName,
662
- workspaceId
663
- } }))).data.id;
664
- });
665
- const findBranchId = (client, projectId, gitName) => call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
666
- path: { projectId },
667
- query: { gitName }
668
- } })).pipe(Effect.map((page) => page.data[0]?.id));
669
- /**
670
- * Finds the stage's Branch by its exact `gitName`, creating it if absent
671
- * unless `ensure` is `false` (find-only — `destroy`), in which case an
672
- * absent Branch fails with `ContainerNotFoundError`. The Management API has
673
- * no server-side "create-or-return" idempotency (`POST
674
- * /v1/projects/:id/branches` 409s on a duplicate `gitName`, with no request
675
- * field to make that a no-op), so idempotency is client-side: observe
676
- * first, and on a racing 409 from create, re-observe rather than fail.
677
- */
678
- const resolveBranch = (client, projectId, gitName, appName, ensure) => Effect.gen(function* () {
679
- const existing = yield* findBranchId(client, projectId, gitName);
680
- if (existing !== void 0) return existing;
681
- if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({
682
- appName,
683
- stage: gitName
684
- }));
685
- return yield* call(() => client.POST("/v1/projects/{projectId}/branches", {
686
- params: { path: { projectId } },
687
- body: { gitName }
688
- })).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)));
689
- });
690
- /**
691
- * Resolves the two containers a stage's deploy runs into (ADR-0019): the
692
- * app's **Project**, found-or-created by name, and — for a named stage
693
- * only — its **Branch**, found-or-created by `gitName`. The default stage
694
- * (no `stage`) creates no Branch; `branchId` is omitted. With `ensure:
695
- * false` (`destroy`), nothing is created — an absent Project or Branch
696
- * fails with `ContainerNotFoundError` instead.
697
- */
698
- const resolveContainer = (opts) => Effect.gen(function* () {
699
- const client = yield* ManagementClient;
700
- const ensure = opts.ensure ?? true;
701
- const projectId = yield* resolveProject(client, opts.workspaceId, opts.appName, ensure);
702
- if (opts.stage === void 0) return { projectId };
703
- return {
704
- projectId,
705
- branchId: yield* resolveBranch(client, projectId, opts.stage, opts.appName, ensure)
706
- };
707
- });
708
- /**
709
- * Soft-deletes a Branch. Tolerates a 404 (already gone). The API refuses if
710
- * the Branch still has live members or is the production/default Branch —
711
- * that surfaces as a `PrismaApiError`.
712
- */
713
- const deleteBranch = (branchId) => Effect.gen(function* () {
714
- const client = yield* ManagementClient;
715
- yield* callVoid(() => client.DELETE("/v1/branches/{branchId}", { params: { path: { branchId } } }));
716
- });
717
- /**
718
- * Deletes a Project. Tolerates a 404 (already gone). The API refuses with a
719
- * 400 if the Project still has live dependencies (e.g. another stage's
720
- * Branch/resources) — that surfaces as a `PrismaApiError`.
721
- */
722
- const deleteProject = (projectId) => Effect.gen(function* () {
723
- const client = yield* ManagementClient;
724
- yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: projectId } } }));
725
- });
726
- /** The collection of Prisma resource providers. */
727
- var Providers = class extends Provider.ProviderCollection()("Prisma") {};
728
- /**
729
- * The Prisma provider bundle: every resource provider, the Management API
730
- * client, and env-based credentials. Plug into a stack with
731
- * `{ providers: Prisma.providers() }`.
732
- */
733
- const providers = () => Layer.effect(Providers, Provider.collection([
734
- Project,
735
- Database,
736
- Connection,
737
- ComputeService,
738
- Deployment,
739
- EnvironmentVariable,
740
- Bucket,
741
- BucketKey
742
- ])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DatabaseProvider(), ConnectionProvider(), ComputeServiceProvider(), DeploymentProvider(), EnvironmentVariableProvider(), BucketProvider(), BucketKeyProvider())), Layer.provideMerge(layer()), Layer.provideMerge(fromEnv()), Layer.orDie);
743
- //#endregion
744
20
  //#region ../../1-prisma-cloud/0-lowering/lowering/dist/state.mjs
745
21
  /** The framework-owned database a stage's deploy state lives in — a child of that stage's Branch (ADR-0034). */
746
22
  const STATE_DATABASE_NAME = "prisma-composer-state";
@@ -1404,166 +680,6 @@ const prismaStateLayer = (ids) => {
1404
680
  };
1405
681
  //#endregion
1406
682
  //#region ../../1-prisma-cloud/1-extensions/target/dist/control.mjs
1407
- const PRISMA_CLOUD_EXTENSION_ID = "@prisma/composer-prisma-cloud";
1408
- var PrismaCloudContainer = class {
1409
- input;
1410
- projectId;
1411
- branchId;
1412
- constructor(input, projectId, branchId) {
1413
- this.input = input;
1414
- this.projectId = projectId;
1415
- this.branchId = branchId;
1416
- }
1417
- serialize() {
1418
- return JSON.stringify({
1419
- input: this.input,
1420
- projectId: this.projectId,
1421
- ...this.branchId !== void 0 ? { branchId: this.branchId } : {}
1422
- });
1423
- }
1424
- };
1425
- /** `instanceof` — parent-side instances and child-side deserialized instances are both constructed by this module. */
1426
- function isPrismaCloudContainer(value) {
1427
- return value instanceof PrismaCloudContainer;
1428
- }
1429
- /** Narrow-or-throw for hook inputs. */
1430
- function prismaCloudContainerOf(value) {
1431
- if (!isPrismaCloudContainer(value)) throw new Error("the Prisma Cloud container was not resolved — the extension's container descriptor did not run.");
1432
- return value;
1433
- }
1434
- function isRecord(value) {
1435
- return typeof value === "object" && value !== null;
1436
- }
1437
- function invalidPayloadError(reason) {
1438
- return /* @__PURE__ */ new Error(`${PRISMA_CLOUD_EXTENSION_ID}: invalid container transport payload — ${reason}.`);
1439
- }
1440
- /** Reconstructs a `PrismaCloudContainer` from `serialize()`'s JSON output — real narrowing, no casts. */
1441
- function deserialize(serialized) {
1442
- let parsed;
1443
- try {
1444
- parsed = JSON.parse(serialized);
1445
- } catch (error) {
1446
- throw invalidPayloadError(`not valid JSON (${error instanceof Error ? error.message : String(error)})`);
1447
- }
1448
- if (!isRecord(parsed)) throw invalidPayloadError("not an object");
1449
- const input = parsed["input"];
1450
- if (!isRecord(input)) throw invalidPayloadError("\"input\" is not an object");
1451
- const appName = input["appName"];
1452
- if (typeof appName !== "string") throw invalidPayloadError("\"input.appName\" is not a string");
1453
- const stage = input["stage"];
1454
- if (stage !== void 0 && typeof stage !== "string") throw invalidPayloadError("\"input.stage\" is not a string or absent");
1455
- const projectId = parsed["projectId"];
1456
- if (typeof projectId !== "string") throw invalidPayloadError("\"projectId\" is not a string");
1457
- const branchId = parsed["branchId"];
1458
- if (branchId !== void 0 && typeof branchId !== "string") throw invalidPayloadError("\"branchId\" is not a string or absent");
1459
- return new PrismaCloudContainer({
1460
- appName,
1461
- stage
1462
- }, projectId, branchId);
1463
- }
1464
- const workspaceRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_WORKSPACE_ID is required.");
1465
- const tokenRequiredError$2 = () => /* @__PURE__ */ new Error("environment variable PRISMA_SERVICE_TOKEN is required.");
1466
- function requireWorkspaceId() {
1467
- const workspaceId = process.env["PRISMA_WORKSPACE_ID"];
1468
- if (workspaceId === void 0 || workspaceId.length === 0) throw workspaceRequiredError();
1469
- return workspaceId;
1470
- }
1471
- function requireTokenUnlessInjected(deps) {
1472
- if (deps?.client === void 0 && (process.env["PRISMA_SERVICE_TOKEN"] ?? "").length === 0) throw tokenRequiredError$2();
1473
- }
1474
- async function ensureContainer(input, deps) {
1475
- const workspaceId = requireWorkspaceId();
1476
- requireTokenUnlessInjected(deps);
1477
- const program = resolveContainer({
1478
- workspaceId,
1479
- appName: input.appName,
1480
- ...input.stage !== void 0 ? { stage: input.stage } : {},
1481
- ensure: true
1482
- }).pipe(Effect.map((c) => ({
1483
- ok: true,
1484
- container: c
1485
- })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
1486
- ok: false,
1487
- message: `Prisma Management API error resolving containers: ${e.message}.`
1488
- })));
1489
- const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
1490
- const outcome = await Effect.runPromise(provided);
1491
- if (!outcome.ok) throw new Error(outcome.message);
1492
- return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
1493
- }
1494
- async function locateContainer(input, deps) {
1495
- const workspaceId = requireWorkspaceId();
1496
- requireTokenUnlessInjected(deps);
1497
- const program = resolveContainer({
1498
- workspaceId,
1499
- appName: input.appName,
1500
- ...input.stage !== void 0 ? { stage: input.stage } : {},
1501
- ensure: false
1502
- }).pipe(Effect.map((c) => ({
1503
- ok: true,
1504
- container: c
1505
- })), Effect.catchTag("ContainerNotFoundError", () => Effect.succeed({ ok: false })), Effect.catchTag("PrismaApiError", (e) => Effect.fail(/* @__PURE__ */ new Error(`Prisma Management API error resolving containers: ${e.message}.`))));
1506
- const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
1507
- const outcome = await Effect.runPromise(provided);
1508
- if (!outcome.ok) return void 0;
1509
- return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
1510
- }
1511
- /**
1512
- * Soft-deletes a named stage's Branch after a successful `alchemy destroy`
1513
- * has removed its members — the Management API refuses to delete a Branch
1514
- * that still has live members.
1515
- */
1516
- async function removeStageBranch(branchId, deps) {
1517
- requireTokenUnlessInjected(deps);
1518
- const program = deleteBranch(branchId).pipe(Effect.map(() => ({ ok: true })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
1519
- ok: false,
1520
- message: `Failed to delete the stage Branch: ${e.message}.`
1521
- })));
1522
- const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
1523
- const outcome = await Effect.runPromise(provided);
1524
- if (!outcome.ok) throw new Error(outcome.message);
1525
- }
1526
- /**
1527
- * Best-effort cleanup after a successful `--production` destroy: removes
1528
- * the app's Project so hand-run stacks don't accumulate as empty Projects
1529
- * (they eventually hit the workspace's plan limit). Unlike `removeStageBranch`,
1530
- * this never throws: the destroy itself already succeeded, and the API's own
1531
- * 400 ("still has dependencies") is the only check that matters — failing
1532
- * the command over a cleanup step would be worse than leaving a Project shell.
1533
- */
1534
- async function removeAppProject(projectId, deps) {
1535
- if (deps?.client === void 0 && (process.env["PRISMA_SERVICE_TOKEN"] ?? "").length === 0) {
1536
- console.warn(`Skipped removing the Project (${projectId}): PRISMA_SERVICE_TOKEN is not set.`);
1537
- return;
1538
- }
1539
- const program = deleteProject(projectId).pipe(Effect.map(() => ({ ok: true })), Effect.catchTag("PrismaApiError", (e) => Effect.succeed({
1540
- ok: false,
1541
- error: e
1542
- })));
1543
- const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
1544
- const outcome = await Effect.runPromise(provided);
1545
- if (outcome.ok) {
1546
- console.log(`Removed the Project (${projectId}) — nothing was left in it.`);
1547
- return;
1548
- }
1549
- if (outcome.error.status === 400) {
1550
- console.log(`Kept the Project (${projectId}) — it still has another stage's resources.`);
1551
- return;
1552
- }
1553
- console.warn(`Could not remove the Project (${projectId}) after destroy: ${outcome.error.message}.`);
1554
- }
1555
- function containerDescriptor(deps) {
1556
- return {
1557
- ensure: (input) => ensureContainer(input, deps),
1558
- locate: (input) => locateContainer(input, deps),
1559
- remove: (instance) => instance.input.stage !== void 0 ? removeStageBranch(instance.branchId ?? missingBranchId(instance), deps) : removeAppProject(instance.projectId, deps),
1560
- deserialize
1561
- };
1562
- }
1563
- /** Defensive: a named-stage container always resolves a Branch together with its stage — `ensure`/`locate`/`deserialize` never produce one without the other. */
1564
- function missingBranchId(instance) {
1565
- 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.`);
1566
- }
1567
683
  const PRISMA_NAME_MIN = 3;
1568
684
  const PRISMA_NAME_MAX = 65;
1569
685
  function validateName(value, source) {
@@ -1631,14 +747,16 @@ function computeDescriptor(o) {
1631
747
  validateName(id, "service name (from provision id)");
1632
748
  const projectId = projectIdOf(application);
1633
749
  const branchId = cloudApplicationOf(application).branchId;
750
+ const svc = yield* ComputeService(`${id}-svc`, {
751
+ projectId,
752
+ name: id,
753
+ region: o().region ?? "us-east-1",
754
+ ...branchId !== void 0 ? { branchId } : {}
755
+ });
1634
756
  return {
1635
- serviceId: (yield* ComputeService(`${id}-svc`, {
1636
- projectId,
1637
- name: id,
1638
- region: o.region ?? "us-east-1",
1639
- ...branchId !== void 0 ? { branchId } : {}
1640
- })).id,
1641
- projectId
757
+ serviceId: svc.id,
758
+ projectId,
759
+ endpointDomain: svc.endpointDomain
1642
760
  };
1643
761
  }),
1644
762
  serialize: (ctx, provisioned, config) => Effect.gen(function* () {
@@ -1669,32 +787,31 @@ function computeDescriptor(o) {
1669
787
  class: cls,
1670
788
  ...branch
1671
789
  }));
1672
- if (svc.expose !== void 0 && Object.keys(svc.expose).length > 0) {
1673
- const refsByBrand = /* @__PURE__ */ new Map();
1674
- for (const edge of provisionedEdges(graph)) {
1675
- if (edge.providerAddress !== address) continue;
1676
- const ref = ctx.provisioned.get(edge.edgeId);
1677
- if (ref === void 0) continue;
1678
- const refs = refsByBrand.get(edge.brand) ?? [];
1679
- refs.push(ref);
1680
- refsByBrand.set(edge.brand, refs);
1681
- }
1682
- for (const [brand, entry] of o.providerParams) {
1683
- const raw = entry.value(refsByBrand.get(brand) ?? []);
1684
- if (raw === void 0) continue;
1685
- const key = configKey(address, {
1686
- owner: "service",
1687
- name: entry.name
1688
- });
1689
- const value = Output.isOutput(raw) ? Output.map(raw, (v) => encode("service", v)) : encode("service", raw);
1690
- records.push(yield* EnvironmentVariable(`${key}-var`, {
1691
- projectId,
1692
- key,
1693
- value,
1694
- class: cls,
1695
- ...branch
1696
- }));
1697
- }
790
+ const exposes = svc.expose !== void 0 && Object.keys(svc.expose).length > 0;
791
+ const refsByBrand = /* @__PURE__ */ new Map();
792
+ if (exposes) for (const edge of provisionedEdges(graph)) {
793
+ if (edge.providerAddress !== address) continue;
794
+ const ref = ctx.provisioned.get(edge.edgeId);
795
+ if (ref === void 0) continue;
796
+ const refs = refsByBrand.get(edge.brand) ?? [];
797
+ refs.push(ref);
798
+ refsByBrand.set(edge.brand, refs);
799
+ }
800
+ for (const [brand, entry] of o().providerParams) {
801
+ const raw = "valueForService" in entry ? entry.valueForService(provisioned, address) : exposes ? entry.value(refsByBrand.get(brand) ?? []) : void 0;
802
+ if (raw === void 0) continue;
803
+ const key = configKey(address, {
804
+ owner: "service",
805
+ name: entry.name
806
+ });
807
+ const value = Output.isOutput(raw) ? Output.map(raw, (v) => encode("service", v)) : encode("service", raw);
808
+ records.push(yield* EnvironmentVariable(`${key}-var`, {
809
+ projectId,
810
+ key,
811
+ value,
812
+ class: cls,
813
+ ...branch
814
+ }));
1698
815
  }
1699
816
  return {
1700
817
  environment: records,
@@ -1730,54 +847,6 @@ function computeDescriptor(o) {
1730
847
  };
1731
848
  }
1732
849
  /**
1733
- * The `PgWarm` Alchemy resource (slice 3, FT-5226) — warm a freshly-provisioned
1734
- * Prisma Postgres database at apply-time so it is ready by deploy-end, and the
1735
- * first real connection (a service's runtime client, or the migration) doesn't
1736
- * eat the cold-start reject.
1737
- *
1738
- * The DB `url` is a lazy `Output` at lowering time, so warming must be an
1739
- * apply-time tracked resource (same pattern as `PnMigration`): its `reconcile`
1740
- * receives the RESOLVED url and connects with `withConnectionRetry` + `select 1`,
1741
- * riding out the cold-start. Shared by BOTH the bare-`postgres` and the
1742
- * `prisma-next` lowerings; keyed on the connection `url`, so an unchanged
1743
- * redeploy is a no-op (warming is idempotent anyway).
1744
- *
1745
- * Deploy-time only: imports `pg` directly + `alchemy`. Imported by `control.ts`
1746
- * and tests, never by `index.ts` / the `./prisma-next` authoring entry — the
1747
- * isolation invariants hold.
1748
- */
1749
- /** The `PgWarm` resource constructor — `yield* PgWarm(id, { url })` in a lowering. */
1750
- const PgWarm = Resource("PrismaCloud.PgWarm");
1751
- /** Connect (retrying the cold-start) and run `select 1`, then release the connection. */
1752
- async function warmDatabase(url) {
1753
- await withConnectionRetry(async () => {
1754
- const client = new pg.Client({ connectionString: normalizeSslMode(url) });
1755
- await client.connect();
1756
- try {
1757
- await client.query("select 1");
1758
- } finally {
1759
- await client.end();
1760
- }
1761
- });
1762
- }
1763
- /**
1764
- * The `PgWarm` provider service. `reconcile` warms the DB (retrying the
1765
- * cold-start) and echoes the `url` so a downstream resource that reads
1766
- * `warm.url` runs only after the DB is warm. Idempotent — safe on redeploy;
1767
- * nothing to enumerate (`list` → `[]`) or tear down (`delete` → no-op; the DB's
1768
- * own deletion handles teardown). Exported so tests can drive it directly.
1769
- */
1770
- const pgWarmProviderService = {
1771
- list: () => Effect.succeed([]),
1772
- reconcile: ({ news }) => Effect.tryPromise({
1773
- try: () => warmDatabase(news.url),
1774
- catch: (error) => error
1775
- }).pipe(Effect.map(() => ({ url: news.url }))),
1776
- delete: () => Effect.void
1777
- };
1778
- /** The `PgWarm` provider layer — merged into the extension descriptor's `providers()`. */
1779
- const PgWarmProvider = () => Provider.effect(PgWarm, Effect.succeed(pgWarmProviderService));
1780
- /**
1781
850
  * One Database per module-provisioned postgres resource — `id` is the
1782
851
  * module provision id, so a resource shared by several consumers is created
1783
852
  * exactly once.
@@ -1789,7 +858,7 @@ function postgresDescriptor(o) {
1789
858
  const db = yield* Database(`${id}-db`, {
1790
859
  projectId: projectIdOf(application),
1791
860
  name: id,
1792
- region: o.region ?? "us-east-1",
861
+ region: o().region ?? "us-east-1",
1793
862
  ...branchId !== void 0 ? { branchId } : {}
1794
863
  });
1795
864
  const conn = yield* Connection(`${id}-conn`, {
@@ -1825,228 +894,6 @@ async function resolveMigrationsDir(configPath) {
1825
894
  return resolve(configPath, "..", (await loadConfig(configPath)).migrations?.dir ?? "migrations");
1826
895
  }
1827
896
  /**
1828
- * The Prisma Next migration step of the deploy lowering (ADR-0022, slice 2) —
1829
- * the safety-critical decision that brings a live database to a target REF
1830
- * using ONLY Prisma Next's authored migrations.
1831
- *
1832
- * Deploy-time only: this module imports `@prisma-next/postgres/control` (which
1833
- * transitively pulls PN's control/migration machinery + `pg`). It is imported
1834
- * by the deploy descriptors and this package's tests, NEVER by `index.ts` / the
1835
- * `./prisma-next` authoring entry — so it never lands in an app runtime bundle
1836
- * (the index-isolation invariant holds).
1837
- *
1838
- * The target is a ref `{ hash, invariants }` — not a bare `storageHash`. A
1839
- * ref's `invariants` are named postconditions established by `data`-class
1840
- * migration steps (e.g. a backfill), recorded monotonically on the live
1841
- * marker. Keying on the hash alone would make a pure data-invariant change an
1842
- * A→A self-edge the deploy wrongly skips. The decision, given the live marker
1843
- * and the target ref (see {@link decideMigrationAction}):
1844
- * - marker at ref.hash AND ref.invariants ⊆ marker.invariants → no-op
1845
- * - no marker (fresh DB) AND no required invariants → `dbInit`
1846
- * - otherwise → `migrate`
1847
- *
1848
- * `dbInit` is additive-only synthesis — it NEVER runs app-space data steps —
1849
- * so it is only correct when the ref requires no invariants; a fresh DB whose
1850
- * target carries invariants goes through `migrate`, which walks the AUTHORED
1851
- * graph (including the invariant-bearing data migrations) from empty.
1852
- *
1853
- * Never `dbUpdate`: synthesized diff-and-apply plans are never run against a
1854
- * deployed database. A no-authored-path (`MIGRATION_PATH_NOT_FOUND`) or a
1855
- * runner failure fails the deploy as a typed `PnMigrationError` (not swallowed).
1856
- * PN applies each migration in its own transaction, so a failed apply is atomic
1857
- * and resume-safe — the marker and schema are left as the last committed step.
1858
- */
1859
- /** A deploy-failing migration error — surfaced, never swallowed. */
1860
- var PnMigrationError = class extends Error {
1861
- code;
1862
- /** PN's structured explanation, when present. */
1863
- why;
1864
- constructor(code, summary, why) {
1865
- super(`prisma-next migrate (${code}): ${summary}`);
1866
- this.name = "PnMigrationError";
1867
- this.code = code;
1868
- this.why = why;
1869
- }
1870
- };
1871
- /**
1872
- * The target `storageHash` a contract heads to — `contractJson.storage.storageHash`.
1873
- * Read defensively: `contractJson` crosses the boundary as `unknown`.
1874
- */
1875
- function targetStorageHash(contractJson) {
1876
- if (typeof contractJson === "object" && contractJson !== null && "storage" in contractJson) {
1877
- const storage = contractJson.storage;
1878
- if (typeof storage === "object" && storage !== null && "storageHash" in storage) {
1879
- const hash = storage.storageHash;
1880
- if (typeof hash === "string" && hash.length > 0) return hash;
1881
- }
1882
- }
1883
- throw new PnMigrationError("INIT_FAILED", "the contract has no storage.storageHash — cannot determine the target schema version");
1884
- }
1885
- /**
1886
- * Resolve the deploy's target ref from the migrations dir.
1887
- *
1888
- * - `targetRef` named: read `migrations/app/refs/<name>.json` — fail loudly
1889
- * (`TARGET_REF_NOT_FOUND`) when the ref doesn't exist or can't be parsed.
1890
- * - Default: the app space's head. PN synthesizes the app head from the
1891
- * emitted contract — `{ hash: contract.storage.storageHash, invariants: [] }`
1892
- * (`contract emit` writes no app-space `refs/head.json` today; extension
1893
- * spaces have one on disk). When a future PN version does emit one, the
1894
- * on-disk `head.json` wins — read via `readContractSpaceHeadRef`, exactly
1895
- * the loader PN's own migrate uses.
1896
- */
1897
- async function resolveTargetRef(migrationsDir, contractJson, targetRef) {
1898
- if (targetRef !== void 0) {
1899
- const refsDir = spaceRefsDirectory(spaceMigrationDirectory(migrationsDir, APP_SPACE_ID));
1900
- try {
1901
- const ref = await readRef(refsDir, targetRef);
1902
- return {
1903
- hash: ref.hash,
1904
- invariants: ref.invariants
1905
- };
1906
- } catch (error) {
1907
- throw new PnMigrationError("TARGET_REF_NOT_FOUND", `targetRef "${targetRef}" could not be read from ${refsDir}`, error instanceof Error ? error.message : String(error));
1908
- }
1909
- }
1910
- const head = await readContractSpaceHeadRef(migrationsDir, APP_SPACE_ID);
1911
- if (head !== null) return {
1912
- hash: head.hash,
1913
- invariants: head.invariants
1914
- };
1915
- return {
1916
- hash: targetStorageHash(contractJson),
1917
- invariants: []
1918
- };
1919
- }
1920
- /**
1921
- * The pure migration decision, mirroring PN's own verifier: the database is
1922
- * AT the target when the marker's hash equals the ref's hash AND every ref
1923
- * invariant is on the marker (marker invariants are monotonic). `dbInit` is
1924
- * additive-only synthesis, so it is chosen only for a fresh DB whose
1925
- * effective required invariants (`ref.invariants − marker.invariants`) are
1926
- * empty; anything else — different hash, missing invariant (the A→A
1927
- * data-only self-edge), or a fresh DB with required invariants — walks the
1928
- * authored graph via `migrate`.
1929
- */
1930
- function decideMigrationAction(marker, ref) {
1931
- const markerInvariants = new Set(marker?.invariants ?? []);
1932
- const missing = ref.invariants.filter((id) => !markerInvariants.has(id));
1933
- if (marker !== null && marker.storageHash === ref.hash && missing.length === 0) return "noop";
1934
- if (marker === null && missing.length === 0) return "init";
1935
- return "migrate";
1936
- }
1937
- /**
1938
- * Bring the database at `url` to the target ref via PN's authored migrations.
1939
- * Reads the live marker, decides no-op / init / migrate
1940
- * ({@link decideMigrationAction}), applies, and throws a typed
1941
- * {@link PnMigrationError} on a no-path or runner failure. `migrationsDir` is
1942
- * the on-disk migrations root and `ref` the resolved target
1943
- * ({@link resolveTargetRef} — both resolved by the lowering, which also keys
1944
- * the PnMigration resource on them). `refName` (the resource's `targetRef`,
1945
- * when set) is threaded into `migrate` so PN targets the named ref's hash and
1946
- * plans an invariant-bearing path.
1947
- */
1948
- async function applyPnMigration(opts) {
1949
- const connection = normalizeSslMode(opts.url);
1950
- return withConnectionRetry(() => runMigration(connection, opts.contractJson, opts.migrationsDir, opts.ref, opts.refName), { shouldRetry: (error) => !(error instanceof PnMigrationError) });
1951
- }
1952
- async function runMigration(connection, contractJson, migrationsDir, ref, refName) {
1953
- const client = createPostgresControlClient({ connection });
1954
- await client.connect();
1955
- try {
1956
- const marker = await client.readMarker();
1957
- const markerHashBefore = marker?.storageHash ?? null;
1958
- const action = decideMigrationAction(marker, ref);
1959
- if (action === "noop") return {
1960
- action,
1961
- targetHash: ref.hash,
1962
- markerHashBefore
1963
- };
1964
- if (action === "init") {
1965
- const result = await client.dbInit({
1966
- contract: contractJson,
1967
- mode: "apply",
1968
- migrationsDir
1969
- });
1970
- if (!result.ok) throw new PnMigrationError("INIT_FAILED", result.failure.summary, result.failure.why);
1971
- return {
1972
- action,
1973
- targetHash: ref.hash,
1974
- markerHashBefore
1975
- };
1976
- }
1977
- const result = await client.migrate({
1978
- contract: contractJson,
1979
- migrationsDir,
1980
- ...refName !== void 0 ? {
1981
- refHash: ref.hash,
1982
- refInvariants: ref.invariants,
1983
- refName
1984
- } : {}
1985
- });
1986
- 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);
1987
- return {
1988
- action,
1989
- targetHash: ref.hash,
1990
- markerHashBefore
1991
- };
1992
- } finally {
1993
- await client.close();
1994
- }
1995
- }
1996
- /**
1997
- * The `PnMigration` Alchemy resource (ADR-0022, slice 2 D2) — the migration
1998
- * step modeled as a tracked resource so it participates in deploy state: keyed
1999
- * on the target REF identity (`targetHash` + sorted `invariants`), an
2000
- * unchanged redeploy is an Alchemy-level no-op (on top of the marker read),
2001
- * and a contract change — or a DATA-ONLY change that adds a ref invariant at
2002
- * the same hash — re-runs the migration.
2003
- *
2004
- * Its provider's `reconcile` receives the RESOLVED props at apply-time — in
2005
- * particular the concrete DB `url` (a lazy `Output` until the Connection
2006
- * provisions) — and delegates to the proven `applyPnMigration` decision. The
2007
- * provider is a standalone `Provider<PnMigration>` layer; the extension
2008
- * descriptor merges it into its `providers()` (`Layer.merge(Prisma.providers(),
2009
- * PnMigrationProvider())`), and Alchemy resolves it at apply via a direct
2010
- * provider-tag lookup (`tryFindProviderByType`) — no change to `@internal/lowering`.
2011
- *
2012
- * Deploy-time only: imports `@prisma-next/postgres/control` (via the helper) +
2013
- * `alchemy`. Imported by `control.ts` and tests, never by `index.ts` / the
2014
- * `./prisma-next` authoring entry — index isolation holds.
2015
- */
2016
- /** The `PnMigration` resource constructor — `yield* PnMigration(id, props)` in the lowering. */
2017
- const PnMigration = Resource("PrismaNext.Migration");
2018
- /**
2019
- * The `PnMigration` provider service. `reconcile` runs for both create and
2020
- * update (Alchemy's unified lifecycle); `applyPnMigration` is idempotent via
2021
- * the live marker read, so it is safe to run for either — the marker decides
2022
- * no-op / init / migrate. A migration has nothing to enumerate (`list` → `[]`)
2023
- * and nothing to tear down on its own (`delete` → no-op; the DB's own deletion
2024
- * handles teardown). Exported so tests can drive `reconcile` directly, without
2025
- * building an Effect layer.
2026
- */
2027
- const pnMigrationProviderService = {
2028
- list: () => Effect.succeed([]),
2029
- reconcile: ({ news }) => Effect.tryPromise({
2030
- try: () => applyPnMigration({
2031
- url: news.url,
2032
- contractJson: news.contractJson,
2033
- migrationsDir: news.migrationsDir,
2034
- ref: {
2035
- hash: news.targetHash,
2036
- invariants: news.invariants
2037
- },
2038
- ...news.refName !== void 0 ? { refName: news.refName } : {}
2039
- }),
2040
- catch: (error) => error
2041
- }).pipe(Effect.map((outcome) => ({
2042
- storageHash: outcome.targetHash,
2043
- invariants: news.invariants
2044
- }))),
2045
- delete: () => Effect.void
2046
- };
2047
- /** The `PnMigration` provider layer — merged into the extension descriptor's `providers()`. */
2048
- const PnMigrationProvider = () => Provider.effect(PnMigration, Effect.succeed(pnMigrationProviderService));
2049
- /**
2050
897
  * The migration is a tracked `PnMigration` Alchemy resource keyed on the
2051
898
  * target REF identity (hash + sorted invariants): unchanged redeploy is a
2052
899
  * no-op, a contract or ref-invariant change re-migrates.
@@ -2058,7 +905,7 @@ function prismaNextDescriptor(o) {
2058
905
  const db = yield* Database(`${id}-db`, {
2059
906
  projectId: projectIdOf(application),
2060
907
  name: id,
2061
- region: o.region ?? "us-east-1",
908
+ region: o().region ?? "us-east-1",
2062
909
  ...branchId !== void 0 ? { branchId } : {}
2063
910
  });
2064
911
  const conn = yield* Connection(`${id}-conn`, {
@@ -2090,48 +937,6 @@ function prismaNextDescriptor(o) {
2090
937
  return Object.assign(lowering, { kind: "resource" });
2091
938
  }
2092
939
  /**
2093
- * The `S3Credentials` Alchemy resource (S5) — mints a random SigV4 key pair
2094
- * ONCE at create and keeps it STABLE across deploys, so an unchanged module
2095
- * no-ops on redeploy. The pair is generated with the Web Crypto global
2096
- * (`crypto.getRandomValues` — no `node:` import, matching this package's
2097
- * runtime-coupling invariant) and persisted in Alchemy state; on every later
2098
- * apply the provider returns the persisted attributes (`reconcile`'s `output`)
2099
- * unchanged — the same way the postgres resource keeps a Connection stable.
2100
- * Rotation is destroy/recreate (a platform ask, not solved here).
2101
- *
2102
- * Deploy-time only: imports `alchemy`. Imported by `control.ts` and tests,
2103
- * never by `index.ts` / the authoring entry.
2104
- */
2105
- /** The `S3Credentials` resource constructor — `yield* S3Credentials(id, {})` in the lowering. */
2106
- const S3Credentials = Resource("PrismaCloud.S3Credentials");
2107
- function randomBytes(n) {
2108
- return crypto.getRandomValues(new Uint8Array(n));
2109
- }
2110
- function toHexUpper(bytes) {
2111
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("").toUpperCase();
2112
- }
2113
- /** A fresh SigV4 key pair: an AKIA-prefixed id and a 40-char base64 secret. */
2114
- function mintKeyPair() {
2115
- return {
2116
- accessKeyId: `AKIA${toHexUpper(randomBytes(8))}`,
2117
- secretAccessKey: btoa(String.fromCharCode(...randomBytes(30)))
2118
- };
2119
- }
2120
- /**
2121
- * The `S3Credentials` provider service. `reconcile` runs for create and update;
2122
- * it returns the persisted `output` when present (a redeploy reuses the stored
2123
- * pair — the no-op property) and mints a fresh pair only on first create.
2124
- * Nothing to enumerate (`list` → `[]`) or tear down (`delete` → no-op; the pair
2125
- * lives only in state). Exported so tests can drive it directly.
2126
- */
2127
- const s3CredentialsProviderService = {
2128
- list: () => Effect.succeed([]),
2129
- reconcile: ({ output }) => Effect.sync(() => output ?? mintKeyPair()),
2130
- delete: () => Effect.void
2131
- };
2132
- /** The `S3Credentials` provider layer — merged into the extension descriptor's `providers()`. */
2133
- const S3CredentialsProvider = () => Provider.effect(S3Credentials, Effect.succeed(s3CredentialsProviderService));
2134
- /**
2135
940
  * One `S3Credentials` resource per provisioned credentials node — `id` is the
2136
941
  * module provision id, so a pair shared by the storage service is minted once
2137
942
  * and kept stable across deploys (the resource's provider preserves it).
@@ -2183,21 +988,6 @@ function s3StoreDescriptor(o) {
2183
988
  })
2184
989
  };
2185
990
  }
2186
- /**
2187
- * Deploy preflight (ADR-0029, extended for env-sourced params): before Alchemy
2188
- * runs, verify every secret binding AND every env-sourced param binding in the
2189
- * app's manifests (each a platform env-var NAME) exists on Prisma Cloud for
2190
- * the target stage. A literal-bound param never touches the platform — only
2191
- * `envParam(...)` sources are checked here.
2192
- * A name absent on the platform but present in the deploy shell is provisioned
2193
- * via a direct Management API POST — NEVER an Alchemy resource, so the value
2194
- * never lands in hosted deploy state. A name absent from both fails the deploy,
2195
- * listing exactly what is missing and where to set it.
2196
- *
2197
- * Control-plane only (imported by control.ts → prisma-composer.config.ts); runs
2198
- * in the CLI parent, so it builds its own Management API client from env — the
2199
- * same credential path `container.ts`'s `ensure`/`locate` use.
2200
- */
2201
991
  /** production for the default stage; preview for a named stage — matching how the pack writes config rows. */
2202
992
  const classFor = (branchId) => branchId === void 0 ? "production" : "preview";
2203
993
  /**
@@ -2273,22 +1063,9 @@ async function managementClient$1() {
2273
1063
  */
2274
1064
  async function runPreflight(input, deps) {
2275
1065
  const { projectId, branchId } = prismaCloudContainerOf(input.container);
1066
+ const collected = collectPreflightNames(input.graph);
2276
1067
  const names = /* @__PURE__ */ new Map();
2277
- for (const binding of provisionManifest(input.graph)) {
2278
- const name = secretName(binding);
2279
- if (!names.has(name)) names.set(name, {
2280
- name,
2281
- serviceAddress: binding.serviceAddress
2282
- });
2283
- }
2284
- for (const binding of paramManifest(input.graph)) {
2285
- if (!isEnvParamSource(binding.binding)) continue;
2286
- const name = paramName(binding);
2287
- if (!names.has(name)) names.set(name, {
2288
- name,
2289
- serviceAddress: binding.serviceAddress
2290
- });
2291
- }
1068
+ for (const meta of [...collected.secrets, ...collected.envParams]) if (!names.has(meta.name)) names.set(meta.name, meta);
2292
1069
  if (names.size === 0) return;
2293
1070
  const client = deps?.client ?? await managementClient$1();
2294
1071
  const missing = [];
@@ -2410,6 +1187,20 @@ const streamsApiKeyValue = (refs) => {
2410
1187
  return distinct[0] ?? "";
2411
1188
  });
2412
1189
  };
1190
+ /**
1191
+ * Origin's deploy-side value function (service-derived): the provisioned
1192
+ * service's own `endpointDomain`, verbatim — `https://…`, no trailing slash,
1193
+ * no normalization. Every compute service gets this row, exposing or not.
1194
+ * The undefined guard is a deploy-time invariant check, not a policy: the
1195
+ * Management API always reports an endpoint domain post-PRO-200, so a missing
1196
+ * one means the platform predates the fix — fail the deploy loudly rather
1197
+ * than write a row origin() would trust. The raw string is JSON-encoded by
1198
+ * the descriptor's generic loop, like every other reserved provider param.
1199
+ */
1200
+ const selfOriginValue = (provisioned, address) => Output.map(provisioned.endpointDomain, (v) => {
1201
+ if (v === void 0) throw new Error(`ComputeService for "${address}" reported no endpointDomain at provision — cannot resolve the service's own origin (Management API predates the PRO-200 fix?)`);
1202
+ return v;
1203
+ });
2413
1204
  /** The user-facing state descriptor: `state: prismaState()` in `prisma-composer.config.ts` (ADR-0017). */
2414
1205
  const prismaState = () => ({
2415
1206
  extension: PRISMA_CLOUD_EXTENSION_ID,
@@ -2437,7 +1228,8 @@ function asProvidersLayer(layer) {
2437
1228
  * param up by brand.
2438
1229
  *
2439
1230
  * `__tests__/provider-params.test.ts` asserts this map's brands are exactly
2440
- * `PROVIDER_PARAMS`'s brands: a brand minted here with no provider param
1231
+ * `PROVIDER_PARAMS`'s edge-derived brands (a service-derived param like the
1232
+ * origin mints nothing, so it has no provisioner): a brand minted here with no provider param
2441
1233
  * below would leave the value it mints written to consumers while no
2442
1234
  * provider ever stores an accepted-keys row for it — `serve()` (or the
2443
1235
  * equivalent runtime reader) then sees an absent var and passes every caller
@@ -2445,7 +1237,7 @@ function asProvidersLayer(layer) {
2445
1237
  */
2446
1238
  const PROVISIONERS = /* @__PURE__ */ new Map([[RPC_PEER_KEY, serviceKeyProvisioner], [STREAMS_API_KEY, streamsApiKeyProvisioner]]);
2447
1239
  /**
2448
- * Every brand's deploy-side `value(refs)` — the only per-brand thing this
1240
+ * Every brand's deploy-side value function — the only per-brand thing this
2449
1241
  * file still holds directly. `PROVIDER_PARAMS` below is built by mapping
2450
1242
  * `RESERVED_PROVIDER_PARAMS` (`provider-params.ts`, the boot-side list) onto
2451
1243
  * this map, so a param can exist on the deploy side only if it already
@@ -2453,11 +1245,15 @@ const PROVISIONERS = /* @__PURE__ */ new Map([[RPC_PEER_KEY, serviceKeyProvision
2453
1245
  * which reserved provider params exist at all, closing the drift the old
2454
1246
  * name-comparison test only detected after the fact.
2455
1247
  */
2456
- const PROVIDER_PARAM_VALUES = /* @__PURE__ */ new Map([[RPC_PEER_KEY, rpcAcceptedKeysValue], [STREAMS_API_KEY, streamsApiKeyValue]]);
1248
+ const PROVIDER_PARAM_VALUES = /* @__PURE__ */ new Map([
1249
+ [RPC_PEER_KEY, { value: rpcAcceptedKeysValue }],
1250
+ [STREAMS_API_KEY, { value: streamsApiKeyValue }],
1251
+ [SELF_ORIGIN, { valueForService: selfOriginValue }]
1252
+ ]);
2457
1253
  /**
2458
1254
  * Builds the deploy-side registry from the boot-side list, keyed by brand —
2459
- * throws if a boot-side entry has no registered deploy-side `value(refs)`, so
2460
- * `RESERVED_PROVIDER_PARAMS` stays the single source of which reserved
1255
+ * throws if a boot-side entry has no registered deploy-side value function,
1256
+ * so `RESERVED_PROVIDER_PARAMS` stays the single source of which reserved
2461
1257
  * provider params exist: deploy can no longer write a row boot never
2462
1258
  * stashes. Exported (rather than inlined into `PROVIDER_PARAMS` below) so
2463
1259
  * `__tests__/provider-params.test.ts` can drive it directly with a
@@ -2469,18 +1265,24 @@ function buildProviderParams(entries, values) {
2469
1265
  if (value === void 0) throw new Error(`prisma-cloud: reserved provider param "${entry.name}" (provider-params.ts) has no registered deploy-side value() in control.ts's PROVIDER_PARAM_VALUES — every param in RESERVED_PROVIDER_PARAMS must have one.`);
2470
1266
  return [entry.brand, {
2471
1267
  ...entry,
2472
- value
1268
+ ...value
2473
1269
  }];
2474
1270
  }));
2475
1271
  }
2476
1272
  const PROVIDER_PARAMS = buildProviderParams(RESERVED_PROVIDER_PARAMS, PROVIDER_PARAM_VALUES);
2477
1273
  /**
2478
- * Resolves the factory's env-or-option inputs, failing fast with the exact
2479
- * variable name.
1274
+ * Resolves the factory's env-or-option inputs. Deliberately does NOT require
1275
+ * `PRISMA_WORKSPACE_ID`: nothing in this file reads `ResolvedCloudOptions.workspaceId`
1276
+ * downstream — it exists only so a caller MAY pin an explicit workspace, and
1277
+ * the real workspace check for a real deploy lives where the value actually
1278
+ * matters, `container.ts`'s `ensureContainer`/`locateContainer`. Region
1279
+ * validation stays eager-on-call (a garbage `PRISMA_REGION` still fails
1280
+ * loudly), but an ABSENT one resolves to `undefined` without touching
1281
+ * anything else — required for `prisma-composer dev`, which never sets
1282
+ * `PRISMA_REGION` and must not fail on its absence (local-dev spec § 5).
2480
1283
  */
2481
1284
  function resolveOptions(opts) {
2482
- const workspaceId = opts.workspaceId ?? process.env["PRISMA_WORKSPACE_ID"];
2483
- if (workspaceId === void 0 || workspaceId.length === 0) throw new Error("prismaCloud(): environment variable PRISMA_WORKSPACE_ID is required.");
1285
+ const workspaceId = opts.workspaceId ?? process.env["PRISMA_WORKSPACE_ID"] ?? "";
2484
1286
  if (opts.region !== void 0) return {
2485
1287
  workspaceId,
2486
1288
  region: opts.region,
@@ -2498,9 +1300,24 @@ function resolveOptions(opts) {
2498
1300
  providerParams: PROVIDER_PARAMS
2499
1301
  };
2500
1302
  }
1303
+ /**
1304
+ * A memoized thunk over `resolveOptions` — evaluated at FIRST LOWERING USE
1305
+ * (inside a node descriptor's `provision`/`serialize`), never at `prismaCloud()`
1306
+ * construction. The node descriptors take this thunk, not a resolved value
1307
+ * (local-dev spec § 5): `prismaCloud()` itself must construct with no
1308
+ * environment present, since it also builds the `localTarget` descriptor, which must
1309
+ * never require `PRISMA_WORKSPACE_ID`/`PRISMA_REGION`/`PRISMA_SERVICE_TOKEN`.
1310
+ */
1311
+ function lazyOptions(opts) {
1312
+ let cached;
1313
+ return () => {
1314
+ cached ??= resolveOptions(opts);
1315
+ return cached;
1316
+ };
1317
+ }
2501
1318
  /** The Prisma Cloud extension descriptor — `prisma-composer.config.ts` lists it under `extensions`. */
2502
1319
  const prismaCloud = (opts = {}) => {
2503
- const o = resolveOptions(opts);
1320
+ const o = lazyOptions(opts);
2504
1321
  return {
2505
1322
  id: PRISMA_CLOUD_EXTENSION_ID,
2506
1323
  container: containerDescriptor(),
@@ -2529,7 +1346,8 @@ const prismaCloud = (opts = {}) => {
2529
1346
  credentials: s3CredentialsDescriptor(o),
2530
1347
  "s3-store": s3StoreDescriptor(o),
2531
1348
  s3: bucketDescriptor(o)
2532
- }
1349
+ },
1350
+ localTarget: () => import("@prisma/composer-prisma-cloud/local-target").then((m) => m.localTargetDescriptor())
2533
1351
  };
2534
1352
  };
2535
1353
  //#endregion