@prisma/composer-prisma-cloud 0.1.0-dev.9 → 0.2.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/control.d.mts +40 -34
  2. package/dist/control.mjs +512 -105
  3. package/dist/control.mjs.map +1 -1
  4. package/dist/cron/index.d.mts +54 -4
  5. package/dist/cron/index.mjs +154 -42
  6. package/dist/cron/index.mjs.map +1 -1
  7. package/dist/cron/scheduler-entrypoint.mjs +459 -306
  8. package/dist/cron/scheduler-entrypoint.mjs.map +1 -1
  9. package/dist/cron/scheduler-service.mjs +154 -42
  10. package/dist/cron/scheduler-service.mjs.map +1 -1
  11. package/dist/index.d.mts +76 -6
  12. package/dist/index.mjs +108 -45
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/{prisma-next-qPB8_Az6.mjs → prisma-next-DlU01kXJ-2sP5mQb6.mjs} +3 -3
  15. package/dist/prisma-next-DlU01kXJ-2sP5mQb6.mjs.map +1 -0
  16. package/dist/prisma-next.d.mts +1 -1
  17. package/dist/prisma-next.mjs +1 -1
  18. package/dist/{provisioned-edges-DIQAR4q4-Bn9op-JG.mjs → provisioned-edges-B_XS1Mz--NFJgcXK7.mjs} +66 -6
  19. package/dist/provisioned-edges-B_XS1Mz--NFJgcXK7.mjs.map +1 -0
  20. package/dist/{serializer-CX4VYdf_-KKGoAxfx.mjs → serializer-D8_84XWO-DA2B6YbX.mjs} +33 -3
  21. package/dist/serializer-D8_84XWO-DA2B6YbX.mjs.map +1 -0
  22. package/dist/storage/index.d.mts +62 -14
  23. package/dist/storage/index.mjs +168 -48
  24. package/dist/storage/index.mjs.map +1 -1
  25. package/dist/storage/storage-entrypoint.mjs +361 -239
  26. package/dist/storage/storage-entrypoint.mjs.map +1 -1
  27. package/dist/storage/storage-service.mjs +168 -48
  28. package/dist/storage/storage-service.mjs.map +1 -1
  29. package/dist/storage/testing.mjs.map +1 -1
  30. package/dist/streams/index.d.mts +62 -89
  31. package/dist/streams/index.mjs +169 -49
  32. package/dist/streams/index.mjs.map +1 -1
  33. package/dist/streams/streams-entrypoint.mjs +361 -239
  34. package/dist/streams/streams-entrypoint.mjs.map +1 -1
  35. package/dist/streams/streams-service.mjs +168 -48
  36. package/dist/streams/streams-service.mjs.map +1 -1
  37. package/dist/streams/testing.d.mts +1 -1
  38. package/dist/streams/testing.mjs.map +1 -1
  39. package/dist/testing.d.mts +1 -1
  40. package/dist/testing.mjs +1 -1
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +14 -14
  43. package/dist/prisma-next-qPB8_Az6.mjs.map +0 -1
  44. package/dist/provisioned-edges-DIQAR4q4-Bn9op-JG.mjs.map +0 -1
  45. package/dist/serializer-CX4VYdf_-KKGoAxfx.mjs.map +0 -1
package/dist/control.mjs CHANGED
@@ -1,14 +1,14 @@
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-qPB8_Az6.mjs";
1
+ import { a as encode, c as paramEntries, d as secretPointerRows, n as configKey, o as encodeParamPointer, u as secretName } from "./serializer-D8_84XWO-DA2B6YbX.mjs";
2
+ import { c as paramName, l as provisionedEdges, n as SELF_ORIGIN, o as isEnvParamSource, r as STREAMS_API_KEY, s as paramBindingFor, t as RESERVED_PROVIDER_PARAMS } from "./provisioned-edges-B_XS1Mz--NFJgcXK7.mjs";
3
+ import { a as normalizeSslMode, n as isPnPostgresResourceNode, o as withConnectionRetry } from "./prisma-next-DlU01kXJ-2sP5mQb6.mjs";
4
4
  import { isParamSource, paramManifest, provisionManifest } from "@prisma/composer";
5
5
  import { blindCast } from "@prisma/composer/casts";
6
6
  import { RPC_PEER_KEY } from "@prisma/composer/service-rpc";
7
7
  import pg from "pg";
