@prisma/composer-prisma-cloud 0.5.0 → 0.6.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.
@@ -63,6 +63,169 @@ const callOptional = (f) => attempt(f).pipe(Effect.flatMap((r) => r.response.sta
63
63
  /** Fire-and-forget a call, tolerating a 404 (already deleted). */
64
64
  const callVoid = (f) => attempt(f).pipe(Effect.flatMap((r) => r.response.status === 404 || r.error === void 0 ? Effect.void : fail(r)));
65
65
  //#endregion
66
+ //#region ../../1-prisma-cloud/0-lowering/lowering/dist/container-DW3Yak_S.mjs
67
+ /** Far beyond any real collection size per listing; hitting it means the API's pagination is broken. */
68
+ const MAX_PAGES = 1e3;
69
+ const brokenPaginationError = (description, reason) => new PrismaApiError({
70
+ status: 0,
71
+ message: `listing ${description} ${reason} — the Management API pagination appears broken; refusing to continue with a possibly incomplete listing.`
72
+ });
73
+ /** {@link drivePages} in its most common shape: every page's rows, accumulated. */
74
+ const collectPages = (description, fetchPage) => Effect.gen(function* () {
75
+ const rows = [];
76
+ yield* drivePages(description, fetchPage, (data) => {
77
+ rows.push(...data);
78
+ return false;
79
+ });
80
+ return rows;
81
+ });
82
+ /**
83
+ * Drives a cursor-paginated Management API listing with a guard against
84
+ * broken pagination: a cursor that does not advance, or more than
85
+ * {@link MAX_PAGES} pages, FAILS instead of hanging forever or returning a
86
+ * listing known to be incomplete. `onPage` receives each page's rows as they
87
+ * arrive; returning `true` stops early (the caller found what it wanted).
88
+ */
89
+ const drivePages = (description, fetchPage, onPage) => Effect.gen(function* () {
90
+ let cursor;
91
+ for (let pageCount = 0;; pageCount++) {
92
+ if (pageCount >= MAX_PAGES) return yield* Effect.fail(brokenPaginationError(description, `did not finish within ${String(MAX_PAGES)} pages`));
93
+ const page = yield* fetchPage(cursor);
94
+ if (onPage(page.data)) return;
95
+ const next = page.pagination.nextCursor;
96
+ if (!page.pagination.hasMore || next === null) return;
97
+ if (next === cursor) return yield* Effect.fail(brokenPaginationError(description, "returned a non-advancing cursor"));
98
+ cursor = next;
99
+ }
100
+ });
101
+ /** Raised with `ensure: false` when the app's Project (or a named stage's Branch) doesn't exist. */
102
+ var ContainerNotFoundError = class extends Data.TaggedError("ContainerNotFoundError") {};
103
+ const listAllProjects = (client) => Effect.gen(function* () {
104
+ const projects = [];
105
+ let cursor;
106
+ for (;;) {
107
+ const query = cursor === void 0 ? {} : { cursor };
108
+ const page = yield* call(() => client.GET("/v1/projects", { params: { query } }));
109
+ projects.push(...page.data);
110
+ if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
111
+ cursor = page.pagination.nextCursor;
112
+ }
113
+ return projects;
114
+ });
115
+ /**
116
+ * Workspace ids circulate in two shapes: `wksp_`-prefixed and bare. Compare
117
+ * bare-to-bare so a `wksp_`-prefixed API id still matches a bare configured
118
+ * one (the same normalization `state/bootstrap.ts` applies to the same
119
+ * `/v1/projects` listing).
120
+ */
121
+ const bareWorkspaceId = (id) => id.startsWith("wksp_") ? id.slice(5) : id;
122
+ /**
123
+ * Finds the app's Project by name in the workspace — PDP allows duplicate
124
+ * project names, so more than one can match; the oldest wins. Creates one
125
+ * if none match, unless `ensure` is `false` (find-only — `destroy`), in
126
+ * which case an absent Project fails with `ContainerNotFoundError`. No
127
+ * ownership marker and no `--project` override (both deferred — see
128
+ * ADR-0019).
129
+ */
130
+ const resolveProject = (client, workspaceId, appName, ensure) => Effect.gen(function* () {
131
+ 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];
132
+ if (oldest !== void 0) return oldest.id;
133
+ if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({ appName }));
134
+ return (yield* call(() => client.POST("/v1/projects", { body: {
135
+ name: appName,
136
+ workspaceId,
137
+ createDatabase: false
138
+ } }))).data.id;
139
+ });
140
+ /**
141
+ * The project's implicit default Branch — every live Project owns exactly
142
+ * one (a platform invariant). The list endpoint has no `isDefault` filter,
143
+ * so this pages through the Branches (bounded — drivePages) and returns as
144
+ * soon as a page contains it. Never creates one: its absence means the
145
+ * platform's invariant is broken, which is not something a deploy can
146
+ * repair.
147
+ */
148
+ const resolveDefaultBranchId = (client, projectId) => Effect.gen(function* () {
149
+ let found;
150
+ yield* drivePages(`branches of project ${projectId}`, (cursor) => call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
151
+ path: { projectId },
152
+ query: cursor === void 0 ? {} : { cursor }
153
+ } })), (data) => {
154
+ found = data.find((b) => b.isDefault)?.id;
155
+ return found !== void 0;
156
+ });
157
+ if (found !== void 0) return found;
158
+ return yield* Effect.fail(new PrismaApiError({
159
+ status: 0,
160
+ message: `project ${projectId} has no default Branch — the platform guarantees every live Project owns one; contact support.`
161
+ }));
162
+ });
163
+ const findBranchId = (client, projectId, gitName) => call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
164
+ path: { projectId },
165
+ query: { gitName }
166
+ } })).pipe(Effect.map((page) => page.data[0]?.id));
167
+ /**
168
+ * Finds the stage's Branch by its exact `gitName`, creating it if absent
169
+ * unless `ensure` is `false` (find-only — `destroy`), in which case an
170
+ * absent Branch fails with `ContainerNotFoundError`. The Management API has
171
+ * no server-side "create-or-return" idempotency (`POST
172
+ * /v1/projects/:id/branches` 409s on a duplicate `gitName`, with no request
173
+ * field to make that a no-op), so idempotency is client-side: observe
174
+ * first, and on a racing 409 from create, re-observe rather than fail.
175
+ */
176
+ const resolveBranch = (client, projectId, gitName, appName, ensure) => Effect.gen(function* () {
177
+ const existing = yield* findBranchId(client, projectId, gitName);
178
+ if (existing !== void 0) return existing;
179
+ if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({
180
+ appName,
181
+ stage: gitName
182
+ }));
183
+ return yield* call(() => client.POST("/v1/projects/{projectId}/branches", {
184
+ params: { path: { projectId } },
185
+ body: { gitName }
186
+ })).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)));
187
+ });
188
+ /**
189
+ * Resolves the two containers a stage's deploy runs into (ADR-0019): the
190
+ * app's **Project**, found-or-created by name, and — for a named stage
191
+ * only — its **Branch**, found-or-created by `gitName`. The default stage
192
+ * (no `stage`) creates no Branch; `branchId` is omitted, and the project's
193
+ * default Branch's id is read into `defaultBranchId` instead. With `ensure:
194
+ * false` (`destroy`), nothing is created — an absent Project or Branch
195
+ * fails with `ContainerNotFoundError` instead.
196
+ */
197
+ const resolveContainer = (opts) => Effect.gen(function* () {
198
+ const client = yield* ManagementClient;
199
+ const ensure = opts.ensure ?? true;
200
+ const projectId = yield* resolveProject(client, opts.workspaceId, opts.appName, ensure);
201
+ if (opts.stage === void 0) return {
202
+ projectId,
203
+ defaultBranchId: yield* resolveDefaultBranchId(client, projectId)
204
+ };
205
+ return {
206
+ projectId,
207
+ branchId: yield* resolveBranch(client, projectId, opts.stage, opts.appName, ensure)
208
+ };
209
+ });
210
+ /**
211
+ * Soft-deletes a Branch. Tolerates a 404 (already gone). The API refuses if
212
+ * the Branch still has live members or is the production/default Branch —
213
+ * that surfaces as a `PrismaApiError`.
214
+ */
215
+ const deleteBranch = (branchId) => Effect.gen(function* () {
216
+ const client = yield* ManagementClient;
217
+ yield* callVoid(() => client.DELETE("/v1/branches/{branchId}", { params: { path: { branchId } } }));
218
+ });
219
+ /**
220
+ * Deletes a Project. Tolerates a 404 (already gone). The API refuses with a
221
+ * 400 if the Project still has live dependencies (e.g. another stage's
222
+ * Branch/resources) — that surfaces as a `PrismaApiError`.
223
+ */
224
+ const deleteProject = (projectId) => Effect.gen(function* () {
225
+ const client = yield* ManagementClient;
226
+ yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: projectId } } }));
227
+ });
228
+ //#endregion
66
229
  //#region ../../1-prisma-cloud/0-lowering/lowering/dist/buckets.mjs
