@prisma/composer-prisma-cloud 0.5.0-dev.2 → 0.5.0-dev.3
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.
- package/dist/auth/pack.mjs +1 -1
- package/dist/auth/pack.mjs.map +1 -1
- package/dist/auth/testing.mjs +1 -1
- package/dist/auth/testing.mjs.map +1 -1
- package/dist/control.mjs +72 -62
- package/dist/control.mjs.map +1 -1
- package/dist/local-target.mjs +1 -1
- package/dist/local-target.mjs.map +1 -1
- package/dist/{s3-credentials-resource-tfirOJBj-BCRHDuEl.mjs → s3-credentials-resource-BMsm9nho-C0Bg1909.mjs} +186 -108
- package/dist/s3-credentials-resource-BMsm9nho-C0Bg1909.mjs.map +1 -0
- package/package.json +18 -18
- package/dist/s3-credentials-resource-tfirOJBj-BCRHDuEl.mjs.map +0 -1
package/dist/control.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { o as paramBindingFor, s as paramName } from "./secret-Doubjztg-CWHCDiuX
|
|
|
2
2
|
import { a as encodeParamPointer, i as encode, l as serializeInput, n as configKey, o as paramEntries } from "./serializer-Ch1Tmx-2-D7cNhkEp.mjs";
|
|
3
3
|
import { a as provisionedEdges, n as SELF_ORIGIN, r as STREAMS_API_KEY, t as RESERVED_PROVIDER_PARAMS } from "./provisioned-edges-COr17uxy-DymAGn_V.mjs";
|
|
4
4
|
import { n as requiredPackHeadOf } from "./required-pack-head-BrrJarzc-DeP50pBu.mjs";
|
|
5
|
-
import { A as packageComputeArtifact, D as EnvironmentVariable, E as Deployment, F as
|
|
5
|
+
import { A as packageComputeArtifact, B as layer, D as EnvironmentVariable, E as Deployment, F as ManagementClient, I as PrismaApiError, L as call, M as BucketKey, N as collectPages, O as ServiceKey, P as resolveDefaultBranchId, R as callVoid, S as Database, T as ComputeService, _ as resolveTargetRef, a as PgWarmProvider, b as providers, d as collectPreflightNames, f as containerDescriptor, g as resolvePrismaNextConfig, h as prismaCloudContainerOf, i as PgWarm, j as Bucket, k as ServiceKeyProvider, l as S3Credentials, m as packHeadRefHashes, n as GeneratedParamProvider, o as PnMigration, r as PRISMA_CLOUD_EXTENSION_ID, s as PnMigrationProvider, t as GeneratedParam, u as S3CredentialsProvider, w as COMPUTE_REGIONS, x as Connection, z as fromEnv } from "./s3-credentials-resource-BMsm9nho-C0Bg1909.mjs";
|
|
6
6
|
import { n as isPnPostgresResourceNode } from "./prisma-next-DmPGOIm2-3V7kdn8T.mjs";
|
|
7
7
|
import { isParamSource } from "@prisma/composer";
|
|
8
8
|
import { blindCast } from "@prisma/composer/casts";
|
|
@@ -21,62 +21,24 @@ import * as Output from "alchemy/Output";
|
|
|
21
21
|
const STATE_DATABASE_NAME = "prisma-composer-state";
|
|
22
22
|
/** Every connection created against a state database carries this prefix — see `cleanupAgedConnections`. */
|
|
23
23
|
const CONNECTION_NAME_PREFIX = "prisma-composer-state-";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const page = yield* call(() => client.GET("/v1/projects/{projectId}/branches", { params: {
|
|
30
|
-
path: { projectId },
|
|
31
|
-
query
|
|
32
|
-
} }));
|
|
33
|
-
branches.push(...page.data);
|
|
34
|
-
if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
|
|
35
|
-
cursor = page.pagination.nextCursor;
|
|
36
|
-
}
|
|
37
|
-
return branches;
|
|
38
|
-
});
|
|
39
|
-
/**
|
|
40
|
-
* The project's implicit default Branch — every live Project owns exactly
|
|
41
|
-
* one (a platform invariant). The list endpoint has no `isDefault` filter, so
|
|
42
|
-
* this pages through every Branch and picks it out client-side. Never creates
|
|
43
|
-
* one: its absence means the platform's invariant is broken, which is not
|
|
44
|
-
* something a deploy can repair.
|
|
45
|
-
*/
|
|
46
|
-
const resolveDefaultBranchId = (client, projectId) => Effect.gen(function* () {
|
|
47
|
-
const found = (yield* listAllBranches(client, projectId)).find((b) => b.isDefault);
|
|
48
|
-
if (found !== void 0) return found.id;
|
|
49
|
-
return yield* Effect.fail(new PrismaApiError({
|
|
50
|
-
status: 0,
|
|
51
|
-
message: `project ${projectId} has no default Branch — the platform guarantees every live Project owns one; contact support.`
|
|
52
|
-
}));
|
|
53
|
-
});
|
|
54
|
-
/** A named stage carries its `branchId`; production omits it, and its state lives on the Project's default Branch. */
|
|
55
|
-
const resolveBranchId = (client, container) => container.branchId !== void 0 ? Effect.succeed(container.branchId) : resolveDefaultBranchId(client, container.projectId);
|
|
24
|
+
/** A named stage carries its `branchId`; production carries `defaultBranchId` (its state lives on the Project's default Branch) — re-resolved only when neither is present. */
|
|
25
|
+
const resolveBranchId = (client, container) => {
|
|
26
|
+
const known = container.branchId ?? container.defaultBranchId;
|
|
27
|
+
return known !== void 0 ? Effect.succeed(known) : resolveDefaultBranchId(client, container.projectId);
|
|
28
|
+
};
|
|
56
29
|
/**
|
|
57
|
-
* Every database on this Branch. Uses the flat
|
|
58
|
-
* accepts `projectId` and `branchId` together —
|
|
59
|
-
* has no branch filter at all.
|
|
30
|
+
* Every database on this Branch (bounded — drivePages). Uses the flat
|
|
31
|
+
* `GET /v1/databases`, which accepts `projectId` and `branchId` together —
|
|
32
|
+
* the project-scoped listing has no branch filter at all.
|
|
60
33
|
*/
|
|
61
|
-
const listAllDatabasesOnBranch = (client, projectId, branchId) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
projectId,
|
|
70
|
-
branchId,
|
|
71
|
-
cursor
|
|
72
|
-
};
|
|
73
|
-
const page = yield* call(() => client.GET("/v1/databases", { params: { query } }));
|
|
74
|
-
databases.push(...page.data);
|
|
75
|
-
if (!page.pagination.hasMore || page.pagination.nextCursor === null) break;
|
|
76
|
-
cursor = page.pagination.nextCursor;
|
|
77
|
-
}
|
|
78
|
-
return databases;
|
|
79
|
-
});
|
|
34
|
+
const listAllDatabasesOnBranch = (client, projectId, branchId) => collectPages(`databases on branch ${branchId}`, (cursor) => call(() => client.GET("/v1/databases", { params: { query: cursor === void 0 ? {
|
|
35
|
+
projectId,
|
|
36
|
+
branchId
|
|
37
|
+
} : {
|
|
38
|
+
projectId,
|
|
39
|
+
branchId,
|
|
40
|
+
cursor
|
|
41
|
+
} } })));
|
|
80
42
|
/**
|
|
81
43
|
* Databases on this Branch named `prisma-composer-state`, oldest first,
|
|
82
44
|
* excluding the Branch's own default database. The default database is always
|
|
@@ -347,6 +309,7 @@ const bootstrapStateConnectionWith = (container, verify) => Effect.gen(function*
|
|
|
347
309
|
yield* cleanupAgedConnections(client, database.id);
|
|
348
310
|
return {
|
|
349
311
|
projectId: container.projectId,
|
|
312
|
+
branchId,
|
|
350
313
|
databaseId: database.id,
|
|
351
314
|
connectionString
|
|
352
315
|
};
|
|
@@ -371,6 +334,50 @@ const deleteStateDatabaseWith = (container, verify) => Effect.gen(function* () {
|
|
|
371
334
|
console.error(`hosted state: removed state database ${candidate.id} from branch ${branchId}`);
|
|
372
335
|
}
|
|
373
336
|
});
|
|
337
|
+
/** Whether the (stack, stage) scope holds any rows in either state table. */
|
|
338
|
+
const scopeOccupied = (sql, stack, stage) => Effect.tryPromise({
|
|
339
|
+
try: async () => {
|
|
340
|
+
return (await sql`
|
|
341
|
+
select
|
|
342
|
+
exists (select 1 from alchemy_resource_state where stack = ${stack} and stage = ${stage})
|
|
343
|
+
or exists (select 1 from alchemy_stack_output where stack = ${stack} and stage = ${stage})
|
|
344
|
+
as occupied
|
|
345
|
+
`)[0]?.occupied === true;
|
|
346
|
+
},
|
|
347
|
+
catch: toStateStoreError
|
|
348
|
+
});
|
|
349
|
+
const listAppsOnBranch = (client, projectId, branchId) => collectPages(`apps on branch ${branchId}`, (cursor) => call(() => client.GET("/v1/apps", { params: { query: cursor === void 0 ? {
|
|
350
|
+
projectId,
|
|
351
|
+
branchId
|
|
352
|
+
} : {
|
|
353
|
+
projectId,
|
|
354
|
+
branchId,
|
|
355
|
+
cursor
|
|
356
|
+
} } })));
|
|
357
|
+
/**
|
|
358
|
+
* The empty-scope-with-live-apps case: the branch-id scope holds no rows
|
|
359
|
+
* while the platform already runs Compute apps on the target Branch. Either
|
|
360
|
+
* this is a pre-branch-id deployment whose rows still sit under a legacy
|
|
361
|
+
* scope (`dev_$USER`, `unknown`, or the old stage name — there is no
|
|
362
|
+
* automatic migration; operator decision TML-3157), or the deploy targets a
|
|
363
|
+
* project that already runs apps. Deploying would recreate every resource
|
|
364
|
+
* and die in per-resource `already_exists` failures — so fail once, up
|
|
365
|
+
* front, naming the empty scope and what exists. Any app on the Branch is
|
|
366
|
+
* this deploy's concern: the Project is app-scoped and the Branch is the
|
|
367
|
+
* deploy's own target. A genuinely fresh deploy sees an empty Branch and
|
|
368
|
+
* passes. Local dev never reaches this — the dev stack pins
|
|
369
|
+
* `state: localState()` (generate-dev-stack.ts).
|
|
370
|
+
*/
|
|
371
|
+
const failOnEmptyScopeWithLiveApps = (projectId, branchId, stack, stage) => Effect.gen(function* () {
|
|
372
|
+
const client = yield* ManagementClient;
|
|
373
|
+
const apps = yield* listAppsOnBranch(client, projectId, branchId);
|
|
374
|
+
if (apps.length === 0) return;
|
|
375
|
+
const names = apps.map((app) => `"${app.name}"`).join(", ");
|
|
376
|
+
return yield* Effect.fail(new PrismaApiError({
|
|
377
|
+
status: 0,
|
|
378
|
+
message: `the deploy state scope "${stage}" is empty, but the platform already runs ${String(apps.length)} app(s) on the target branch ${branchId}: ${names}. With no state, a deploy would recreate every resource and fail with already_exists, and a destroy would remove nothing. If those apps are a deployment from before the branch-id state scope, UPDATE the stage column of alchemy_resource_state and alchemy_stack_output to '${stage}' WHERE stack = '${stack}' (the same database can hold other stacks' rows) in this branch's prisma-composer-state database — or delete the apps in the Prisma Console (or via the Management API) and redeploy fresh. If they are another deployment's, remove them or deploy into a different project. Then retry.`
|
|
379
|
+
}));
|
|
380
|
+
});
|
|
374
381
|
/** Codes for "can't reach the host yet" — DNS/refusal, not a mid-session drop. */
|
|
375
382
|
const ESTABLISHMENT_CODES = /* @__PURE__ */ new Set([
|
|
376
383
|
"ECONNREFUSED",
|
|
@@ -662,14 +669,15 @@ const guardStateService = (service, checkLive, now = Date.now) => {
|
|
|
662
669
|
* state store is `Effect.die` in `Stack.make`).
|
|
663
670
|
*/
|
|
664
671
|
const prismaStateLayer = (ids) => {
|
|
665
|
-
const { projectId, branchId } = ids;
|
|
672
|
+
const { projectId, branchId, defaultBranchId } = ids;
|
|
666
673
|
return Layer.effect(State, Effect.gen(function* () {
|
|
667
674
|
const stack = yield* Stack;
|
|
668
675
|
const container = branchId === void 0 ? projectId : `${projectId}/${branchId}`;
|
|
669
676
|
const bootstrapError = (step) => (cause) => hostedStateBootstrapError(container, step, cause);
|
|
670
|
-
const { connectionString } = yield* bootstrapStateConnection(
|
|
677
|
+
const { connectionString, branchId: stateBranchId } = yield* bootstrapStateConnection({
|
|
671
678
|
projectId,
|
|
672
|
-
branchId
|
|
679
|
+
...branchId !== void 0 ? { branchId } : {},
|
|
680
|
+
...defaultBranchId !== void 0 ? { defaultBranchId } : {}
|
|
673
681
|
}).pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))), Effect.mapError(bootstrapError("resolving the state database on the stage branch")));
|
|
674
682
|
const sql = postgres(Redacted.value(connectionString), {
|
|
675
683
|
max: 5,
|
|
@@ -679,6 +687,7 @@ const prismaStateLayer = (ids) => {
|
|
|
679
687
|
yield* migratePrismaState(sql).pipe(Effect.retry(Schedule.both(Schedule.spaced("5 seconds"), Schedule.during("2 minutes"))), Effect.mapError(bootstrapError("schema migration")));
|
|
680
688
|
const lock = yield* acquireStateLock(sql, stack.name, stack.stage).pipe(Effect.mapError(bootstrapError("lock acquisition")));
|
|
681
689
|
yield* Effect.addFinalizer(() => Effect.promise(() => lock.release()));
|
|
690
|
+
if (!(yield* scopeOccupied(sql, stack.name, stack.stage).pipe(Effect.mapError(bootstrapError("probing the deploy state scope"))))) yield* failOnEmptyScopeWithLiveApps(projectId, stateBranchId, stack.name, stack.stage).pipe(Effect.provide(layer().pipe(Layer.provide(fromEnv()))), Effect.mapError(bootstrapError("checking the empty deploy state scope")));
|
|
682
691
|
const service = guardStateService(makePrismaStateService(sql), lock.checkLive);
|
|
683
692
|
return Effect.succeed(service);
|
|
684
693
|
})).pipe(Layer.orDie);
|
|
@@ -1247,11 +1256,12 @@ const selfOriginValue = (provisioned, address) => Output.map(provisioned.endpoin
|
|
|
1247
1256
|
const prismaState = () => ({
|
|
1248
1257
|
extension: PRISMA_CLOUD_EXTENSION_ID,
|
|
1249
1258
|
create: (container) => {
|
|
1250
|
-
const { projectId, branchId } = prismaCloudContainerOf(container);
|
|
1251
|
-
return prismaStateLayer(
|
|
1259
|
+
const { projectId, branchId, defaultBranchId } = prismaCloudContainerOf(container);
|
|
1260
|
+
return prismaStateLayer({
|
|
1252
1261
|
projectId,
|
|
1253
|
-
branchId
|
|
1254
|
-
|
|
1262
|
+
...branchId !== void 0 ? { branchId } : {},
|
|
1263
|
+
...defaultBranchId !== void 0 ? { defaultBranchId } : {}
|
|
1264
|
+
});
|
|
1255
1265
|
}
|
|
1256
1266
|
});
|
|
1257
1267
|
const KNOWN_REGION_SET = new Set(COMPUTE_REGIONS);
|