8
- import * as Layer from "effect/Layer";
9
8
  import { createManagementApiClient } from "@prisma/management-api-sdk";
10
9
  import * as Context from "effect/Context";
11
10
  import * as Effect from "effect/Effect";
11
+ import * as Layer from "effect/Layer";
12
12
  import * as Redacted from "effect/Redacted";
13
13
  import * as Config$1 from "effect/Config";
14
14
  import * as Data from "effect/Data";
@@ -28,7 +28,7 @@ import { resolve } from "pathe";
28
28
  import { readRef } from "@prisma-next/migration-tools/refs";
29
29
  import { APP_SPACE_ID, readContractSpaceHeadRef, spaceMigrationDirectory, spaceRefsDirectory } from "@prisma-next/migration-tools/spaces";
30
30
  import { createPostgresControlClient } from "@prisma-next/postgres/control";
31
- //#region ../../1-prisma-cloud/0-lowering/lowering/dist/http-CxGdfSAP.mjs
31
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/http-CJM0E6rj.mjs
32
32
  /**
33
33
  * The Prisma service token used to authenticate Management API calls. Kept
34
34
  * as a Redacted value so it never lands in logs or error output.
@@ -68,6 +68,84 @@ const callOptional = (f) => attempt$1(f).pipe(Effect.flatMap((r) => r.response.s
68
68
  /** Fire-and-forget a call, tolerating a 404 (already deleted). */
69
69
  const callVoid = (f) => attempt$1(f).pipe(Effect.flatMap((r) => r.response.status === 404 || r.error === void 0 ? Effect.void : fail(r)));
70
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
71
149
  //#region ../../1-prisma-cloud/0-lowering/lowering/dist/compute.mjs
72
150
  /**
73
151
  * Stopping a deployment before the app that owns it can be
@@ -470,25 +548,23 @@ const DatabaseProvider = () => Provider.effect(Database, Effect.gen(function* ()
470
548
  list: () => Effect.succeed([]),
471
549
  reconcile: Effect.fn(function* ({ news, output }) {
472
550
  const observed = output?.id ? yield* callOptional(() => client.GET("/v1/databases/{databaseId}", { params: { path: { databaseId: output.id } } })) : void 0;
473
- let result;
474
- if (observed) result = {
475
- id: observed.data.id,
476
- name: observed.data.name
477
- };
478
- else {
479
- const created = yield* call(() => client.POST("/v1/projects/{projectId}/databases", {
480
- params: { path: { projectId: news.projectId } },
481
- body: {
482
- name: news.name,
483
- region: news.region,
484
- ...news.isDefault !== void 0 && { isDefault: news.isDefault }
485
- }
486
- }));
487
- result = {
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 {
488
560
  id: created.data.id,
489
561
  name: created.data.name
490
562
  };
491
563
  }
564
+ const result = {
565
+ id: observed.data.id,
566
+ name: observed.data.name
567
+ };
492
568
  if (news.branchId !== void 0) {
493
569
  const branchId = news.branchId;
494
570
  yield* call(() => client.PATCH("/v1/databases/{databaseId}", {
@@ -546,7 +622,107 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
546
622
  })
547
623
  };
548
624
  }));
549
- Data.TaggedError("ContainerNotFoundError");
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
+ });
550
726
  /** The collection of Prisma resource providers. */
551
727
  var Providers = class extends Provider.ProviderCollection()("Prisma") {};
552
728
  /**
@@ -560,8 +736,10 @@ const providers = () => Layer.effect(Providers, Provider.collection([
560
736
  Connection,
561
737
  ComputeService,
562
738
  Deployment,
563
- EnvironmentVariable
564
- ])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), DatabaseProvider(), ConnectionProvider(), ComputeServiceProvider(), DeploymentProvider(), EnvironmentVariableProvider())), Layer.provideMerge(layer()), Layer.provideMerge(fromEnv()), Layer.orDie);
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);
565
743
  //#endregion
566
744
  //#region ../../1-prisma-cloud/0-lowering/lowering/dist/state.mjs
567
745
  /** The framework-owned database a stage's deploy state lives in — a child of that stage's Branch (ADR-0034). */