67
230
  /** A Prisma **Object Store bucket** inside a project. */
68
231
  const Bucket = Resource("Prisma.Bucket");
@@ -620,105 +783,6 @@ const ProjectProvider = () => Provider.effect(Project, Effect.gen(function* () {
620
783
  }));
621
784
  //#endregion
622
785
  //#region ../../1-prisma-cloud/0-lowering/lowering/dist/index.mjs
623
- /** Raised with `ensure: false` when the app's Project (or a named stage's Branch) doesn't exist. */
624
- var ContainerNotFoundError = class extends Data.TaggedError("ContainerNotFoundError") {};
625
- const listAllProjects = (client) => Effect.gen(function* () {
626
- const projects = [];
627
- let cursor;
628
- for (;;) {
629
- const query = cursor === void 0 ? {} : { cursor };
630
- const page = yield* call(() => client.GET("/v1/projects", { params: { query } }));
631
- projects.push(...page.data);
632
- if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
633
- cursor = page.pagination.nextCursor;
634
- }
635
- return projects;
636
- });
637
- /**
638
- * Workspace ids circulate in two shapes: `wksp_`-prefixed and bare. Compare
639
- * bare-to-bare so a `wksp_`-prefixed API id still matches a bare configured
640
- * one (the same normalization `state/bootstrap.ts` applies to the same
641
- * `/v1/projects` listing).
642
- */
643
- const bareWorkspaceId = (id) => id.startsWith("wksp_") ? id.slice(5) : id;
644
- /**
645
- * Finds the app's Project by name in the workspace — PDP allows duplicate
646
- * project names, so more than one can match; the oldest wins. Creates one
647
- * if none match, unless `ensure` is `false` (find-only — `destroy`), in
648
- * which case an absent Project fails with `ContainerNotFoundError`. No
649
- * ownership marker and no `--project` override (both deferred — see
650
- * ADR-0019).
651
- */
652
- const resolveProject = (client, workspaceId, appName, ensure) => Effect.gen(function* () {
653
- 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];
654
- if (oldest !== void 0) return oldest.id;
655
- if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({ appName }));
656
- return (yield* call(() => client.POST("/v1/projects", { body: {
657
- name: appName,
658
- workspaceId
659
- } }))).data.id;
660
- });
661
- const findBranchId = (client, projectId, gitName) => call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
662
- path: { projectId },
663
- query: { gitName }
664
- } })).pipe(Effect.map((page) => page.data[0]?.id));
665
- /**
666
- * Finds the stage's Branch by its exact `gitName`, creating it if absent
667
- * unless `ensure` is `false` (find-only — `destroy`), in which case an
668
- * absent Branch fails with `ContainerNotFoundError`. The Management API has
669
- * no server-side "create-or-return" idempotency (`POST
670
- * /v1/projects/:id/branches` 409s on a duplicate `gitName`, with no request
671
- * field to make that a no-op), so idempotency is client-side: observe
672
- * first, and on a racing 409 from create, re-observe rather than fail.
673
- */
674
- const resolveBranch = (client, projectId, gitName, appName, ensure) => Effect.gen(function* () {
675
- const existing = yield* findBranchId(client, projectId, gitName);
676
- if (existing !== void 0) return existing;
677
- if (!ensure) return yield* Effect.fail(new ContainerNotFoundError({
678
- appName,
679
- stage: gitName
680
- }));
681
- return yield* call(() => client.POST("/v1/projects/{projectId}/branches", {
682
- params: { path: { projectId } },
683
- body: { gitName }
684
- })).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)));
685
- });
686
- /**
687
- * Resolves the two containers a stage's deploy runs into (ADR-0019): the
688
- * app's **Project**, found-or-created by name, and — for a named stage
689
- * only — its **Branch**, found-or-created by `gitName`. The default stage
690
- * (no `stage`) creates no Branch; `branchId` is omitted. With `ensure:
691
- * false` (`destroy`), nothing is created — an absent Project or Branch
692
- * fails with `ContainerNotFoundError` instead.
693
- */
694
- const resolveContainer = (opts) => Effect.gen(function* () {
695
- const client = yield* ManagementClient;
696
- const ensure = opts.ensure ?? true;
697
- const projectId = yield* resolveProject(client, opts.workspaceId, opts.appName, ensure);
698
- if (opts.stage === void 0) return { projectId };
699
- return {
700
- projectId,
701
- branchId: yield* resolveBranch(client, projectId, opts.stage, opts.appName, ensure)
702
- };
703
- });
704
- /**
705
- * Soft-deletes a Branch. Tolerates a 404 (already gone). The API refuses if
706
- * the Branch still has live members or is the production/default Branch —
707
- * that surfaces as a `PrismaApiError`.
708
- */
709
- const deleteBranch = (branchId) => Effect.gen(function* () {
710
- const client = yield* ManagementClient;
711
- yield* callVoid(() => client.DELETE("/v1/branches/{branchId}", { params: { path: { branchId } } }));
712
- });
713
- /**
714
- * Deletes a Project. Tolerates a 404 (already gone). The API refuses with a
715
- * 400 if the Project still has live dependencies (e.g. another stage's
716
- * Branch/resources) — that surfaces as a `PrismaApiError`.
717
- */
718
- const deleteProject = (projectId) => Effect.gen(function* () {
719
- const client = yield* ManagementClient;
720
- yield* callVoid(() => client.DELETE("/v1/projects/{id}", { params: { path: { id: projectId } } }));
721
- });
722
786
  /** The collection of Prisma resource providers. */
723
787
  var Providers = class extends Provider.ProviderCollection()("Prisma") {};
724
788
  /**
@@ -763,22 +827,35 @@ function mintKeyPair() {
763
827
  };
764
828
  }
765
829
  //#endregion
766
- //#region ../../1-prisma-cloud/1-extensions/target/dist/s3-credentials-resource-tfirOJBj.mjs
830
+ //#region ../../1-prisma-cloud/1-extensions/target/dist/s3-credentials-resource-BMsm9nho.mjs
767
831
  const PRISMA_CLOUD_EXTENSION_ID = "@prisma/composer-prisma-cloud";
832
+ /** Accepts exactly what Alchemy's own `--stage` validation accepts (pinned 2.0.0-beta.59, `Cli/commands/_shared.ts`), rewritten without overlapping quantifiers so it cannot backtrack catastrophically. Asserted before a Branch id is exposed as a stage. */
833
+ const ALCHEMY_STAGE_PATTERN = /^[a-z0-9][-_a-z0-9]*$/i;
834
+ function invalidAlchemyStageError(branchId) {
835
+ return /* @__PURE__ */ new Error(`${PRISMA_CLOUD_EXTENSION_ID}: the resolved Branch id "${branchId}" does not match Alchemy's stage pattern ^[a-z0-9][-_a-z0-9]*\$ (case-insensitive) — it cannot scope the deploy state. The platform should never return such an id; contact support.`);
836
+ }
768
837
  var PrismaCloudContainer = class {
769
838
  input;
770
839
  projectId;
771
840
  branchId;
772
- constructor(input, projectId, branchId) {
841
+ defaultBranchId;
842
+ /** The deterministic Alchemy stage (ContainerInstance SPI): the stage Branch's id, or the default Branch's id for the default stage. Absent only for the dev container, which resolves no Branch. */
843
+ alchemyStage;
844
+ constructor(input, projectId, branchId, defaultBranchId) {
773
845
  this.input = input;
774
846
  this.projectId = projectId;
775
847
  this.branchId = branchId;
848
+ this.defaultBranchId = defaultBranchId;
849
+ const stageBranchId = branchId ?? defaultBranchId;
850
+ if (stageBranchId !== void 0 && !ALCHEMY_STAGE_PATTERN.test(stageBranchId)) throw invalidAlchemyStageError(stageBranchId);
851
+ this.alchemyStage = stageBranchId;
776
852
  }
777
853
  serialize() {
778
854
  return JSON.stringify({
779
855
  input: this.input,
780
856
  projectId: this.projectId,
781
- ...this.branchId !== void 0 ? { branchId: this.branchId } : {}
857
+ ...this.branchId !== void 0 ? { branchId: this.branchId } : {},
858
+ ...this.defaultBranchId !== void 0 ? { defaultBranchId: this.defaultBranchId } : {}
782
859
  });
783
860
  }
784
861
  };
@@ -821,10 +898,12 @@ function deserialize(serialized) {
821
898
  if (typeof projectId !== "string") throw invalidPayloadError("\"projectId\" is not a string");
822
899
  const branchId = parsed["branchId"];
823
900
  if (branchId !== void 0 && typeof branchId !== "string") throw invalidPayloadError("\"branchId\" is not a string or absent");
901
+ const defaultBranchId = parsed["defaultBranchId"];
902
+ if (defaultBranchId !== void 0 && typeof defaultBranchId !== "string") throw invalidPayloadError("\"defaultBranchId\" is not a string or absent");
824
903
  return new PrismaCloudContainer({
825
904
  appName,
826
905
  stage
827
- }, projectId, branchId);
906
+ }, projectId, branchId, defaultBranchId);
828
907
  }