@@ -1202,11 +1380,8 @@ const guardStateService = (service, checkLive, now = Date.now) => {
1202
1380
  * StackServices>` contract and alchemy's own convention (e.g. a missing
1203
1381
  * state store is `Effect.die` in `Stack.make`).
1204
1382
  */
1205
- const prismaState = () => {
1206
- const projectId = process.env["PRISMA_PROJECT_ID"];
1207
- if (projectId === void 0 || projectId.length === 0) throw new Error("prismaState(): environment variable PRISMA_PROJECT_ID is required (the CLI sets it — deploy via `prisma-composer deploy`).");
1208
- const branchIdEnv = process.env["PRISMA_BRANCH_ID"];
1209
- const branchId = branchIdEnv === void 0 || branchIdEnv.length === 0 ? void 0 : branchIdEnv;
1383
+ const prismaStateLayer = (ids) => {
1384
+ const { projectId, branchId } = ids;
1210
1385
  return Layer.effect(State, Effect.gen(function* () {
1211
1386
  const stack = yield* Stack;
1212
1387
  const container = branchId === void 0 ? projectId : `${projectId}/${branchId}`;
@@ -1229,18 +1404,218 @@ const prismaState = () => {
1229
1404
  };
1230
1405
  //#endregion
1231
1406
  //#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
+ }
1232
1567
  const PRISMA_NAME_MIN = 3;
1233
1568
  const PRISMA_NAME_MAX = 65;
1234
1569
  function validateName(value, source) {
1235
1570
  if (value.length < PRISMA_NAME_MIN || value.length > PRISMA_NAME_MAX) throw new Error(`prisma-cloud: ${source} "${value}" (${value.length} characters) is not a valid Prisma resource name — Prisma requires ${PRISMA_NAME_MIN}–${PRISMA_NAME_MAX} characters. Rename the provision id (or the deploy --name) to fit.`);
1236
1571
  }
1237
1572
  function isCloudApplication(value) {
1238
- return typeof value === "object" && value !== null && "projectId" in value && typeof value.projectId === "string";
1573
+ return typeof value === "object" && value !== null && "projectId" in value && typeof value.projectId === "string" && "branchId" in value && (value.branchId === void 0 || typeof value.branchId === "string");
1239
1574
  }
1240
1575
  /** Narrows `ctx.application`, which core hands over as `unknown`, to this extension's own product; throws naming the hook when it hasn't run. */
1241
- function projectIdOf(application) {
1576
+ function cloudApplicationOf(application) {
1242
1577
  if (!isCloudApplication(application)) throw new Error("prisma-cloud: ctx.application is not this extension's application product — the prismaCloud() application hook must run before any node lowers.");
1243
- return application.projectId;
1578
+ return application;
1579
+ }
1580
+ function projectIdOf(application) {
1581
+ return cloudApplicationOf(application).projectId;
1582
+ }
1583
+ /**
1584
+ * One Bucket per module-provisioned bucket resource — `id` is the module
1585
+ * provision id, so a resource shared by several consumers is created exactly
1586
+ * once. A BucketKey is minted for the bucket: it is the reveal-once credential
1587
+ * carrier, and its attributes (endpoint, bucketName, accessKeyId,
1588
+ * secretAccessKey) become the four S3Config outputs consumers resolve by name.
1589
+ */
1590
+ function bucketDescriptor(_o) {
1591
+ const lowering = ({ id, application }) => Effect.gen(function* () {
1592
+ validateName(id, "resource name (from provision id)");
1593
+ const branchId = cloudApplicationOf(application).branchId;
1594
+ const bkt = yield* Bucket(`${id}-bucket`, {
1595
+ projectId: projectIdOf(application),
1596
+ name: id,
1597
+ ...branchId !== void 0 ? { branchId } : {}
1598
+ });
1599
+ const key = yield* BucketKey(`${id}-key`, {
1600
+ bucketId: bkt.id,
1601
+ name: id,
1602
+ role: "read_write"
1603
+ });
1604
+ const secretAccessKey = Output.map(key.secretAccessKey, (v) => Redacted.value(v));
1605
+ return {
1606
+ outputs: {
1607
+ url: key.endpoint,
1608
+ bucket: key.bucketName,
1609
+ accessKeyId: key.accessKeyId,
1610
+ secretAccessKey
1611
+ },
1612
+ entities: [{
1613
+ kind: "bucket",
1614
+ id: bkt.id
1615
+ }]
1616
+ };
1617
+ });
1618
+ return Object.assign(lowering, { kind: "resource" });
1244
1619
  }
1245
1620
  /** The `compute` node kind's descriptor: the four service hooks — provision, serialize, package, deploy. */
1246
1621
  /**
@@ -1255,20 +1630,24 @@ function computeDescriptor(o) {
1255
1630
  provision: ({ id, application }) => Effect.gen(function* () {
1256
1631
  validateName(id, "service name (from provision id)");
1257
1632
  const projectId = projectIdOf(application);
1633
+ const branchId = cloudApplicationOf(application).branchId;
1634
+ const svc = yield* ComputeService(`${id}-svc`, {
1635
+ projectId,
1636
+ name: id,
1637
+ region: o.region ?? "us-east-1",
1638
+ ...branchId !== void 0 ? { branchId } : {}
1639
+ });
1258
1640
  return {
1259
- serviceId: (yield* ComputeService(`${id}-svc`, {
1260
- projectId,
1261
- name: id,
1262
- region: o.region ?? "us-east-1",
1263
- ...o.branchId !== void 0 ? { branchId: o.branchId } : {}
1264
- })).id,
1265
- projectId
1641
+ serviceId: svc.id,
1642
+ projectId,
1643
+ endpointDomain: svc.endpointDomain
1266
1644
  };
1267
1645
  }),
1268
1646
  serialize: (ctx, provisioned, config) => Effect.gen(function* () {
1269
1647
  const { address, node, graph } = ctx;
1270
- const cls = o.branchId ? "preview" : "production";
1271
- const branch = o.branchId !== void 0 ? { branchId: o.branchId } : {};
1648
+ const branchId = cloudApplicationOf(ctx.application).branchId;
1649
+ const cls = branchId ? "preview" : "production";
1650
+ const branch = branchId !== void 0 ? { branchId } : {};
1272
1651
  const projectId = provisioned.projectId;
1273
1652
  const svc = node;
1274
1653
  const records = [];
@@ -1292,32 +1671,31 @@ function computeDescriptor(o) {
1292
1671
  class: cls,
1293
1672
  ...branch
1294
1673
  }));
1295
- if (svc.expose !== void 0 && Object.keys(svc.expose).length > 0) {
1296
- const refsByBrand = /* @__PURE__ */ new Map();
1297
- for (const edge of provisionedEdges(graph)) {
1298
- if (edge.providerAddress !== address) continue;
1299
- const ref = ctx.provisioned.get(edge.edgeId);
1300
- if (ref === void 0) continue;
1301
- const refs = refsByBrand.get(edge.brand) ?? [];
1302
- refs.push(ref);
1303
- refsByBrand.set(edge.brand, refs);
1304
- }
1305
- for (const [brand, entry] of o.providerParams) {
1306
- const raw = entry.value(refsByBrand.get(brand) ?? []);
1307
- if (raw === void 0) continue;
1308
- const key = configKey(address, {
1309
- owner: "service",
1310
- name: entry.name
1311
- });
1312
- const value = Output.isOutput(raw) ? Output.map(raw, (v) => encode("service", v)) : encode("service", raw);
1313
- records.push(yield* EnvironmentVariable(`${key}-var`, {
1314
- projectId,
1315
- key,
1316
- value,
1317
- class: cls,
1318
- ...branch
1319
- }));
1320
- }
1674
+ const exposes = svc.expose !== void 0 && Object.keys(svc.expose).length > 0;
1675
+ const refsByBrand = /* @__PURE__ */ new Map();
1676
+ if (exposes) for (const edge of provisionedEdges(graph)) {
1677
+ if (edge.providerAddress !== address) continue;
1678
+ const ref = ctx.provisioned.get(edge.edgeId);
1679
+ if (ref === void 0) continue;
1680
+ const refs = refsByBrand.get(edge.brand) ?? [];
1681
+ refs.push(ref);
1682
+ refsByBrand.set(edge.brand, refs);
1683
+ }
1684
+ for (const [brand, entry] of o.providerParams) {
1685
+ const raw = "valueForService" in entry ? entry.valueForService(provisioned, address) : exposes ? entry.value(refsByBrand.get(brand) ?? []) : void 0;
1686
+ if (raw === void 0) continue;
1687
+ const key = configKey(address, {
1688
+ owner: "service",
1689
+ name: entry.name
1690
+ });
1691
+ const value = Output.isOutput(raw) ? Output.map(raw, (v) => encode("service", v)) : encode("service", raw);
1692
+ records.push(yield* EnvironmentVariable(`${key}-var`, {
1693
+ projectId,
1694
+ key,
1695
+ value,
1696
+ class: cls,
1697
+ ...branch
1698
+ }));
1321
1699
  }
1322
1700
  return {
1323
1701
  environment: records,
@@ -1408,11 +1786,12 @@ const PgWarmProvider = () => Provider.effect(PgWarm, Effect.succeed(pgWarmProvid
1408
1786
  function postgresDescriptor(o) {
1409
1787
  const lowering = ({ id, application }) => Effect.gen(function* () {
1410
1788
  validateName(id, "resource name (from provision id)");
1789
+ const branchId = cloudApplicationOf(application).branchId;
1411
1790
  const db = yield* Database(`${id}-db`, {
1412
1791
  projectId: projectIdOf(application),
1413
1792
  name: id,
1414
1793
  region: o.region ?? "us-east-1",
1415
- ...o.branchId !== void 0 ? { branchId: o.branchId } : {}
1794
+ ...branchId !== void 0 ? { branchId } : {}
1416
1795
  });
1417
1796
  const conn = yield* Connection(`${id}-conn`, {
1418
1797
  databaseId: db.id,
@@ -1676,11 +2055,12 @@ const PnMigrationProvider = () => Provider.effect(PnMigration, Effect.succeed(pn
1676
2055
  function prismaNextDescriptor(o) {
1677
2056
  const lowering = ({ id, node, application }) => Effect.gen(function* () {
1678
2057
  validateName(id, "resource name (from provision id)");
2058
+ const branchId = cloudApplicationOf(application).branchId;
1679
2059
  const db = yield* Database(`${id}-db`, {
1680
2060
  projectId: projectIdOf(application),
1681
2061
  name: id,
1682
2062
  region: o.region ?? "us-east-1",
1683
- ...o.branchId !== void 0 ? { branchId: o.branchId } : {}
2063
+ ...branchId !== void 0 ? { branchId } : {}
1684
2064
  });
1685
2065
  const conn = yield* Connection(`${id}-conn`, {
1686
2066
  databaseId: db.id,
@@ -1817,7 +2197,7 @@ function s3StoreDescriptor(o) {
1817
2197
  *
1818
2198
  * Control-plane only (imported by control.ts → prisma-composer.config.ts); runs
1819
2199
  * in the CLI parent, so it builds its own Management API client from env — the
1820
- * same credential path `ensureContainers` uses.
2200
+ * same credential path `container.ts`'s `ensure`/`locate` use.
1821
2201
  */
1822
2202
  /** production for the default stage; preview for a named stage — matching how the pack writes config rows. */
1823
2203
  const classFor = (branchId) => branchId === void 0 ? "production" : "preview";
@@ -1860,21 +2240,21 @@ async function existsOnPlatform(client, projectId, branchId, key) {
1860
2240
  * EnvironmentVariable.ts). A 409 means a concurrent deploy already provisioned
1861
2241
  * it — tolerated. The value is never logged.
1862
2242
  */
1863
- async function fillMissing(client, input, key, value) {
2243
+ async function fillMissing(client, projectId, branchId, key, value) {
1864
2244
  const res = await client.POST("/v1/environment-variables", { body: {
1865
- projectId: input.projectId,
1866
- class: classFor(input.branchId),
2245
+ projectId,
2246
+ class: classFor(branchId),
1867
2247
  key,
1868
2248
  value,
1869
- ...input.branchId !== void 0 ? { branchId: input.branchId } : {}
2249
+ ...branchId !== void 0 ? { branchId } : {}
1870
2250
  } });
1871
2251
  if (res.error !== void 0 && res.response.status !== 409) throw fillFailedError(key, res.error);
1872
2252
  }
1873
2253
  const tokenRequiredError$1 = () => /* @__PURE__ */ new Error("environment variable PRISMA_SERVICE_TOKEN is required for deploy preflight.");
1874
2254
  const listFailedError = (key, error) => /* @__PURE__ */ new Error(`deploy preflight: Prisma Management API error listing "${key}": ${JSON.stringify(error)}.`);
1875
2255
  const fillFailedError = (key, error) => /* @__PURE__ */ new Error(`deploy preflight: failed to provision "${key}" from the deploy shell: ${JSON.stringify(error)}.`);
1876
- function missingError(missing, input) {
1877
- const scope = input.branchId === void 0 ? "the production class (project-level template)" : `the preview class of stage "${input.stage ?? input.branchId}" (branch override or template)`;
2256
+ function missingError(missing, branchId, stage) {
2257
+ const scope = branchId === void 0 ? "the production class (project-level template)" : `the preview class of stage "${stage ?? branchId}" (branch override or template)`;
1878
2258
  const lines = missing.map((m) => ` - ${m.name} (required by service "${m.serviceAddress}")`);
1879
2259
  return /* @__PURE__ */ new Error(`Deploy preflight failed — ${missing.length} env var(s) (secret or env-sourced param) are not provisioned on Prisma Cloud for ${scope}, and are absent from the deploy shell:\n${lines.join("\n")}\n\nSet each in the deploy shell environment (the CLI will provision it on deploy), or create it on the platform (Prisma Console or the Management API) in ${scope}.`);
1880
2260
  }
@@ -1893,6 +2273,7 @@ async function managementClient$1() {
1893
2273
  * from env.
1894
2274
  */
1895
2275
  async function runPreflight(input, deps) {
2276
+ const { projectId, branchId } = prismaCloudContainerOf(input.container);
1896
2277
  const names = /* @__PURE__ */ new Map();
1897
2278
  for (const binding of provisionManifest(input.graph)) {
1898
2279
  const name = secretName(binding);
@@ -1913,15 +2294,15 @@ async function runPreflight(input, deps) {
1913
2294
  const client = deps?.client ?? await managementClient$1();
1914
2295
  const missing = [];
1915
2296
  for (const meta of names.values()) {
1916
- if (await existsOnPlatform(client, input.projectId, input.branchId, meta.name)) continue;
2297
+ if (await existsOnPlatform(client, projectId, branchId, meta.name)) continue;
1917
2298
  const shellValue = process.env[meta.name];
1918
2299
  if (shellValue !== void 0 && shellValue.length > 0) {
1919
- await fillMissing(client, input, meta.name, shellValue);
2300
+ await fillMissing(client, projectId, branchId, meta.name, shellValue);
1920
2301
  continue;
1921
2302
  }
1922
2303
  missing.push(meta);
1923
2304
  }
1924
- if (missing.length > 0) throw missingError(missing, input);
2305
+ if (missing.length > 0) throw missingError(missing, branchId, input.stage);
1925
2306
  }
1926
2307
  const tokenRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_SERVICE_TOKEN is required for destroy teardown.");
1927
2308
  async function managementClient() {
@@ -1946,12 +2327,13 @@ async function managementClient() {
1946
2327
  * builds a client from env and verifies against the real database.
1947
2328
  */
1948
2329
  async function runTeardown(input, deps) {
1949
- const isNamedStage = input.branchId !== void 0;
2330
+ const { projectId, branchId } = prismaCloudContainerOf(input.container);
2331
+ const isNamedStage = branchId !== void 0;
1950
2332
  try {
1951
2333
  const client = deps?.client ?? await managementClient();
1952
2334
  await Effect.runPromise(deleteStateDatabaseWith({
1953
- projectId: input.projectId,
1954
- ...input.branchId !== void 0 ? { branchId: input.branchId } : {}
2335
+ projectId,
2336
+ ...branchId !== void 0 ? { branchId } : {}
1955
2337
  }, deps?.verify ?? verifyOwnership).pipe(Effect.provideService(ManagementClient, client)));
1956
2338
  } catch (error) {
1957
2339
  const reason = error instanceof Error ? error.message : String(error);
@@ -2029,6 +2411,31 @@ const streamsApiKeyValue = (refs) => {
2029
2411
  return distinct[0] ?? "";
2030
2412
  });
2031
2413
  };
2414
+ /**
2415
+ * Origin's deploy-side value function (service-derived): the provisioned
2416
+ * service's own `endpointDomain`, verbatim — `https://…`, no trailing slash,
2417
+ * no normalization. Every compute service gets this row, exposing or not.
2418
+ * The undefined guard is a deploy-time invariant check, not a policy: the
2419
+ * Management API always reports an endpoint domain post-PRO-200, so a missing
2420
+ * one means the platform predates the fix — fail the deploy loudly rather
2421
+ * than write a row origin() would trust. The raw string is JSON-encoded by
2422
+ * the descriptor's generic loop, like every other reserved provider param.
2423
+ */
2424
+ const selfOriginValue = (provisioned, address) => Output.map(provisioned.endpointDomain, (v) => {
2425
+ 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?)`);
2426
+ return v;
2427
+ });
2428
+ /** The user-facing state descriptor: `state: prismaState()` in `prisma-composer.config.ts` (ADR-0017). */
2429
+ const prismaState = () => ({
2430
+ extension: PRISMA_CLOUD_EXTENSION_ID,
2431
+ create: (container) => {
2432
+ const { projectId, branchId } = prismaCloudContainerOf(container);
2433
+ return prismaStateLayer(branchId !== void 0 ? {
2434
+ projectId,
2435
+ branchId
2436
+ } : { projectId });
2437
+ }
2438
+ });
2032
2439
  const KNOWN_REGION_SET = new Set(COMPUTE_REGIONS);
2033
2440
  function isComputeRegion(value) {
2034
2441
  return KNOWN_REGION_SET.has(value);
@@ -2045,7 +2452,8 @@ function asProvidersLayer(layer) {
2045
2452
  * param up by brand.
2046
2453
  *
2047
2454
  * `__tests__/provider-params.test.ts` asserts this map's brands are exactly
2048
- * `PROVIDER_PARAMS`'s brands: a brand minted here with no provider param
2455
+ * `PROVIDER_PARAMS`'s edge-derived brands (a service-derived param like the
2456
+ * origin mints nothing, so it has no provisioner): a brand minted here with no provider param
2049
2457
  * below would leave the value it mints written to consumers while no
2050
2458
  * provider ever stores an accepted-keys row for it — `serve()` (or the
2051
2459
  * equivalent runtime reader) then sees an absent var and passes every caller
@@ -2053,7 +2461,7 @@ function asProvidersLayer(layer) {
2053
2461
  */
2054
2462
  const PROVISIONERS = /* @__PURE__ */ new Map([[RPC_PEER_KEY, serviceKeyProvisioner], [STREAMS_API_KEY, streamsApiKeyProvisioner]]);
2055
2463
  /**
2056
- * Every brand's deploy-side `value(refs)` — the only per-brand thing this
2464
+ * Every brand's deploy-side value function — the only per-brand thing this
2057
2465
  * file still holds directly. `PROVIDER_PARAMS` below is built by mapping
2058
2466
  * `RESERVED_PROVIDER_PARAMS` (`provider-params.ts`, the boot-side list) onto
2059
2467
  * this map, so a param can exist on the deploy side only if it already
@@ -2061,11 +2469,15 @@ const PROVISIONERS = /* @__PURE__ */ new Map([[RPC_PEER_KEY, serviceKeyProvision
2061
2469
  * which reserved provider params exist at all, closing the drift the old
2062
2470
  * name-comparison test only detected after the fact.
2063
2471
  */
2064
- const PROVIDER_PARAM_VALUES = /* @__PURE__ */ new Map([[RPC_PEER_KEY, rpcAcceptedKeysValue], [STREAMS_API_KEY, streamsApiKeyValue]]);
2472
+ const PROVIDER_PARAM_VALUES = /* @__PURE__ */ new Map([
2473
+ [RPC_PEER_KEY, { value: rpcAcceptedKeysValue }],
2474
+ [STREAMS_API_KEY, { value: streamsApiKeyValue }],
2475
+ [SELF_ORIGIN, { valueForService: selfOriginValue }]
2476
+ ]);
2065
2477
  /**
2066
2478
  * Builds the deploy-side registry from the boot-side list, keyed by brand —
2067
- * throws if a boot-side entry has no registered deploy-side `value(refs)`, so
2068
- * `RESERVED_PROVIDER_PARAMS` stays the single source of which reserved
2479
+ * throws if a boot-side entry has no registered deploy-side value function,
2480
+ * so `RESERVED_PROVIDER_PARAMS` stays the single source of which reserved
2069
2481
  * provider params exist: deploy can no longer write a row boot never
2070
2482
  * stashes. Exported (rather than inlined into `PROVIDER_PARAMS` below) so
2071
2483
  * `__tests__/provider-params.test.ts` can drive it directly with a
@@ -2077,41 +2489,32 @@ function buildProviderParams(entries, values) {
2077
2489
  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.`);
2078
2490
  return [entry.brand, {
2079
2491
  ...entry,
2080
- value
2492
+ ...value
2081
2493
  }];
2082
2494
  }));