829
908
  const workspaceRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_WORKSPACE_ID is required.");
830
909
  const tokenRequiredError = () => /* @__PURE__ */ new Error("environment variable PRISMA_SERVICE_TOKEN is required.");
@@ -854,7 +933,7 @@ async function ensureContainer(input, deps) {
854
933
  const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
855
934
  const outcome = await Effect.runPromise(provided);
856
935
  if (!outcome.ok) throw new Error(outcome.message);
857
- return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
936
+ return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId, outcome.container.defaultBranchId);
858
937
  }
859
938
  async function locateContainer(input, deps) {
860
939
  const workspaceId = requireWorkspaceId();
@@ -871,7 +950,7 @@ async function locateContainer(input, deps) {
871
950
  const provided = deps?.client !== void 0 ? program.pipe(Effect.provideService(ManagementClient, deps.client)) : program.pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))));
872
951
  const outcome = await Effect.runPromise(provided);
873
952
  if (!outcome.ok) return void 0;
874
- return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId);
953
+ return new PrismaCloudContainer(input, outcome.container.projectId, outcome.container.branchId, outcome.container.defaultBranchId);
875
954
  }
876
955
  /**
877
956
  * Soft-deletes a named stage's Branch after a successful `alchemy destroy`
@@ -1350,6 +1429,6 @@ const s3CredentialsProviderService = {
1350
1429
  /** The `S3Credentials` provider layer — merged into the extension descriptor's `providers()`. */
1351
1430
  const S3CredentialsProvider = () => Provider.effect(S3Credentials, Effect.succeed(s3CredentialsProviderService));
1352
1431
  //#endregion
1353
- export { packageComputeArtifact as A, Project as C, EnvironmentVariable as D, Deployment as E, call as F, callVoid as I, fromEnv as L, BucketKey as M, ManagementClient as N, ServiceKey as O, PrismaApiError as P, layer as R, Database as S, ComputeService as T, resolveTargetRef as _, PgWarmProvider as a, providers as b, PrismaCloudContainer as c, collectPreflightNames as d, containerDescriptor as f, resolvePrismaNextConfig as g, prismaCloudContainerOf as h, PgWarm as i, Bucket as j, ServiceKeyProvider as k, S3Credentials as l, packHeadRefHashes as m, GeneratedParamProvider as n, PnMigration as o, deserialize as p, PRISMA_CLOUD_EXTENSION_ID as r, PnMigrationProvider as s, GeneratedParam as t, S3CredentialsProvider as u, mintKeyPair as v, COMPUTE_REGIONS as w, Connection as x, Providers as y };
1432
+ export { packageComputeArtifact as A, layer as B, Project as C, EnvironmentVariable as D, Deployment as E, ManagementClient as F, PrismaApiError as I, call as L, BucketKey as M, collectPages as N, ServiceKey as O, resolveDefaultBranchId as P, callVoid as R, Database as S, ComputeService as T, resolveTargetRef as _, PgWarmProvider as a, providers as b, PrismaCloudContainer as c, collectPreflightNames as d, containerDescriptor as f, resolvePrismaNextConfig as g, prismaCloudContainerOf as h, PgWarm as i, Bucket as j, ServiceKeyProvider as k, S3Credentials as l, packHeadRefHashes as m, GeneratedParamProvider as n, PnMigration as o, deserialize as p, PRISMA_CLOUD_EXTENSION_ID as r, PnMigrationProvider as s, GeneratedParam as t, S3CredentialsProvider as u, mintKeyPair as v, COMPUTE_REGIONS as w, Connection as x, Providers as y, fromEnv as z };
1354
1433
 
1355
- //# sourceMappingURL=s3-credentials-resource-tfirOJBj-79G9R_h8.mjs.map
1434
+ //# sourceMappingURL=s3-credentials-resource-BMsm9nho-C0Bg1909.mjs.map