2083
2495
  }
2084
2496
  const PROVIDER_PARAMS = buildProviderParams(RESERVED_PROVIDER_PARAMS, PROVIDER_PARAM_VALUES);
2085
2497
  /**
2086
2498
  * Resolves the factory's env-or-option inputs, failing fast with the exact
2087
- * variable name. `projectId`/`branchId` aren't required here — `prismaCloud()`
2088
- * also runs in the CLI parent, before they're set; the required check lives in `application.provision`.
2499
+ * variable name.
2089
2500
  */
2090
2501
  function resolveOptions(opts) {
2091
2502
  const workspaceId = opts.workspaceId ?? process.env["PRISMA_WORKSPACE_ID"];
2092
2503
  if (workspaceId === void 0 || workspaceId.length === 0) throw new Error("prismaCloud(): environment variable PRISMA_WORKSPACE_ID is required.");
2093
- const projectId = process.env["PRISMA_PROJECT_ID"] || void 0;
2094
- const branchId = process.env["PRISMA_BRANCH_ID"] || void 0;
2095
2504
  if (opts.region !== void 0) return {
2096
2505
  workspaceId,
2097
2506
  region: opts.region,
2098
- projectId,
2099
- branchId,
2100
2507
  providerParams: PROVIDER_PARAMS
2101
2508
  };
2102
2509
  const region = process.env["PRISMA_REGION"];
2103
2510
  if (region === void 0 || region.length === 0) return {
2104
2511
  workspaceId,
2105
- projectId,
2106
- branchId,
2107
2512
  providerParams: PROVIDER_PARAMS
2108
2513
  };
2109
2514
  if (!isComputeRegion(region)) throw new Error(`prismaCloud(): environment variable PRISMA_REGION="${region}" is not a known region (expected one of: ${COMPUTE_REGIONS.join(", ")}).`);
2110
2515
  return {
2111
2516
  workspaceId,
2112
2517
  region,
2113
- projectId,
2114
- branchId,
2115
2518
  providerParams: PROVIDER_PARAMS
2116
2519
  };
2117
2520
  }
@@ -2119,21 +2522,24 @@ function resolveOptions(opts) {
2119
2522
  const prismaCloud = (opts = {}) => {
2120
2523
  const o = resolveOptions(opts);
2121
2524
  return {
2122
- id: "@prisma/composer-prisma-cloud",
2525
+ id: PRISMA_CLOUD_EXTENSION_ID,
2526
+ container: containerDescriptor(),
2123
2527
  providers: () => asProvidersLayer(Layer.mergeAll(providers(), PgWarmProvider(), PnMigrationProvider(), S3CredentialsProvider(), ServiceKeyProvider())),
2124
2528
  preflight: (input) => runPreflight(input),
2125
2529
  teardown: (input) => runTeardown(input),
2126
- application: { provision: () => Effect.gen(function* () {
2127
- const projectId = o.projectId;
2128
- if (projectId === void 0 || projectId.length === 0) throw new Error("prismaCloud(): environment variable PRISMA_PROJECT_ID is required (the CLI sets it — deploy via `prisma-composer deploy`).");
2530
+ application: { provision: (ctx) => Effect.gen(function* () {
2531
+ const { projectId, branchId } = prismaCloudContainerOf(ctx.container);
2129
2532
  for (const key of ["DATABASE_URL", "DATABASE_URL_POOLED"]) yield* EnvironmentVariable(`${key}-poison`, {
2130
2533
  projectId,
2131
2534
  key,
2132
2535
  value: "-",
2133
- class: o.branchId ? "preview" : "production",
2134
- ...o.branchId !== void 0 ? { branchId: o.branchId } : {}
2536
+ class: branchId ? "preview" : "production",
2537
+ ...branchId !== void 0 ? { branchId } : {}
2135
2538
  });
2136
- return { projectId };
2539
+ return {
2540
+ projectId,
2541
+ branchId
2542
+ };
2137
2543
  }) },
2138
2544
  provisions: PROVISIONERS,
2139
2545
  nodes: {
@@ -2141,7 +2547,8 @@ const prismaCloud = (opts = {}) => {
2141
2547
  "prisma-next": prismaNextDescriptor(o),
2142
2548
  compute: computeDescriptor(o),
2143
2549
  credentials: s3CredentialsDescriptor(o),
2144
- "s3-store": s3StoreDescriptor(o)
2550
+ "s3-store": s3StoreDescriptor(o),
2551
+ s3: bucketDescriptor(o)
2145
2552
  }
2146
2553
  };
2147
2554
  };