@prisma/composer-prisma-cloud 0.30.0-dev.3 → 0.30.0-dev.5
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/buckets-main.mjs +101 -82
- package/dist/buckets-main.mjs.map +1 -1
- package/dist/control.mjs +3 -3
- package/dist/control.mjs.map +1 -1
- package/dist/local-target.d.mts.map +1 -1
- package/dist/local-target.mjs +782 -416
- package/dist/local-target.mjs.map +1 -1
- package/dist/postgres-main.mjs +324 -193
- package/dist/postgres-main.mjs.map +1 -1
- package/dist/{s3-credentials-resource-BZjtuopJ-azxxffwQ.mjs → s3-credentials-resource-D4jcpAZp-DrGYkOaZ.mjs} +50 -10
- package/dist/{s3-credentials-resource-BZjtuopJ-azxxffwQ.mjs.map → s3-credentials-resource-D4jcpAZp-DrGYkOaZ.mjs.map} +1 -1
- package/package.json +18 -18
- package/dist/segments-CA7TOJ1U-CoICbb95.mjs +0 -14
- package/dist/segments-CA7TOJ1U-CoICbb95.mjs.map +0 -1
package/dist/local-target.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as blindCast$1 } from "./casts-DpaahrlC-Ykus3kCE.mjs";
|
|
2
|
-
import { E as
|
|
2
|
+
import { C as Providers, E as ServiceKeyProvider, O as isWithin, S as mintKeyPair, b as serviceAppName, c as PgWarmProvider, d as S3CredentialsProvider, f as collectPreflightNames, g as prismaCloudContainerOf, l as PrismaCloudContainer, m as deserialize, n as GeneratedParamProvider, r as LocalOrmMigrationProvider, y as resourceName } from "./s3-credentials-resource-D4jcpAZp-DrGYkOaZ.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { blindCast } from "@prisma/composer/casts";
|
|
5
5
|
import * as Effect from "effect/Effect";
|
|
@@ -58,6 +58,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
58
58
|
}) : target, mod));
|
|
59
59
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
60
60
|
//#endregion
|
|
61
|
+
//#region ../../0-framework/0-foundation/foundation/dist/internal-error-Dfur7Zqt.mjs
|
|
62
|
+
/**
|
|
63
|
+
* A bug in Prisma ORM, not a user error. Never catch this except at the
|
|
64
|
+
* outermost boundary for crash reporting — an InternalError means an invariant
|
|
65
|
+
* broke and the process cannot reliably continue. User-facing failures use
|
|
66
|
+
* `structuredError` with a dotted code instead.
|
|
67
|
+
*/
|
|
68
|
+
var InternalError = class extends Error {
|
|
69
|
+
isPrismaInternalError = true;
|
|
70
|
+
constructor(message, options) {
|
|
71
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
72
|
+
this.name = "InternalError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
61
76
|
//#region ../../0-framework/0-foundation/foundation/dist/errors.mjs
|
|
62
77
|
/**
|
|
63
78
|
* Returns an object with the key/value if value is defined, otherwise an empty object.
|
|
@@ -1780,6 +1795,9 @@ function isPidAlive(pid) {
|
|
|
1780
1795
|
return false;
|
|
1781
1796
|
}
|
|
1782
1797
|
}
|
|
1798
|
+
async function readRegistryEntry(registryRoot, name) {
|
|
1799
|
+
return readJsonFile(registryFilePath(registryRoot, name), isRegistryEntry$1);
|
|
1800
|
+
}
|
|
1783
1801
|
async function probeHealth(port, healthPath, timeoutMs) {
|
|
1784
1802
|
try {
|
|
1785
1803
|
const res = await fetch(`http://127.0.0.1:${port}${healthPath}`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -2050,50 +2068,53 @@ async function ensureDaemon(name, entry, opts = {}) {
|
|
|
2050
2068
|
await release();
|
|
2051
2069
|
}
|
|
2052
2070
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
*
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
/**
|
|
2078
|
-
*
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
*
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2071
|
+
(class ResultImpl {
|
|
2072
|
+
ok;
|
|
2073
|
+
_value;
|
|
2074
|
+
_failure;
|
|
2075
|
+
constructor(ok, valueOrFailure) {
|
|
2076
|
+
this.ok = ok;
|
|
2077
|
+
if (ok) this._value = blindCast$1(valueOrFailure);
|
|
2078
|
+
else this._failure = blindCast$1(valueOrFailure);
|
|
2079
|
+
Object.freeze(this);
|
|
2080
|
+
}
|
|
2081
|
+
get value() {
|
|
2082
|
+
if (!this.ok) throw new InternalError("Cannot access value on NotOk result");
|
|
2083
|
+
return this._value;
|
|
2084
|
+
}
|
|
2085
|
+
get failure() {
|
|
2086
|
+
if (this.ok) throw new InternalError("Cannot access failure on Ok result");
|
|
2087
|
+
return this._failure;
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Creates a successful result.
|
|
2091
|
+
*/
|
|
2092
|
+
static ok(value) {
|
|
2093
|
+
return blindCast$1(new ResultImpl(true, value));
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Creates an unsuccessful result.
|
|
2097
|
+
*/
|
|
2098
|
+
static notOk(failure) {
|
|
2099
|
+
return blindCast$1(new ResultImpl(false, failure));
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Asserts that this result is Ok and returns the value.
|
|
2103
|
+
* Throws if the result is NotOk.
|
|
2104
|
+
*/
|
|
2105
|
+
assertOk() {
|
|
2106
|
+
if (!this.ok) throw new InternalError("Expected Ok result but got NotOk");
|
|
2107
|
+
return this.value;
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Asserts that this result is NotOk and returns the failure.
|
|
2111
|
+
* Throws if the result is Ok.
|
|
2112
|
+
*/
|
|
2113
|
+
assertNotOk() {
|
|
2114
|
+
if (this.ok) throw new InternalError("Expected NotOk result but got Ok");
|
|
2115
|
+
return this.failure;
|
|
2116
|
+
}
|
|
2117
|
+
}).ok(void 0);
|
|
2097
2118
|
//#endregion
|
|
2098
2119
|
//#region ../../1-prisma-cloud/0-lowering/dev-emulators/dist/index.mjs
|
|
2099
2120
|
/**
|
|
@@ -2126,10 +2147,6 @@ function resolveBaseUrl(name, opts) {
|
|
|
2126
2147
|
if (!isRegistryEntry(parsed) || !isPidAlive(parsed.pid)) throw notRunningError(name);
|
|
2127
2148
|
return `http://127.0.0.1:${parsed.port}`;
|
|
2128
2149
|
}
|
|
2129
|
-
function encodeSegment(segment) {
|
|
2130
|
-
if (!isValidSegment(segment)) throw new Error(`invalid path segment "${segment}"`);
|
|
2131
|
-
return encodeURIComponent(segment);
|
|
2132
|
-
}
|
|
2133
2150
|
const ADMIN_FETCH_ATTEMPTS = 3;
|
|
2134
2151
|
const ADMIN_FETCH_RETRY_DELAY_MS = 150;
|
|
2135
2152
|
/**
|
|
@@ -2176,11 +2193,11 @@ const INSTANCE_STATUSES = /* @__PURE__ */ new Set([
|
|
|
2176
2193
|
function isStringRecord$1(value) {
|
|
2177
2194
|
return typeof value === "object" && value !== null && Object.values(value).every((v) => typeof v === "string");
|
|
2178
2195
|
}
|
|
2179
|
-
function
|
|
2196
|
+
function isServiceInstanceInfo(value) {
|
|
2180
2197
|
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "labels" in value && isStringRecord$1(value.labels) && "port" in value && typeof value.port === "number" && "url" in value && typeof value.url === "string" && "status" in value && typeof value.status === "string" && INSTANCE_STATUSES.has(value.status) && "logPath" in value && typeof value.logPath === "string";
|
|
2181
2198
|
}
|
|
2182
|
-
function
|
|
2183
|
-
return Array.isArray(value) && value.every(
|
|
2199
|
+
function isServiceInstanceInfoArray(value) {
|
|
2200
|
+
return Array.isArray(value) && value.every(isServiceInstanceInfo);
|
|
2184
2201
|
}
|
|
2185
2202
|
function isHealthBody(value) {
|
|
2186
2203
|
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
|
|
@@ -2206,19 +2223,19 @@ function computeClient(opts = {}) {
|
|
|
2206
2223
|
},
|
|
2207
2224
|
async createInstance(request) {
|
|
2208
2225
|
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`, postJson(request), { retry: false }))).json();
|
|
2209
|
-
if (!
|
|
2226
|
+
if (!isServiceInstanceInfo(body)) throw new Error("malformed instance-create response from the compute emulator");
|
|
2210
2227
|
return body;
|
|
2211
2228
|
},
|
|
2212
2229
|
async listInstances() {
|
|
2213
2230
|
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`))).json();
|
|
2214
|
-
if (!
|
|
2231
|
+
if (!isServiceInstanceInfoArray(body)) throw new Error("malformed instances listing from the compute emulator");
|
|
2215
2232
|
return body;
|
|
2216
2233
|
},
|
|
2217
2234
|
async getInstance(id) {
|
|
2218
2235
|
const res = await adminFetch(instancePath(baseUrl, id));
|
|
2219
2236
|
if (res.status === 404) return void 0;
|
|
2220
2237
|
const body = await (await expectOk(res)).json();
|
|
2221
|
-
if (!
|
|
2238
|
+
if (!isServiceInstanceInfo(body)) throw new Error("malformed instance response from the compute emulator");
|
|
2222
2239
|
return body;
|
|
2223
2240
|
},
|
|
2224
2241
|
async putDeployment(id, deployment) {
|
|
@@ -2255,8 +2272,15 @@ function computeClient(opts = {}) {
|
|
|
2255
2272
|
}
|
|
2256
2273
|
};
|
|
2257
2274
|
}
|
|
2275
|
+
function isBucketInstanceInfo(value) {
|
|
2276
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "labels" in value && isStringRecord$1(value.labels) && "dir" in value && typeof value.dir === "string";
|
|
2277
|
+
}
|
|
2278
|
+
function isBucketInstanceInfoArray(value) {
|
|
2279
|
+
return Array.isArray(value) && value.every(isBucketInstanceInfo);
|
|
2280
|
+
}
|
|
2258
2281
|
function bucketsClient(opts = {}) {
|
|
2259
2282
|
const baseUrl = resolveBaseUrl("buckets", opts);
|
|
2283
|
+
const adminUrl = `${baseUrl}/_pcdev/instances`;
|
|
2260
2284
|
return {
|
|
2261
2285
|
baseUrl,
|
|
2262
2286
|
async health() {
|
|
@@ -2264,15 +2288,25 @@ function bucketsClient(opts = {}) {
|
|
|
2264
2288
|
if (!isHealthBody(body)) throw new Error("malformed /_pcdev/health response from the buckets emulator");
|
|
2265
2289
|
return body;
|
|
2266
2290
|
},
|
|
2267
|
-
async
|
|
2268
|
-
await expectOk(await adminFetch(
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2291
|
+
async createInstance(request) {
|
|
2292
|
+
const body = await (await expectOk(await adminFetch(adminUrl, postJson(request), { retry: false }))).json();
|
|
2293
|
+
if (!isBucketInstanceInfo(body)) throw new Error("malformed instance-create response from the buckets emulator");
|
|
2294
|
+
return body;
|
|
2295
|
+
},
|
|
2296
|
+
async listInstances() {
|
|
2297
|
+
const body = await (await expectOk(await adminFetch(adminUrl))).json();
|
|
2298
|
+
if (!isBucketInstanceInfoArray(body)) throw new Error("malformed instances listing from the buckets emulator");
|
|
2299
|
+
return body;
|
|
2300
|
+
},
|
|
2301
|
+
async getInstance(id) {
|
|
2302
|
+
const res = await adminFetch(instancePath(`${baseUrl}/_pcdev`, id));
|
|
2303
|
+
if (res.status === 404) return void 0;
|
|
2304
|
+
const body = await (await expectOk(res)).json();
|
|
2305
|
+
if (!isBucketInstanceInfo(body)) throw new Error("malformed instance response from the buckets emulator");
|
|
2306
|
+
return body;
|
|
2273
2307
|
},
|
|
2274
|
-
async putCredentials(
|
|
2275
|
-
await expectOk(await adminFetch(`${baseUrl}/_pcdev
|
|
2308
|
+
async putCredentials(id, accessKeyId, secretAccessKey) {
|
|
2309
|
+
await expectOk(await adminFetch(`${instancePath(`${baseUrl}/_pcdev`, id)}/credentials`, {
|
|
2276
2310
|
method: "PUT",
|
|
2277
2311
|
headers: { "content-type": "application/json" },
|
|
2278
2312
|
body: JSON.stringify({
|
|
@@ -2281,19 +2315,16 @@ function bucketsClient(opts = {}) {
|
|
|
2281
2315
|
})
|
|
2282
2316
|
}));
|
|
2283
2317
|
},
|
|
2284
|
-
async
|
|
2285
|
-
await expectOk(await adminFetch(`${baseUrl}/_pcdev
|
|
2318
|
+
async deleteInstance(id) {
|
|
2319
|
+
await expectOk(await adminFetch(instancePath(`${baseUrl}/_pcdev`, id), { method: "DELETE" }));
|
|
2286
2320
|
}
|
|
2287
2321
|
};
|
|
2288
2322
|
}
|
|
2289
|
-
function
|
|
2290
|
-
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "
|
|
2323
|
+
function isDatabaseInstanceInfo(value) {
|
|
2324
|
+
return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "labels" in value && isStringRecord$1(value.labels) && "port" in value && typeof value.port === "number" && "url" in value && typeof value.url === "string" && "status" in value && (value.status === "running" || value.status === "stopped");
|
|
2291
2325
|
}
|
|
2292
|
-
function
|
|
2293
|
-
return Array.isArray(value) && value.every(
|
|
2294
|
-
}
|
|
2295
|
-
function isEnsuredDatabase(value) {
|
|
2296
|
-
return typeof value === "object" && value !== null && "url" in value && typeof value.url === "string";
|
|
2326
|
+
function isDatabaseInstanceInfoArray(value) {
|
|
2327
|
+
return Array.isArray(value) && value.every(isDatabaseInstanceInfo);
|
|
2297
2328
|
}
|
|
2298
2329
|
function postgresClient(opts = {}) {
|
|
2299
2330
|
const baseUrl = resolveBaseUrl("postgres", opts);
|
|
@@ -2304,25 +2335,53 @@ function postgresClient(opts = {}) {
|
|
|
2304
2335
|
if (!isHealthBody(body)) throw new Error("malformed /health response from the postgres emulator");
|
|
2305
2336
|
return body;
|
|
2306
2337
|
},
|
|
2307
|
-
async
|
|
2308
|
-
const body = await (await expectOk(await adminFetch(`${baseUrl}/
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2338
|
+
async createInstance(request) {
|
|
2339
|
+
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`, postJson(request), { retry: false }))).json();
|
|
2340
|
+
if (!isDatabaseInstanceInfo(body)) throw new Error("malformed instance-create response from the postgres emulator");
|
|
2341
|
+
return body;
|
|
2342
|
+
},
|
|
2343
|
+
async listInstances() {
|
|
2344
|
+
const body = await (await expectOk(await adminFetch(`${baseUrl}/instances`))).json();
|
|
2345
|
+
if (!isDatabaseInstanceInfoArray(body)) throw new Error("malformed instances listing from the postgres emulator");
|
|
2314
2346
|
return body;
|
|
2315
2347
|
},
|
|
2316
|
-
async
|
|
2317
|
-
const
|
|
2318
|
-
if (
|
|
2348
|
+
async getInstance(id) {
|
|
2349
|
+
const res = await adminFetch(instancePath(baseUrl, id));
|
|
2350
|
+
if (res.status === 404) return void 0;
|
|
2351
|
+
const body = await (await expectOk(res)).json();
|
|
2352
|
+
if (!isDatabaseInstanceInfo(body)) throw new Error("malformed instance response from the postgres emulator");
|
|
2319
2353
|
return body;
|
|
2320
2354
|
},
|
|
2321
|
-
async
|
|
2322
|
-
await expectOk(await adminFetch(`${baseUrl}/
|
|
2355
|
+
async startInstances(ids, prismaDevModulePath) {
|
|
2356
|
+
await expectOk(await adminFetch(`${baseUrl}/instances/start`, postJson({
|
|
2357
|
+
ids,
|
|
2358
|
+
prismaDevModulePath
|
|
2359
|
+
})));
|
|
2360
|
+
},
|
|
2361
|
+
async stopInstances(ids) {
|
|
2362
|
+
await expectOk(await adminFetch(`${baseUrl}/instances/stop`, postJson({ ids })));
|
|
2363
|
+
},
|
|
2364
|
+
async deleteInstance(id, prismaDevModulePath) {
|
|
2365
|
+
await expectOk(await adminFetch(instancePath(baseUrl, id), {
|
|
2366
|
+
method: "DELETE",
|
|
2367
|
+
...prismaDevModulePath !== void 0 ? {
|
|
2368
|
+
headers: { "content-type": "application/json" },
|
|
2369
|
+
body: JSON.stringify({ prismaDevModulePath })
|
|
2370
|
+
} : {}
|
|
2371
|
+
}));
|
|
2323
2372
|
}
|
|
2324
2373
|
};
|
|
2325
2374
|
}
|
|
2375
|
+
const DAEMON_OF$1 = {
|
|
2376
|
+
service: "compute",
|
|
2377
|
+
database: "postgres",
|
|
2378
|
+
bucket: "buckets"
|
|
2379
|
+
};
|
|
2380
|
+
/** Whether the emulator for `kind` is registered with a live process. */
|
|
2381
|
+
async function isEmulatorRunning(kind, opts = {}) {
|
|
2382
|
+
const entry = await readRegistryEntry(opts.registryRoot ?? defaultRegistryRoot(), DAEMON_OF$1[kind]);
|
|
2383
|
+
return entry !== void 0 && isPidAlive(entry.pid);
|
|
2384
|
+
}
|
|
2326
2385
|
//#endregion
|
|
2327
2386
|
//#region ../../../node_modules/tar/dist/esm/index.min.js
|
|
2328
2387
|
var zr = Object.defineProperty;
|
|
@@ -5640,156 +5699,6 @@ function appNameOf(container) {
|
|
|
5640
5699
|
return container.input.appName;
|
|
5641
5700
|
}
|
|
5642
5701
|
/**
|
|
5643
|
-
* Shared helpers for the upstream-attribute records every local provider
|
|
5644
|
-
* emits — one implementation, so the compute and postgres families cannot
|
|
5645
|
-
* drift on the `'local'` project fallback or the timestamp.
|
|
5646
|
-
*/
|
|
5647
|
-
/** The fixed `createdAt`/`updatedAt` local providers stamp: local dev has no meaningful creation time. */
|
|
5648
|
-
const DEV_TIMESTAMP = "1970-01-01T00:00:00.000Z";
|
|
5649
|
-
/** Reads a project id from upstream's `project` input: a plain string or a resolved `Prisma.Project` attributes record. */
|
|
5650
|
-
function projectIdOfInput(value) {
|
|
5651
|
-
if (typeof value === "string") return value;
|
|
5652
|
-
if (Predicate.isObject(value) && typeof value["projectId"] === "string") return value["projectId"];
|
|
5653
|
-
return "local";
|
|
5654
|
-
}
|
|
5655
|
-
/**
|
|
5656
|
-
* Local bucket-cluster providers (local-dev spec § 4): both `Bucket` and
|
|
5657
|
-
* `BucketAccessKey` are clients of the machine-global bucket emulator —
|
|
5658
|
-
* already up by the time these run, since the extension's
|
|
5659
|
-
* `localTarget.emulators` hook ensures it before converge.
|
|
5660
|
-
*/
|
|
5661
|
-
/** Upstream lets `name` default to the resource's logical ID; locally a bucket must have a concrete name to register under. */
|
|
5662
|
-
const bucketNameOf = (news, id) => news.name ?? id;
|
|
5663
|
-
/** Reads a bucket id from upstream's `bucket` input: a plain string or a resolved `Prisma.Bucket` attributes record. */
|
|
5664
|
-
function bucketIdOfInput(value) {
|
|
5665
|
-
if (typeof value === "string") return value;
|
|
5666
|
-
if (Predicate.isObject(value) && typeof value["bucketId"] === "string") return value["bucketId"];
|
|
5667
|
-
return "local";
|
|
5668
|
-
}
|
|
5669
|
-
/** `Bucket` → registers `<app>--<name>` with the bucket emulator, backed by an in-project data root. */
|
|
5670
|
-
function LocalBucketProvider(input) {
|
|
5671
|
-
return Provider.effect(Bucket, Effect.succeed({
|
|
5672
|
-
list: () => Effect.succeed([]),
|
|
5673
|
-
reconcile: ({ news, id }) => Effect.tryPromise({
|
|
5674
|
-
try: async () => {
|
|
5675
|
-
const app = appNameOf(input.container);
|
|
5676
|
-
const name = bucketNameOf(news, id);
|
|
5677
|
-
const dir = path$1.join(input.devDir, "buckets", name);
|
|
5678
|
-
await bucketsClient().putBucket(app, name, dir);
|
|
5679
|
-
return {
|
|
5680
|
-
bucketId: name,
|
|
5681
|
-
name,
|
|
5682
|
-
projectId: projectIdOfInput(news.project),
|
|
5683
|
-
createdAt: DEV_TIMESTAMP,
|
|
5684
|
-
logicalId: null
|
|
5685
|
-
};
|
|
5686
|
-
},
|
|
5687
|
-
catch: (cause) => cause
|
|
5688
|
-
}),
|
|
5689
|
-
delete: () => Effect.void,
|
|
5690
|
-
read: ({ output }) => Effect.succeed(output)
|
|
5691
|
-
}));
|
|
5692
|
-
}
|
|
5693
|
-
/**
|
|
5694
|
-
* `BucketAccessKey` → mint-once-stable (the same lifecycle as `ServiceKey`/
|
|
5695
|
-
* `S3Credentials`: reuse `output`'s pair when present, mint only on first
|
|
5696
|
-
* create) — but ALWAYS re-registers the (prior or fresh) pair with the
|
|
5697
|
-
* emulator, so a bucket emulator whose own state was wiped self-heals on the
|
|
5698
|
-
* next converge.
|
|
5699
|
-
*/
|
|
5700
|
-
function LocalBucketAccessKeyProvider(input) {
|
|
5701
|
-
return Provider.effect(BucketAccessKey, Effect.succeed({
|
|
5702
|
-
list: () => Effect.succeed([]),
|
|
5703
|
-
reconcile: ({ news, output, id }) => Effect.tryPromise({
|
|
5704
|
-
try: async () => {
|
|
5705
|
-
const app = appNameOf(input.container);
|
|
5706
|
-
const pair = output !== void 0 ? {
|
|
5707
|
-
accessKeyId: output.accessKeyId,
|
|
5708
|
-
secretAccessKey: Redacted.value(output.secretAccessKey)
|
|
5709
|
-
} : mintKeyPair();
|
|
5710
|
-
const client = bucketsClient();
|
|
5711
|
-
await client.putCredentials(app, pair.accessKeyId, pair.secretAccessKey);
|
|
5712
|
-
const bucketId = bucketIdOfInput(news.bucket);
|
|
5713
|
-
return {
|
|
5714
|
-
bucketAccessKeyId: news.name ?? id,
|
|
5715
|
-
bucketId,
|
|
5716
|
-
accessKeyId: pair.accessKeyId,
|
|
5717
|
-
secretAccessKey: Redacted.make(pair.secretAccessKey),
|
|
5718
|
-
endpoint: client.baseUrl,
|
|
5719
|
-
bucketName: `${app}--${bucketId}`
|
|
5720
|
-
};
|
|
5721
|
-
},
|
|
5722
|
-
catch: (cause) => cause
|
|
5723
|
-
}),
|
|
5724
|
-
delete: () => Effect.void,
|
|
5725
|
-
read: ({ output }) => Effect.succeed(output)
|
|
5726
|
-
}));
|
|
5727
|
-
}
|
|
5728
|
-
/**
|
|
5729
|
-
* Extracts the tar.gz `@internal/lowering`'s `packageComputeArtifact` writes
|
|
5730
|
-
* (local-dev spec § 4) — emulation-only code, so it lives here rather than
|
|
5731
|
-
* beside the writer (production source stays physically separated from
|
|
5732
|
-
* emulation). Reading tar is commodity even though the writer is a
|
|
5733
|
-
* deterministic subset we own (fixed mtimes, sorted entries): the maintained
|
|
5734
|
-
* `tar` package (dependency razor) does the parsing; this module keeps only
|
|
5735
|
-
* the pinned entry filtering (regular files plus safe relative symlinks,
|
|
5736
|
-
* reject devices/path escapes) and the directory-level temp-then-rename.
|
|
5737
|
-
*/
|
|
5738
|
-
function unsupportedEntryError(entryPath, type) {
|
|
5739
|
-
return /* @__PURE__ */ new Error(`compute artifact entry "${entryPath}" has type "${type}" — only regular files and safe symlinks are supported; this artifact was not produced by packageComputeArtifact.`);
|
|
5740
|
-
}
|
|
5741
|
-
function pathEscapeError(entryPath) {
|
|
5742
|
-
return /* @__PURE__ */ new Error(`compute artifact entry "${entryPath}" escapes the extraction directory — this artifact was not produced by packageComputeArtifact.`);
|
|
5743
|
-
}
|
|
5744
|
-
const REGULAR_FILE_TYPES = /* @__PURE__ */ new Set([
|
|
5745
|
-
"File",
|
|
5746
|
-
"OldFile",
|
|
5747
|
-
"ContiguousFile"
|
|
5748
|
-
]);
|
|
5749
|
-
const SYMLINK_TYPE = "SymbolicLink";
|
|
5750
|
-
/**
|
|
5751
|
-
* Extracts `tarGzPath` (a `packageComputeArtifact` tar.gz) into `destDir`,
|
|
5752
|
-
* temp-then-rename at the directory level: entries are written into a
|
|
5753
|
-
* sibling temp directory first, then that directory is renamed onto
|
|
5754
|
-
* `destDir` — a concurrent unpack of the SAME hash races benignly (last
|
|
5755
|
-
* rename wins, both write identical bytes), and a reader of `destDir` never
|
|
5756
|
-
* sees a partial tree.
|
|
5757
|
-
*/
|
|
5758
|
-
function extractComputeArtifact(tarGzPath, destDir) {
|
|
5759
|
-
const parentDir = path$1.dirname(destDir);
|
|
5760
|
-
fs$2.mkdirSync(parentDir, { recursive: true });
|
|
5761
|
-
const tmpDir = path$1.join(parentDir, `.${path$1.basename(destDir)}.${crypto$1.randomUUID()}.tmp`);
|
|
5762
|
-
fs$2.mkdirSync(tmpDir, { recursive: true });
|
|
5763
|
-
try {
|
|
5764
|
-
So({
|
|
5765
|
-
file: tarGzPath,
|
|
5766
|
-
cwd: tmpDir,
|
|
5767
|
-
sync: true,
|
|
5768
|
-
onentry: (entry) => {
|
|
5769
|
-
const resolved = path$1.resolve(tmpDir, entry.path);
|
|
5770
|
-
if (!isWithin(tmpDir, resolved)) throw pathEscapeError(entry.path);
|
|
5771
|
-
if (REGULAR_FILE_TYPES.has(entry.type)) return;
|
|
5772
|
-
if (entry.type !== SYMLINK_TYPE) throw unsupportedEntryError(entry.path, entry.type);
|
|
5773
|
-
if (entry.linkpath === void 0) throw unsupportedEntryError(entry.path, `${entry.type} without a target`);
|
|
5774
|
-
if (path$1.isAbsolute(entry.linkpath)) throw pathEscapeError(`${entry.path} -> ${entry.linkpath}`);
|
|
5775
|
-
const linkTarget = path$1.resolve(path$1.dirname(resolved), entry.linkpath);
|
|
5776
|
-
if (!isWithin(tmpDir, linkTarget)) throw pathEscapeError(`${entry.path} -> ${entry.linkpath}`);
|
|
5777
|
-
}
|
|
5778
|
-
});
|
|
5779
|
-
fs$2.rmSync(destDir, {
|
|
5780
|
-
recursive: true,
|
|
5781
|
-
force: true
|
|
5782
|
-
});
|
|
5783
|
-
fs$2.renameSync(tmpDir, destDir);
|
|
5784
|
-
} catch (error) {
|
|
5785
|
-
fs$2.rmSync(tmpDir, {
|
|
5786
|
-
recursive: true,
|
|
5787
|
-
force: true
|
|
5788
|
-
});
|
|
5789
|
-
throw error;
|
|
5790
|
-
}
|
|
5791
|
-
}
|
|
5792
|
-
/**
|
|
5793
5702
|
* The shared dev-instance store (local-dev spec § 4): the JSON files under
|
|
5794
5703
|
* `<devDir>/` every local provider reads or writes. Every write is
|
|
5795
5704
|
* temp-then-rename; reads and writes on the SAME file are additionally
|
|
@@ -5854,8 +5763,28 @@ function envStore(devDir) {
|
|
|
5854
5763
|
function secretsStore(devDir) {
|
|
5855
5764
|
return createStore(path$1.join(devDir, "secrets.json"), isStringRecord, {}, 384);
|
|
5856
5765
|
}
|
|
5766
|
+
/** Where local dev loads `@prisma/dev` from, for the Postgres emulator. */
|
|
5767
|
+
function noPrismaDevError(cause) {
|
|
5768
|
+
return /* @__PURE__ */ new Error(`local dev needs @prisma/dev for its local Postgres emulator — @prisma/composer-prisma-cloud declares it as a dependency, but it did not resolve from Composer's own installation; reinstall your dependencies. (${cause instanceof Error ? cause.message : String(cause)})`);
|
|
5769
|
+
}
|
|
5770
|
+
/**
|
|
5771
|
+
* Composer owns the local Postgres emulator's version: `@prisma/dev` is a
|
|
5772
|
+
* dependency of `@prisma/composer-prisma-cloud` and is resolved from
|
|
5773
|
+
* Composer's own package, never from the app. The app's copy (if any) is
|
|
5774
|
+
* ignored on purpose — an old `@prisma/dev` pulled in transitively (alchemy
|
|
5775
|
+
* pins `^0.20.0`, whose pglite-socket crashes on any message over 64 KiB)
|
|
5776
|
+
* would otherwise silently replace the version Composer tested against.
|
|
5777
|
+
* The daemon imports the returned path dynamically.
|
|
5778
|
+
*/
|
|
5779
|
+
function resolvePrismaDevModulePath() {
|
|
5780
|
+
try {
|
|
5781
|
+
return createRequire(import.meta.url).resolve("@prisma/dev");
|
|
5782
|
+
} catch (cause) {
|
|
5783
|
+
throw noPrismaDevError(cause);
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5857
5786
|
/**
|
|
5858
|
-
* Which emulator instance backs each node of
|
|
5787
|
+
* Which emulator instance backs each node of each app run from a directory.
|
|
5859
5788
|
* `prisma dev` records it in `<directory>/.prisma-composer/dev/`, and every
|
|
5860
5789
|
* Composer operation on an emulator instance goes through it. The emulators
|
|
5861
5790
|
* know nothing about Composer: the labels written on create are for people
|
|
@@ -5863,128 +5792,293 @@ function secretsStore(devDir) {
|
|
|
5863
5792
|
*/
|
|
5864
5793
|
const EMULATOR_INSTANCES_FILE = "emulator-instances.json";
|
|
5865
5794
|
function isNodeInstance(value) {
|
|
5866
|
-
|
|
5795
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5796
|
+
if (!("id" in value) || typeof value.id !== "string" || !("kind" in value)) return false;
|
|
5797
|
+
if (value.kind === "bucket") return true;
|
|
5798
|
+
return (value.kind === "service" || value.kind === "database") && "port" in value && typeof value.port === "number";
|
|
5799
|
+
}
|
|
5800
|
+
function isNodes(value) {
|
|
5801
|
+
return typeof value === "object" && value !== null && Object.values(value).every(isNodeInstance);
|
|
5802
|
+
}
|
|
5803
|
+
function isAppInstances(value) {
|
|
5804
|
+
return typeof value === "object" && value !== null && "nodes" in value && isNodes(value.nodes);
|
|
5867
5805
|
}
|
|
5868
5806
|
function isEmulatorInstances(value) {
|
|
5869
|
-
return typeof value === "object" && value !== null && "
|
|
5807
|
+
return typeof value === "object" && value !== null && "current" in value && typeof value.current === "string" && "apps" in value && typeof value.apps === "object" && value.apps !== null && Object.values(value.apps).every(isAppInstances);
|
|
5808
|
+
}
|
|
5809
|
+
function isEarlierServiceNode(value) {
|
|
5810
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "compute" && "id" in value && typeof value.id === "string" && "port" in value && typeof value.port === "number";
|
|
5811
|
+
}
|
|
5812
|
+
function isSingleAppRecord(value) {
|
|
5813
|
+
return typeof value === "object" && value !== null && "app" in value && typeof value.app === "string" && "nodes" in value && typeof value.nodes === "object" && value.nodes !== null && Object.values(value.nodes).every(isEarlierServiceNode);
|
|
5814
|
+
}
|
|
5815
|
+
function isStoredRecord(value) {
|
|
5816
|
+
return isEmulatorInstances(value) || isSingleAppRecord(value);
|
|
5870
5817
|
}
|
|
5818
|
+
/** A single-app record reads as a record holding that one app; the next write rewrites it. */
|
|
5819
|
+
function normalized(stored) {
|
|
5820
|
+
if (stored === void 0 || isEmulatorInstances(stored)) return stored;
|
|
5821
|
+
const nodes = Object.fromEntries(Object.entries(stored.nodes).map(([address, { id, port }]) => [address, {
|
|
5822
|
+
kind: "service",
|
|
5823
|
+
id,
|
|
5824
|
+
port
|
|
5825
|
+
}]));
|
|
5826
|
+
return {
|
|
5827
|
+
current: stored.app,
|
|
5828
|
+
apps: { [stored.app]: { nodes } }
|
|
5829
|
+
};
|
|
5830
|
+
}
|
|
5831
|
+
/**
|
|
5832
|
+
* A database's or bucket's key in the record: its resource `name`, which the
|
|
5833
|
+
* lowering sets from the same helper the emulators hook keys it by. A
|
|
5834
|
+
* resource without a name cannot be matched to the instance the hook
|
|
5835
|
+
* recorded, so it is refused rather than recorded under another key.
|
|
5836
|
+
*/
|
|
5837
|
+
function recordKeyOf(kind, resourceId, name) {
|
|
5838
|
+
if (name === void 0) throw new Error(`local dev: the ${kind} resource "${resourceId}" has no name, so its emulator instance cannot be found in the directory's record.`);
|
|
5839
|
+
return name;
|
|
5840
|
+
}
|
|
5841
|
+
/** The project directory that holds `devDir` (`<directory>/.prisma-composer/dev`). */
|
|
5871
5842
|
function directoryOf(devDir) {
|
|
5872
|
-
const
|
|
5873
|
-
return
|
|
5843
|
+
const normalizedDir = path$1.normalize(devDir);
|
|
5844
|
+
return normalizedDir.endsWith(DEV_DIR) ? normalizedDir.slice(0, -DEV_DIR.length - 1) : normalizedDir;
|
|
5874
5845
|
}
|
|
5875
5846
|
function store(devDir) {
|
|
5876
|
-
return createStore(path$1.join(devDir, EMULATOR_INSTANCES_FILE),
|
|
5847
|
+
return createStore(path$1.join(devDir, EMULATOR_INSTANCES_FILE), isStoredRecord, void 0);
|
|
5848
|
+
}
|
|
5849
|
+
async function readRecord(devDir) {
|
|
5850
|
+
return normalized(await store(devDir).read());
|
|
5851
|
+
}
|
|
5852
|
+
async function updateRecord(devDir, mutate) {
|
|
5853
|
+
await store(devDir).update((stored) => mutate(normalized(stored)));
|
|
5877
5854
|
}
|
|
5878
|
-
/**
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5855
|
+
/** The record `prisma dev` left in `directory`; undefined when it has not run there. */
|
|
5856
|
+
function readEmulatorInstances(directory) {
|
|
5857
|
+
return readRecord(path$1.join(directory, DEV_DIR));
|
|
5858
|
+
}
|
|
5859
|
+
async function appNodes$1(scope) {
|
|
5860
|
+
return (await readRecord(scope.devDir))?.apps[scope.app]?.nodes ?? {};
|
|
5882
5861
|
}
|
|
5883
5862
|
async function updateNodes(scope, mutate) {
|
|
5884
|
-
await
|
|
5885
|
-
const nodes = { ...
|
|
5863
|
+
await updateRecord(scope.devDir, (record) => {
|
|
5864
|
+
const nodes = { ...record?.apps[scope.app]?.nodes ?? {} };
|
|
5886
5865
|
mutate(nodes);
|
|
5887
5866
|
return {
|
|
5888
|
-
|
|
5889
|
-
|
|
5867
|
+
current: record?.current ?? scope.app,
|
|
5868
|
+
apps: {
|
|
5869
|
+
...record?.apps,
|
|
5870
|
+
[scope.app]: { nodes }
|
|
5871
|
+
}
|
|
5890
5872
|
};
|
|
5891
5873
|
});
|
|
5892
5874
|
}
|
|
5875
|
+
function labelsFor(scope, address) {
|
|
5876
|
+
return {
|
|
5877
|
+
"composer.directory": directoryOf(scope.devDir),
|
|
5878
|
+
"composer.app": scope.app,
|
|
5879
|
+
"composer.address": address
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
function idsOf(nodes, kind) {
|
|
5883
|
+
return Object.values(nodes).flatMap((node) => node.kind === kind ? [node.id] : []);
|
|
5884
|
+
}
|
|
5893
5885
|
/**
|
|
5894
|
-
*
|
|
5895
|
-
*
|
|
5896
|
-
*
|
|
5886
|
+
* Stops the service processes and database servers of `nodes`, keeping
|
|
5887
|
+
* their data and ports, and returns the nodes it stopped. A kind whose
|
|
5888
|
+
* emulator is not running has nothing running to stop.
|
|
5897
5889
|
*/
|
|
5898
|
-
async function
|
|
5899
|
-
const
|
|
5900
|
-
|
|
5901
|
-
const
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5890
|
+
async function stopNodes(nodes) {
|
|
5891
|
+
const services = idsOf(nodes, "service");
|
|
5892
|
+
const databases = idsOf(nodes, "database");
|
|
5893
|
+
const results = await Promise.allSettled([services.length > 0 && whenRunning("service", () => computeClient().stopInstances(services)), databases.length > 0 && whenRunning("database", () => postgresClient().stopInstances(databases))]);
|
|
5894
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
5895
|
+
if (failure) throw failure.reason;
|
|
5896
|
+
const stoppedKinds = /* @__PURE__ */ new Set();
|
|
5897
|
+
if (results[0].status === "fulfilled" && results[0].value === true) stoppedKinds.add("service");
|
|
5898
|
+
if (results[1].status === "fulfilled" && results[1].value === true) stoppedKinds.add("database");
|
|
5899
|
+
return Object.entries(nodes).flatMap(([address, node]) => node.kind !== "bucket" && stoppedKinds.has(node.kind) ? [{
|
|
5900
|
+
address,
|
|
5901
|
+
kind: node.kind
|
|
5902
|
+
}] : []);
|
|
5903
|
+
}
|
|
5904
|
+
/** Runs `action` when the emulator for `kind` is running; returns whether it ran. */
|
|
5905
|
+
async function whenRunning(kind, action) {
|
|
5906
|
+
if (!await isEmulatorRunning(kind)) return false;
|
|
5907
|
+
await action();
|
|
5908
|
+
return true;
|
|
5909
|
+
}
|
|
5910
|
+
/**
|
|
5911
|
+
* Makes `scope.app` the directory's current app. When another app ran here
|
|
5912
|
+
* last, its service processes and database servers are stopped and its
|
|
5913
|
+
* instances stay recorded, so running it again resumes them with their data
|
|
5914
|
+
* and ports. Returns the previous app's name when it changed.
|
|
5915
|
+
*/
|
|
5916
|
+
async function selectApp(scope) {
|
|
5917
|
+
const record = await readRecord(scope.devDir);
|
|
5918
|
+
const previous = record?.current;
|
|
5919
|
+
if (previous !== void 0 && previous !== scope.app) await stopNodes(record?.apps[previous]?.nodes ?? {});
|
|
5920
|
+
await updateRecord(scope.devDir, (latest) => ({
|
|
5921
|
+
current: scope.app,
|
|
5922
|
+
apps: {
|
|
5923
|
+
...latest?.apps,
|
|
5924
|
+
[scope.app]: latest?.apps[scope.app] ?? { nodes: {} }
|
|
5925
|
+
}
|
|
5906
5926
|
}));
|
|
5907
|
-
return
|
|
5927
|
+
return previous !== void 0 && previous !== scope.app ? previous : void 0;
|
|
5908
5928
|
}
|
|
5909
|
-
/** Each
|
|
5910
|
-
async function
|
|
5911
|
-
return Object.entries(await appNodes(scope)).
|
|
5929
|
+
/** Each node of the app of `kind`, with the instance that backs it. */
|
|
5930
|
+
async function nodeInstancesOf(scope, kind) {
|
|
5931
|
+
return Object.entries(await appNodes$1(scope)).flatMap(([address, instance]) => isOfKind(instance, kind) ? [{
|
|
5912
5932
|
address,
|
|
5913
5933
|
instance
|
|
5914
|
-
})
|
|
5934
|
+
}] : []);
|
|
5915
5935
|
}
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5936
|
+
function isOfKind(instance, kind) {
|
|
5937
|
+
return instance.kind === kind;
|
|
5938
|
+
}
|
|
5939
|
+
/** The instance of `kind` recorded for `address`; undefined when there is none. */
|
|
5940
|
+
async function recordedInstance(scope, address, kind) {
|
|
5941
|
+
const recorded = (await appNodes$1(scope))[address];
|
|
5942
|
+
return recorded !== void 0 && isOfKind(recorded, kind) ? recorded : void 0;
|
|
5919
5943
|
}
|
|
5920
5944
|
/**
|
|
5921
5945
|
* The instance that runs the service at `address`. The recorded instance is
|
|
5922
5946
|
* reused while the Compute emulator still has it. Otherwise a new one is
|
|
5923
5947
|
* created, requesting the recorded port, and recorded.
|
|
5924
5948
|
*/
|
|
5925
|
-
async function
|
|
5949
|
+
async function ensureServiceInstance(scope, address) {
|
|
5926
5950
|
const client = computeClient();
|
|
5927
|
-
const recorded = await
|
|
5951
|
+
const recorded = await recordedInstance(scope, address, "service");
|
|
5928
5952
|
if (recorded && await client.getInstance(recorded.id)) return { instance: recorded };
|
|
5929
5953
|
const created = await client.createInstance({
|
|
5930
|
-
labels:
|
|
5931
|
-
"composer.directory": directoryOf(scope.devDir),
|
|
5932
|
-
"composer.app": scope.app,
|
|
5933
|
-
"composer.address": address
|
|
5934
|
-
},
|
|
5954
|
+
labels: labelsFor(scope, address),
|
|
5935
5955
|
...recorded ? { port: recorded.port } : {}
|
|
5936
5956
|
});
|
|
5937
5957
|
const instance = {
|
|
5938
|
-
kind: "
|
|
5958
|
+
kind: "service",
|
|
5939
5959
|
id: created.id,
|
|
5940
5960
|
port: created.port
|
|
5941
5961
|
};
|
|
5942
5962
|
await updateNodes(scope, (nodes) => {
|
|
5943
5963
|
nodes[address] = instance;
|
|
5944
5964
|
});
|
|
5945
|
-
|
|
5965
|
+
return {
|
|
5946
5966
|
instance,
|
|
5947
|
-
|
|
5948
|
-
address,
|
|
5949
|
-
previous: recorded.port,
|
|
5950
|
-
given: created.port
|
|
5951
|
-
}
|
|
5967
|
+
...portChangeOf("service", address, recorded?.port, created.port)
|
|
5952
5968
|
};
|
|
5953
|
-
return { instance };
|
|
5954
5969
|
}
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5970
|
+
function portChangeOf(kind, address, previous, given) {
|
|
5971
|
+
return previous !== void 0 && previous !== given ? { portChange: {
|
|
5972
|
+
kind,
|
|
5973
|
+
address,
|
|
5974
|
+
previous,
|
|
5975
|
+
given
|
|
5976
|
+
} } : {};
|
|
5977
|
+
}
|
|
5978
|
+
/**
|
|
5979
|
+
* The running database for `address`. A recorded database the Postgres
|
|
5980
|
+
* emulator still has is started if it is stopped. Otherwise a new one is
|
|
5981
|
+
* created, requesting the recorded port, and recorded; it starts empty.
|
|
5982
|
+
*/
|
|
5983
|
+
async function ensureDatabaseInstance(scope, address, prismaDevModulePath) {
|
|
5984
|
+
const client = postgresClient();
|
|
5985
|
+
const recorded = await recordedInstance(scope, address, "database");
|
|
5986
|
+
const existing = recorded ? await client.getInstance(recorded.id) : void 0;
|
|
5987
|
+
if (existing?.status === "running") return { instance: existing };
|
|
5988
|
+
if (existing) {
|
|
5989
|
+
await client.startInstances([existing.id], prismaDevModulePath);
|
|
5990
|
+
return { instance: await client.getInstance(existing.id) ?? existing };
|
|
5991
|
+
}
|
|
5992
|
+
const created = await client.createInstance({
|
|
5993
|
+
labels: labelsFor(scope, address),
|
|
5994
|
+
...recorded ? { port: recorded.port } : {},
|
|
5995
|
+
prismaDevModulePath
|
|
5996
|
+
});
|
|
5997
|
+
await updateNodes(scope, (nodes) => {
|
|
5998
|
+
nodes[address] = {
|
|
5999
|
+
kind: "database",
|
|
6000
|
+
id: created.id,
|
|
6001
|
+
port: created.port
|
|
6002
|
+
};
|
|
6003
|
+
});
|
|
6004
|
+
return {
|
|
6005
|
+
instance: created,
|
|
6006
|
+
...portChangeOf("database", address, recorded?.port, created.port)
|
|
6007
|
+
};
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* The bucket for `address`. The recorded bucket is reused while the bucket
|
|
6011
|
+
* emulator still has it. Otherwise a new one is created over `dir` and
|
|
6012
|
+
* recorded; the objects already in `dir` are its objects.
|
|
6013
|
+
*/
|
|
6014
|
+
async function ensureBucketInstance(scope, address, dir) {
|
|
6015
|
+
const client = bucketsClient();
|
|
6016
|
+
const recorded = await recordedInstance(scope, address, "bucket");
|
|
6017
|
+
const existing = recorded ? await client.getInstance(recorded.id) : void 0;
|
|
6018
|
+
if (existing) return existing;
|
|
6019
|
+
const created = await client.createInstance({
|
|
6020
|
+
labels: labelsFor(scope, address),
|
|
6021
|
+
dir
|
|
6022
|
+
});
|
|
6023
|
+
await updateNodes(scope, (nodes) => {
|
|
6024
|
+
nodes[address] = {
|
|
6025
|
+
kind: "bucket",
|
|
6026
|
+
id: created.id
|
|
6027
|
+
};
|
|
6028
|
+
});
|
|
6029
|
+
return created;
|
|
6030
|
+
}
|
|
6031
|
+
/** The Compute emulator's view of the instance recorded for `address`; undefined when either is missing. */
|
|
6032
|
+
async function liveServiceInstance(scope, address) {
|
|
6033
|
+
const recorded = await recordedInstance(scope, address, "service");
|
|
5958
6034
|
return recorded ? computeClient().getInstance(recorded.id) : void 0;
|
|
5959
6035
|
}
|
|
6036
|
+
/** The id recorded for `address`; undefined when there is none. */
|
|
6037
|
+
async function recordedInstanceId(scope, address) {
|
|
6038
|
+
return (await appNodes$1(scope))[address]?.id;
|
|
6039
|
+
}
|
|
6040
|
+
async function deleteInstance(instance) {
|
|
6041
|
+
if (instance.kind === "service") await computeClient().deleteInstance(instance.id);
|
|
6042
|
+
else if (instance.kind === "database") await postgresClient().deleteInstance(instance.id, resolvePrismaDevModulePath());
|
|
6043
|
+
else await bucketsClient().deleteInstance(instance.id);
|
|
6044
|
+
}
|
|
5960
6045
|
/** Deletes the instance recorded for `address` and forgets it. */
|
|
5961
|
-
async function
|
|
5962
|
-
const recorded = await
|
|
6046
|
+
async function deleteNodeInstance(scope, address) {
|
|
6047
|
+
const recorded = (await appNodes$1(scope))[address];
|
|
5963
6048
|
if (!recorded) return;
|
|
5964
|
-
await
|
|
6049
|
+
await deleteInstance(recorded);
|
|
5965
6050
|
await updateNodes(scope, (nodes) => {
|
|
5966
6051
|
delete nodes[address];
|
|
5967
6052
|
});
|
|
5968
6053
|
}
|
|
6054
|
+
/** The kinds of instance recorded for the app, so a caller can start their emulators first. */
|
|
6055
|
+
async function recordedKinds(scope) {
|
|
6056
|
+
return new Set(Object.values(await appNodes$1(scope)).map((node) => node.kind));
|
|
6057
|
+
}
|
|
5969
6058
|
/**
|
|
5970
|
-
* `--fresh`: deletes
|
|
5971
|
-
*
|
|
5972
|
-
*
|
|
6059
|
+
* `--fresh`: deletes the app's recorded instances of every kind and its
|
|
6060
|
+
* entry. Other apps' entries are left alone. A bucket's objects live in the
|
|
6061
|
+
* dev directory, which `--fresh` removes separately.
|
|
5973
6062
|
*/
|
|
5974
|
-
async function
|
|
5975
|
-
const
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
6063
|
+
async function deleteAppInstances(scope) {
|
|
6064
|
+
for (const instance of Object.values(await appNodes$1(scope))) await deleteInstance(instance);
|
|
6065
|
+
await updateRecord(scope.devDir, (record) => {
|
|
6066
|
+
const apps = { ...record?.apps };
|
|
6067
|
+
delete apps[scope.app];
|
|
6068
|
+
return {
|
|
6069
|
+
current: record?.current ?? scope.app,
|
|
6070
|
+
apps
|
|
6071
|
+
};
|
|
6072
|
+
});
|
|
6073
|
+
}
|
|
6074
|
+
/** When `prisma dev` ends: stops the app's service processes and database servers, keeping their data and ports. */
|
|
6075
|
+
async function stopAppInstances(scope) {
|
|
6076
|
+
return stopNodes(await appNodes$1(scope));
|
|
5983
6077
|
}
|
|
5984
6078
|
/**
|
|
5985
|
-
* The
|
|
5986
|
-
*
|
|
5987
|
-
*
|
|
6079
|
+
* The emulators' failure messages name instances by id; `prisma dev` names
|
|
6080
|
+
* services by address. Rewrites a plain `Error`'s message and leaves any
|
|
6081
|
+
* other error, such as a structured one, untouched.
|
|
5988
6082
|
*/
|
|
5989
6083
|
function withServiceAddresses(error, nodes) {
|
|
5990
6084
|
if (!(error instanceof Error) || error.constructor !== Error) return error;
|
|
@@ -5993,6 +6087,204 @@ function withServiceAddresses(error, nodes) {
|
|
|
5993
6087
|
return message === error.message ? error : new Error(message, { cause: error });
|
|
5994
6088
|
}
|
|
5995
6089
|
/**
|
|
6090
|
+
* Shared helpers for the upstream-attribute records every local provider
|
|
6091
|
+
* emits — one implementation, so the compute and postgres families cannot
|
|
6092
|
+
* drift on the `'local'` project fallback or the timestamp.
|
|
6093
|
+
*/
|
|
6094
|
+
/** The fixed `createdAt`/`updatedAt` local providers stamp: local dev has no meaningful creation time. */
|
|
6095
|
+
const DEV_TIMESTAMP = "1970-01-01T00:00:00.000Z";
|
|
6096
|
+
/** Reads a project id from upstream's `project` input: a plain string or a resolved `Prisma.Project` attributes record. */
|
|
6097
|
+
function projectIdOfInput(value) {
|
|
6098
|
+
if (typeof value === "string") return value;
|
|
6099
|
+
if (Predicate.isObject(value) && typeof value["projectId"] === "string") return value["projectId"];
|
|
6100
|
+
return "local";
|
|
6101
|
+
}
|
|
6102
|
+
/**
|
|
6103
|
+
* Local bucket-cluster providers (local-dev spec § 4): both `Bucket` and
|
|
6104
|
+
* `BucketAccessKey` are clients of the machine-global bucket emulator —
|
|
6105
|
+
* already up by the time these run, since the extension's
|
|
6106
|
+
* `localTarget.emulators` hook ensures it before converge.
|
|
6107
|
+
*/
|
|
6108
|
+
/** Reads a bucket id from upstream's `bucket` input: a plain string or a resolved `Prisma.Bucket` attributes record. */
|
|
6109
|
+
function bucketIdOfInput(value) {
|
|
6110
|
+
if (typeof value === "string") return value;
|
|
6111
|
+
if (Predicate.isObject(value) && typeof value["bucketId"] === "string") return value["bucketId"];
|
|
6112
|
+
return "local";
|
|
6113
|
+
}
|
|
6114
|
+
const BUCKETS_DIR = "buckets";
|
|
6115
|
+
/** Where a bucket's objects live: in the project's dev directory, under its app, so `--fresh` removes them with the app. */
|
|
6116
|
+
function bucketDataDir(scope, name) {
|
|
6117
|
+
return path$1.join(scope.devDir, BUCKETS_DIR, scope.app, name);
|
|
6118
|
+
}
|
|
6119
|
+
function appScopeOf$2(input) {
|
|
6120
|
+
return {
|
|
6121
|
+
devDir: input.devDir,
|
|
6122
|
+
app: appNameOf(input.container)
|
|
6123
|
+
};
|
|
6124
|
+
}
|
|
6125
|
+
/**
|
|
6126
|
+
* `Bucket` → the bucket instance the directory's record names for the
|
|
6127
|
+
* resource, created when there is none, over an in-project data root. Its
|
|
6128
|
+
* `bucketId` is the instance id, which is also its S3 bucket name. A Bucket
|
|
6129
|
+
* whose recorded instance changed is updated, so its access key is
|
|
6130
|
+
* registered on the new instance.
|
|
6131
|
+
*/
|
|
6132
|
+
function LocalBucketProvider(input) {
|
|
6133
|
+
return Provider.effect(Bucket, Effect.succeed({
|
|
6134
|
+
list: () => Effect.succeed([]),
|
|
6135
|
+
reconcile: ({ news, id }) => Effect.tryPromise({
|
|
6136
|
+
try: async () => {
|
|
6137
|
+
const name = recordKeyOf("bucket", id, news.name);
|
|
6138
|
+
return {
|
|
6139
|
+
bucketId: (await ensureBucketInstance(appScopeOf$2(input), name, bucketDataDir(appScopeOf$2(input), name))).id,
|
|
6140
|
+
name,
|
|
6141
|
+
projectId: projectIdOfInput(news.project),
|
|
6142
|
+
createdAt: DEV_TIMESTAMP,
|
|
6143
|
+
logicalId: null
|
|
6144
|
+
};
|
|
6145
|
+
},
|
|
6146
|
+
catch: (cause) => cause
|
|
6147
|
+
}),
|
|
6148
|
+
diff: ({ output }) => Effect.tryPromise({
|
|
6149
|
+
try: async () => {
|
|
6150
|
+
if (output === void 0) return void 0;
|
|
6151
|
+
return await recordedInstanceId(appScopeOf$2(input), output.name) === output.bucketId ? void 0 : { action: "update" };
|
|
6152
|
+
},
|
|
6153
|
+
catch: (cause) => cause
|
|
6154
|
+
}),
|
|
6155
|
+
delete: () => Effect.void,
|
|
6156
|
+
read: ({ output }) => Effect.succeed(output)
|
|
6157
|
+
}));
|
|
6158
|
+
}
|
|
6159
|
+
/**
|
|
6160
|
+
* `BucketAccessKey` → mint-once-stable (the same lifecycle as `ServiceKey`/
|
|
6161
|
+
* `S3Credentials`: reuse `output`'s pair when present, mint only on first
|
|
6162
|
+
* create) — but ALWAYS re-registers the (prior or fresh) pair with the
|
|
6163
|
+
* emulator, so a bucket emulator whose own state was wiped self-heals on the
|
|
6164
|
+
* next converge.
|
|
6165
|
+
*/
|
|
6166
|
+
function LocalBucketAccessKeyProvider(_input) {
|
|
6167
|
+
return Provider.effect(BucketAccessKey, Effect.succeed({
|
|
6168
|
+
list: () => Effect.succeed([]),
|
|
6169
|
+
reconcile: ({ news, output, id }) => Effect.tryPromise({
|
|
6170
|
+
try: async () => {
|
|
6171
|
+
const pair = output !== void 0 ? {
|
|
6172
|
+
accessKeyId: output.accessKeyId,
|
|
6173
|
+
secretAccessKey: Redacted.value(output.secretAccessKey)
|
|
6174
|
+
} : mintKeyPair();
|
|
6175
|
+
const client = bucketsClient();
|
|
6176
|
+
const bucketId = bucketIdOfInput(news.bucket);
|
|
6177
|
+
await client.putCredentials(bucketId, pair.accessKeyId, pair.secretAccessKey);
|
|
6178
|
+
return {
|
|
6179
|
+
bucketAccessKeyId: news.name ?? id,
|
|
6180
|
+
bucketId,
|
|
6181
|
+
accessKeyId: pair.accessKeyId,
|
|
6182
|
+
secretAccessKey: Redacted.make(pair.secretAccessKey),
|
|
6183
|
+
endpoint: client.baseUrl,
|
|
6184
|
+
bucketName: bucketId
|
|
6185
|
+
};
|
|
6186
|
+
},
|
|
6187
|
+
catch: (cause) => cause
|
|
6188
|
+
}),
|
|
6189
|
+
delete: () => Effect.void,
|
|
6190
|
+
read: ({ output }) => Effect.succeed(output)
|
|
6191
|
+
}));
|
|
6192
|
+
}
|
|
6193
|
+
/**
|
|
6194
|
+
* The emulators hook's half of the directory's record: before Alchemy runs,
|
|
6195
|
+
* every node of the app gets its emulator instance, one at a time in the
|
|
6196
|
+
* order given.
|
|
6197
|
+
*/
|
|
6198
|
+
/**
|
|
6199
|
+
* Ensures each node's instance, in the order given: a recorded instance is
|
|
6200
|
+
* reused (a stopped database is started), a missing one is created again on
|
|
6201
|
+
* its remembered port, and a new node gets a new instance. A new instance
|
|
6202
|
+
* gets the smallest free port, so on a fresh start the ports follow this
|
|
6203
|
+
* order. Returns the nodes whose re-created instance could not get its
|
|
6204
|
+
* remembered port back.
|
|
6205
|
+
*/
|
|
6206
|
+
async function ensureAppInstances(scope, nodes) {
|
|
6207
|
+
const changes = [];
|
|
6208
|
+
for (const { address, kind } of nodes) try {
|
|
6209
|
+
const { portChange } = await ensureNodeInstance(scope, address, kind);
|
|
6210
|
+
if (portChange) changes.push(portChange);
|
|
6211
|
+
} catch (cause) {
|
|
6212
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
6213
|
+
throw new Error(`ensuring the local instance for ${kind} "${address}" failed: ${reason}`, { cause });
|
|
6214
|
+
}
|
|
6215
|
+
return changes;
|
|
6216
|
+
}
|
|
6217
|
+
async function ensureNodeInstance(scope, address, kind) {
|
|
6218
|
+
if (kind === "service") return ensureServiceInstance(scope, address);
|
|
6219
|
+
if (kind === "database") return ensureDatabaseInstance(scope, address, resolvePrismaDevModulePath());
|
|
6220
|
+
await ensureBucketInstance(scope, address, bucketDataDir(scope, address));
|
|
6221
|
+
return {};
|
|
6222
|
+
}
|
|
6223
|
+
/**
|
|
6224
|
+
* Extracts the tar.gz `@internal/lowering`'s `packageComputeArtifact` writes
|
|
6225
|
+
* (local-dev spec § 4) — emulation-only code, so it lives here rather than
|
|
6226
|
+
* beside the writer (production source stays physically separated from
|
|
6227
|
+
* emulation). Reading tar is commodity even though the writer is a
|
|
6228
|
+
* deterministic subset we own (fixed mtimes, sorted entries): the maintained
|
|
6229
|
+
* `tar` package (dependency razor) does the parsing; this module keeps only
|
|
6230
|
+
* the pinned entry filtering (regular files plus safe relative symlinks,
|
|
6231
|
+
* reject devices/path escapes) and the directory-level temp-then-rename.
|
|
6232
|
+
*/
|
|
6233
|
+
function unsupportedEntryError(entryPath, type) {
|
|
6234
|
+
return /* @__PURE__ */ new Error(`compute artifact entry "${entryPath}" has type "${type}" — only regular files and safe symlinks are supported; this artifact was not produced by packageComputeArtifact.`);
|
|
6235
|
+
}
|
|
6236
|
+
function pathEscapeError(entryPath) {
|
|
6237
|
+
return /* @__PURE__ */ new Error(`compute artifact entry "${entryPath}" escapes the extraction directory — this artifact was not produced by packageComputeArtifact.`);
|
|
6238
|
+
}
|
|
6239
|
+
const REGULAR_FILE_TYPES = /* @__PURE__ */ new Set([
|
|
6240
|
+
"File",
|
|
6241
|
+
"OldFile",
|
|
6242
|
+
"ContiguousFile"
|
|
6243
|
+
]);
|
|
6244
|
+
const SYMLINK_TYPE = "SymbolicLink";
|
|
6245
|
+
/**
|
|
6246
|
+
* Extracts `tarGzPath` (a `packageComputeArtifact` tar.gz) into `destDir`,
|
|
6247
|
+
* temp-then-rename at the directory level: entries are written into a
|
|
6248
|
+
* sibling temp directory first, then that directory is renamed onto
|
|
6249
|
+
* `destDir` — a concurrent unpack of the SAME hash races benignly (last
|
|
6250
|
+
* rename wins, both write identical bytes), and a reader of `destDir` never
|
|
6251
|
+
* sees a partial tree.
|
|
6252
|
+
*/
|
|
6253
|
+
function extractComputeArtifact(tarGzPath, destDir) {
|
|
6254
|
+
const parentDir = path$1.dirname(destDir);
|
|
6255
|
+
fs$2.mkdirSync(parentDir, { recursive: true });
|
|
6256
|
+
const tmpDir = path$1.join(parentDir, `.${path$1.basename(destDir)}.${crypto$1.randomUUID()}.tmp`);
|
|
6257
|
+
fs$2.mkdirSync(tmpDir, { recursive: true });
|
|
6258
|
+
try {
|
|
6259
|
+
So({
|
|
6260
|
+
file: tarGzPath,
|
|
6261
|
+
cwd: tmpDir,
|
|
6262
|
+
sync: true,
|
|
6263
|
+
onentry: (entry) => {
|
|
6264
|
+
const resolved = path$1.resolve(tmpDir, entry.path);
|
|
6265
|
+
if (!isWithin(tmpDir, resolved)) throw pathEscapeError(entry.path);
|
|
6266
|
+
if (REGULAR_FILE_TYPES.has(entry.type)) return;
|
|
6267
|
+
if (entry.type !== SYMLINK_TYPE) throw unsupportedEntryError(entry.path, entry.type);
|
|
6268
|
+
if (entry.linkpath === void 0) throw unsupportedEntryError(entry.path, `${entry.type} without a target`);
|
|
6269
|
+
if (path$1.isAbsolute(entry.linkpath)) throw pathEscapeError(`${entry.path} -> ${entry.linkpath}`);
|
|
6270
|
+
const linkTarget = path$1.resolve(path$1.dirname(resolved), entry.linkpath);
|
|
6271
|
+
if (!isWithin(tmpDir, linkTarget)) throw pathEscapeError(`${entry.path} -> ${entry.linkpath}`);
|
|
6272
|
+
}
|
|
6273
|
+
});
|
|
6274
|
+
fs$2.rmSync(destDir, {
|
|
6275
|
+
recursive: true,
|
|
6276
|
+
force: true
|
|
6277
|
+
});
|
|
6278
|
+
fs$2.renameSync(tmpDir, destDir);
|
|
6279
|
+
} catch (error) {
|
|
6280
|
+
fs$2.rmSync(tmpDir, {
|
|
6281
|
+
recursive: true,
|
|
6282
|
+
force: true
|
|
6283
|
+
});
|
|
6284
|
+
throw error;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
/**
|
|
5996
6288
|
* Local compute-cluster providers: upstream alchemy's `Prisma.App` and
|
|
5997
6289
|
* `Prisma.Deployment` become clients of the machine-scoped Compute emulator;
|
|
5998
6290
|
* `Prisma.EnvironmentVariable` becomes a row in the dev env store;
|
|
@@ -6106,7 +6398,7 @@ async function materializeEnv(devDir, address, port) {
|
|
|
6106
6398
|
if (process.env["HOME"] !== void 0) env["HOME"] = process.env["HOME"];
|
|
6107
6399
|
return env;
|
|
6108
6400
|
}
|
|
6109
|
-
function appScopeOf(input) {
|
|
6401
|
+
function appScopeOf$1(input) {
|
|
6110
6402
|
return {
|
|
6111
6403
|
devDir: input.devDir,
|
|
6112
6404
|
app: appNameOf(input.container)
|
|
@@ -6128,7 +6420,7 @@ function LocalAppProvider(input) {
|
|
|
6128
6420
|
reconcile: ({ id, news }) => Effect.tryPromise({
|
|
6129
6421
|
try: async () => {
|
|
6130
6422
|
const name = news.displayName ?? id;
|
|
6131
|
-
const { instance } = await
|
|
6423
|
+
const { instance } = await ensureServiceInstance(appScopeOf$1(input), name);
|
|
6132
6424
|
const url = localUrl(instance.port);
|
|
6133
6425
|
return {
|
|
6134
6426
|
appId: name,
|
|
@@ -6147,41 +6439,18 @@ function LocalAppProvider(input) {
|
|
|
6147
6439
|
diff: ({ output }) => Effect.tryPromise({
|
|
6148
6440
|
try: async () => {
|
|
6149
6441
|
if (output === void 0) return void 0;
|
|
6150
|
-
if ((await
|
|
6442
|
+
if ((await liveServiceInstance(appScopeOf$1(input), output.name))?.url === output.appEndpointDomain) return void 0;
|
|
6151
6443
|
return { action: "update" };
|
|
6152
6444
|
},
|
|
6153
6445
|
catch: (cause) => cause
|
|
6154
6446
|
}),
|
|
6155
6447
|
delete: ({ output }) => Effect.tryPromise({
|
|
6156
|
-
try: () =>
|
|
6448
|
+
try: () => deleteNodeInstance(appScopeOf$1(input), output.name),
|
|
6157
6449
|
catch: (cause) => cause
|
|
6158
6450
|
}),
|
|
6159
6451
|
read: ({ output }) => Effect.succeed(output)
|
|
6160
6452
|
}));
|
|
6161
6453
|
}
|
|
6162
|
-
/**
|
|
6163
|
-
* Ensures each service's emulator instance one at a time, in the order given,
|
|
6164
|
-
* before Alchemy applies the `App` resources concurrently. A new instance gets
|
|
6165
|
-
* the smallest free port, so on a fresh start the ports follow this order; a
|
|
6166
|
-
* service that already has an instance keeps its port. `serviceAppNames` are
|
|
6167
|
-
* the `App` resources' `displayName`s. Returns the services whose instance had
|
|
6168
|
-
* to be re-created and could not get its remembered port back.
|
|
6169
|
-
*/
|
|
6170
|
-
async function ensureServiceInstances(container, devDir, serviceAppNames) {
|
|
6171
|
-
const scope = {
|
|
6172
|
-
devDir,
|
|
6173
|
-
app: appNameOf(container)
|
|
6174
|
-
};
|
|
6175
|
-
const changes = [];
|
|
6176
|
-
for (const service of serviceAppNames) try {
|
|
6177
|
-
const { portChange } = await ensureComputeInstance(scope, service);
|
|
6178
|
-
if (portChange) changes.push(portChange);
|
|
6179
|
-
} catch (cause) {
|
|
6180
|
-
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
6181
|
-
throw new Error(`ensuring the local service for service "${service}" failed: ${reason}`, { cause });
|
|
6182
|
-
}
|
|
6183
|
-
return changes;
|
|
6184
|
-
}
|
|
6185
6454
|
/** `Prisma.EnvironmentVariable` → a key/value row in `<devDir>/env.json`. Upstream's value is `Redacted`; env.json holds the plain string the child process is given. */
|
|
6186
6455
|
function LocalEnvironmentVariableProvider(input) {
|
|
6187
6456
|
return Provider.effect(EnvironmentVariable, Effect.succeed({
|
|
@@ -6239,7 +6508,7 @@ function LocalDeploymentProvider(input) {
|
|
|
6239
6508
|
const artifactDir = path$1.join(input.devDir, "artifacts", artifactHash);
|
|
6240
6509
|
if (!fs$2.existsSync(artifactDir)) extractComputeArtifact(news.artifactPath, artifactDir);
|
|
6241
6510
|
const { address, listens } = readComputeManifest(artifactDir);
|
|
6242
|
-
const { instance } = await
|
|
6511
|
+
const { instance } = await ensureServiceInstance(appScopeOf$1(input), appId);
|
|
6243
6512
|
const env = await materializeEnv(input.devDir, address, instance.port);
|
|
6244
6513
|
try {
|
|
6245
6514
|
await computeClient().putDeployment(instance.id, {
|
|
@@ -6270,7 +6539,7 @@ function LocalDeploymentProvider(input) {
|
|
|
6270
6539
|
diff: ({ output }) => Effect.tryPromise({
|
|
6271
6540
|
try: async () => {
|
|
6272
6541
|
if (output === void 0) return void 0;
|
|
6273
|
-
if ((await
|
|
6542
|
+
if ((await liveServiceInstance(appScopeOf$1(input), output.appId))?.artifactHash === output.artifactHash) return void 0;
|
|
6274
6543
|
return { action: "update" };
|
|
6275
6544
|
},
|
|
6276
6545
|
catch: (cause) => cause
|
|
@@ -6304,57 +6573,106 @@ function LocalProjectProvider(_input) {
|
|
|
6304
6573
|
delete: () => Effect.void
|
|
6305
6574
|
}));
|
|
6306
6575
|
}
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6576
|
+
const CONDITION_OF = {
|
|
6577
|
+
running: "running",
|
|
6578
|
+
stopped: "stopped",
|
|
6579
|
+
backoff: "crashing",
|
|
6580
|
+
held: "crashing"
|
|
6581
|
+
};
|
|
6582
|
+
/** A node whose emulator no longer has the instance the record names. */
|
|
6583
|
+
function missing(address, kind) {
|
|
6584
|
+
return {
|
|
6585
|
+
address,
|
|
6586
|
+
kind,
|
|
6587
|
+
emulator: "running",
|
|
6588
|
+
state: "missing",
|
|
6589
|
+
condition: "missing"
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
async function locate(address, node) {
|
|
6593
|
+
const kind = node.kind;
|
|
6594
|
+
if (!await isEmulatorRunning(kind)) return {
|
|
6595
|
+
address,
|
|
6596
|
+
kind,
|
|
6597
|
+
emulator: "not-running"
|
|
6598
|
+
};
|
|
6599
|
+
if (node.kind === "service") {
|
|
6600
|
+
const instance = await computeClient().getInstance(node.id);
|
|
6601
|
+
if (instance === void 0) return missing(address, kind);
|
|
6602
|
+
return {
|
|
6603
|
+
address,
|
|
6604
|
+
kind,
|
|
6605
|
+
emulator: "running",
|
|
6606
|
+
url: instance.url,
|
|
6607
|
+
state: instance.status,
|
|
6608
|
+
condition: CONDITION_OF[instance.status],
|
|
6609
|
+
logPath: instance.logPath
|
|
6610
|
+
};
|
|
6611
|
+
}
|
|
6612
|
+
if (node.kind === "database") {
|
|
6613
|
+
const instance = await postgresClient().getInstance(node.id);
|
|
6614
|
+
if (instance === void 0) return missing(address, kind);
|
|
6615
|
+
return {
|
|
6616
|
+
address,
|
|
6617
|
+
kind,
|
|
6618
|
+
emulator: "running",
|
|
6619
|
+
url: instance.url,
|
|
6620
|
+
state: instance.status,
|
|
6621
|
+
condition: instance.status
|
|
6622
|
+
};
|
|
6623
|
+
}
|
|
6624
|
+
const client = bucketsClient();
|
|
6625
|
+
if (await client.getInstance(node.id) === void 0) return missing(address, kind);
|
|
6626
|
+
return {
|
|
6627
|
+
address,
|
|
6628
|
+
kind,
|
|
6629
|
+
emulator: "running",
|
|
6630
|
+
url: `${client.baseUrl}/${node.id}`,
|
|
6631
|
+
state: "running",
|
|
6632
|
+
condition: "running"
|
|
6633
|
+
};
|
|
6634
|
+
}
|
|
6635
|
+
/** The directory's current app; undefined when `prisma dev` never ran there. */
|
|
6636
|
+
async function currentApp(devDir) {
|
|
6637
|
+
const record = await readEmulatorInstances(directoryOf(devDir));
|
|
6638
|
+
if (record === void 0) return void 0;
|
|
6639
|
+
const app = record.current;
|
|
6640
|
+
const nodes = record.apps[app]?.nodes ?? {};
|
|
6641
|
+
return {
|
|
6642
|
+
app,
|
|
6643
|
+
addresses: Object.keys(nodes),
|
|
6644
|
+
locate: async (address) => {
|
|
6645
|
+
const node = nodes[address];
|
|
6646
|
+
return node === void 0 ? void 0 : locate(address, node);
|
|
6647
|
+
},
|
|
6648
|
+
stop: () => stopAppInstances({
|
|
6649
|
+
devDir,
|
|
6650
|
+
app
|
|
6651
|
+
})
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6318
6654
|
/** Reads a database id from upstream's `database` input: a plain string or a resolved `Prisma.Database` attributes record. */
|
|
6319
6655
|
function databaseIdOfInput(value) {
|
|
6320
6656
|
if (typeof value === "string") return value;
|
|
6321
6657
|
if (Predicate.isObject(value) && typeof value["databaseId"] === "string") return value["databaseId"];
|
|
6322
6658
|
}
|
|
6323
|
-
function noPrismaDevError(cause) {
|
|
6324
|
-
return /* @__PURE__ */ new Error(`local dev needs @prisma/dev for its local Postgres emulator — @prisma/composer-prisma-cloud declares it as a dependency, but it did not resolve from Composer's own installation; reinstall your dependencies. (${cause instanceof Error ? cause.message : String(cause)})`);
|
|
6325
|
-
}
|
|
6326
6659
|
/**
|
|
6327
|
-
*
|
|
6328
|
-
*
|
|
6329
|
-
*
|
|
6330
|
-
*
|
|
6331
|
-
*
|
|
6332
|
-
* would otherwise silently replace the version Composer tested against.
|
|
6333
|
-
* The daemon imports the returned path dynamically.
|
|
6334
|
-
*/
|
|
6335
|
-
function resolvePrismaDevModulePath() {
|
|
6336
|
-
try {
|
|
6337
|
-
return createRequire(import.meta.url).resolve("@prisma/dev");
|
|
6338
|
-
} catch (cause) {
|
|
6339
|
-
throw noPrismaDevError(cause);
|
|
6340
|
-
}
|
|
6341
|
-
}
|
|
6342
|
-
/**
|
|
6343
|
-
* `Prisma.Database` → an ensured `postgres-main` server, one per resource.
|
|
6344
|
-
* Stores the daemon's returned url as the `directConnectionString` attribute.
|
|
6660
|
+
* `Prisma.Database` → the database instance the directory's record names for
|
|
6661
|
+
* the resource, started if it is stopped and created when there is none.
|
|
6662
|
+
* Stores its url as the `directConnectionString` attribute. A Database whose
|
|
6663
|
+
* recorded instance changed (re-created after the emulator lost it) is
|
|
6664
|
+
* updated, so its Connection and dependents get the new instance's URL.
|
|
6345
6665
|
*/
|
|
6346
6666
|
function LocalDatabaseProvider(input) {
|
|
6347
6667
|
return Provider.effect(Prisma.Database, Effect.succeed({
|
|
6348
6668
|
list: () => Effect.succeed([]),
|
|
6349
6669
|
reconcile: ({ id, news }) => Effect.tryPromise({
|
|
6350
6670
|
try: async () => {
|
|
6351
|
-
const
|
|
6352
|
-
const
|
|
6353
|
-
const
|
|
6354
|
-
const { url } = await postgresClient().ensureDatabase(app, slug(name), prismaDevModulePath);
|
|
6355
|
-
const direct = Redacted.make(url);
|
|
6671
|
+
const name = recordKeyOf("database", id, news.name);
|
|
6672
|
+
const { instance } = await ensureDatabaseInstance(appScopeOf(input), name, resolvePrismaDevModulePath());
|
|
6673
|
+
const direct = Redacted.make(instance.url);
|
|
6356
6674
|
return {
|
|
6357
|
-
databaseId:
|
|
6675
|
+
databaseId: instance.id,
|
|
6358
6676
|
databaseName: name,
|
|
6359
6677
|
projectId: projectIdOfInput(news.project),
|
|
6360
6678
|
status: "ready",
|
|
@@ -6374,28 +6692,40 @@ function LocalDatabaseProvider(input) {
|
|
|
6374
6692
|
},
|
|
6375
6693
|
catch: (cause) => cause
|
|
6376
6694
|
}),
|
|
6695
|
+
diff: ({ output }) => Effect.tryPromise({
|
|
6696
|
+
try: async () => {
|
|
6697
|
+
if (output === void 0 || output.databaseName === void 0) return void 0;
|
|
6698
|
+
return await recordedInstanceId(appScopeOf(input), output.databaseName) === output.databaseId ? void 0 : { action: "update" };
|
|
6699
|
+
},
|
|
6700
|
+
catch: (cause) => cause
|
|
6701
|
+
}),
|
|
6377
6702
|
delete: () => Effect.void
|
|
6378
6703
|
}));
|
|
6379
6704
|
}
|
|
6705
|
+
function appScopeOf(input) {
|
|
6706
|
+
return {
|
|
6707
|
+
devDir: input.devDir,
|
|
6708
|
+
app: appNameOf(input.container)
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6380
6711
|
function noRecordedInstanceError(databaseId) {
|
|
6381
6712
|
return /* @__PURE__ */ new Error(`no local Postgres instance recorded for databaseId "${databaseId}" — the Database provider did not run; converge is corrupt (try --fresh).`);
|
|
6382
6713
|
}
|
|
6383
|
-
/** `Prisma.Connection` → the
|
|
6384
|
-
function LocalConnectionProvider(
|
|
6714
|
+
/** `Prisma.Connection` → the database instance the Database attributes' `databaseId` names. */
|
|
6715
|
+
function LocalConnectionProvider(_input) {
|
|
6385
6716
|
return Provider.effect(Prisma.Connection, Effect.succeed({
|
|
6386
6717
|
list: () => Effect.succeed([]),
|
|
6387
6718
|
reconcile: ({ id, news }) => Effect.tryPromise({
|
|
6388
6719
|
try: async () => {
|
|
6389
|
-
const app = appNameOf(input.container);
|
|
6390
6720
|
const databaseId = databaseIdOfInput(news.database);
|
|
6391
6721
|
if (databaseId === void 0) throw noRecordedInstanceError(String(news.database));
|
|
6392
|
-
const found =
|
|
6722
|
+
const found = await postgresClient().getInstance(databaseId);
|
|
6393
6723
|
if (found === void 0) throw noRecordedInstanceError(databaseId);
|
|
6394
6724
|
const direct = Redacted.make(found.url);
|
|
6395
6725
|
return {
|
|
6396
|
-
connectionId: found.
|
|
6726
|
+
connectionId: found.id,
|
|
6397
6727
|
connectionName: news.name ?? id,
|
|
6398
|
-
databaseId: found.
|
|
6728
|
+
databaseId: found.id,
|
|
6399
6729
|
kind: "postgres",
|
|
6400
6730
|
createdAt: DEV_TIMESTAMP,
|
|
6401
6731
|
directConnectionString: direct,
|
|
@@ -6441,10 +6771,8 @@ function resolvePackageEntry(specifier) {
|
|
|
6441
6771
|
* ADR-0041 D12). The target extension's `dev/teardown.ts` orchestrates
|
|
6442
6772
|
* (which app, which emulator clients); this module owns the actual
|
|
6443
6773
|
* filesystem operations, so the extension's own source stays free of
|
|
6444
|
-
* `node:`/`bun:` imports (its invariant 5).
|
|
6445
|
-
*
|
|
6446
|
-
* `postgresClient().deleteApp(app)` call, made directly alongside the
|
|
6447
|
-
* compute/buckets clients — no filesystem or child-process work of its own.
|
|
6774
|
+
* `node:`/`bun:` imports (its invariant 5). Emulator instances are not
|
|
6775
|
+
* removed here: the extension deletes them through the directory's record.
|
|
6448
6776
|
*/
|
|
6449
6777
|
/** `fs.rm(..., { recursive: true, force: true })` for every given path — tolerates absence. */
|
|
6450
6778
|
function removeLocalPaths(paths) {
|
|
@@ -6453,6 +6781,21 @@ function removeLocalPaths(paths) {
|
|
|
6453
6781
|
force: true
|
|
6454
6782
|
});
|
|
6455
6783
|
}
|
|
6784
|
+
/**
|
|
6785
|
+
* Removes the dev state directory's contents for `--fresh`, except what
|
|
6786
|
+
* other apps run from the directory still use: the record of their emulator
|
|
6787
|
+
* instances and their buckets' objects. This app's objects go.
|
|
6788
|
+
*/
|
|
6789
|
+
function removeAppDevState(devDir, app) {
|
|
6790
|
+
let entries;
|
|
6791
|
+
try {
|
|
6792
|
+
entries = fs$2.readdirSync(devDir);
|
|
6793
|
+
} catch {
|
|
6794
|
+
return;
|
|
6795
|
+
}
|
|
6796
|
+
const kept = /* @__PURE__ */ new Set([EMULATOR_INSTANCES_FILE, BUCKETS_DIR]);
|
|
6797
|
+
removeLocalPaths([...entries.filter((entry) => !kept.has(entry)).map((entry) => path$1.join(devDir, entry)), path$1.join(devDir, BUCKETS_DIR, app)]);
|
|
6798
|
+
}
|
|
6456
6799
|
//#endregion
|
|
6457
6800
|
//#region ../../1-prisma-cloud/1-extensions/target/dist/local-target.mjs
|
|
6458
6801
|
const RELIST_INTERVAL_MS = 2e3;
|
|
@@ -6500,7 +6843,7 @@ async function* mergedLogs(scope, signal, tail) {
|
|
|
6500
6843
|
})();
|
|
6501
6844
|
};
|
|
6502
6845
|
const relist = async () => {
|
|
6503
|
-
for (const { address, instance } of await
|
|
6846
|
+
for (const { address, instance } of await nodeInstancesOf(scope, "service")) follow(instance.id, address);
|
|
6504
6847
|
};
|
|
6505
6848
|
await relist();
|
|
6506
6849
|
const timer = setInterval(() => void relist(), RELIST_INTERVAL_MS);
|
|
@@ -6532,7 +6875,7 @@ async function devAttach(input) {
|
|
|
6532
6875
|
const client = computeClient();
|
|
6533
6876
|
return {
|
|
6534
6877
|
startServices: async () => {
|
|
6535
|
-
const nodes = await
|
|
6878
|
+
const nodes = await nodeInstancesOf(scope, "service");
|
|
6536
6879
|
try {
|
|
6537
6880
|
await client.startInstances(nodes.map(({ instance }) => instance.id));
|
|
6538
6881
|
} catch (error) {
|
|
@@ -6540,15 +6883,14 @@ async function devAttach(input) {
|
|
|
6540
6883
|
}
|
|
6541
6884
|
},
|
|
6542
6885
|
endpoints: async () => {
|
|
6543
|
-
return (await
|
|
6886
|
+
return (await nodeInstancesOf(scope, "service")).map(({ address, instance }) => ({
|
|
6544
6887
|
address,
|
|
6545
6888
|
url: `http://localhost:${String(instance.port)}`
|
|
6546
6889
|
}));
|
|
6547
6890
|
},
|
|
6548
6891
|
logs: (signal, opts) => mergedLogs(scope, signal, opts?.tail ?? 0),
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
await client.stopInstances(nodes.map(({ instance }) => instance.id));
|
|
6892
|
+
stopApp: async () => {
|
|
6893
|
+
await stopAppInstances(scope);
|
|
6552
6894
|
}
|
|
6553
6895
|
};
|
|
6554
6896
|
}
|
|
@@ -6571,34 +6913,54 @@ function devContainerDescriptor() {
|
|
|
6571
6913
|
function daemonEntry(name) {
|
|
6572
6914
|
return resolvePackageEntry(`@prisma/composer-prisma-cloud/local-target/${name}-main`);
|
|
6573
6915
|
}
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6916
|
+
const DATABASE_TYPES = /* @__PURE__ */ new Set(["raw-postgres", "postgres"]);
|
|
6917
|
+
const BUCKET_TYPE = "s3";
|
|
6918
|
+
/**
|
|
6919
|
+
* This extension's nodes that run on an emulator, in `graph.nodes` order —
|
|
6920
|
+
* dependencies first, ties in declaration order — keyed by the name their
|
|
6921
|
+
* resource is given: a service by its `App` name, a database and a bucket by
|
|
6922
|
+
* their resource name — the keys the local providers look them up by.
|
|
6923
|
+
*/
|
|
6924
|
+
function appNodes(input) {
|
|
6925
|
+
return input.graph.nodes.flatMap((n) => {
|
|
6926
|
+
if (n.node.kind === "service" && n.node.extension === "@prisma/composer-prisma-cloud") return [{
|
|
6927
|
+
address: serviceAppName(n.id),
|
|
6928
|
+
kind: "service"
|
|
6929
|
+
}];
|
|
6930
|
+
if (n.node.kind === "resource" && DATABASE_TYPES.has(n.node.type)) return [{
|
|
6931
|
+
address: resourceName(n.id),
|
|
6932
|
+
kind: "database"
|
|
6933
|
+
}];
|
|
6934
|
+
if (n.node.kind === "resource" && n.node.type === BUCKET_TYPE) return [{
|
|
6935
|
+
address: resourceName(n.id),
|
|
6936
|
+
kind: "bucket"
|
|
6937
|
+
}];
|
|
6938
|
+
return [];
|
|
6939
|
+
});
|
|
6583
6940
|
}
|
|
6584
6941
|
async function runDevEmulators(input) {
|
|
6942
|
+
const nodes = appNodes(input);
|
|
6585
6943
|
const { url: computeUrl } = await ensureDaemon("compute", daemonEntry("compute"));
|
|
6586
6944
|
console.log(`[dev] compute emulator ready at ${computeUrl}`);
|
|
6587
|
-
|
|
6588
|
-
devDir: input.devDir,
|
|
6589
|
-
app: appNameOf(input.container)
|
|
6590
|
-
});
|
|
6591
|
-
if (retired !== void 0) console.log(`[dev] removed the local services of "${retired}", the app run here before`);
|
|
6592
|
-
const portChanges = await ensureServiceInstances(input.container, input.devDir, serviceAppNames(input));
|
|
6593
|
-
for (const { address, previous, given } of portChanges) console.log(`[dev] service "${address}" now uses port ${String(given)}: its previous port ${String(previous)} is in use`);
|
|
6594
|
-
if (usesBuckets(input)) {
|
|
6945
|
+
if (nodes.some((node) => node.kind === "bucket")) {
|
|
6595
6946
|
const { url: bucketsUrl } = await ensureDaemon("buckets", daemonEntry("buckets"));
|
|
6596
6947
|
console.log(`[dev] buckets emulator ready at ${bucketsUrl}`);
|
|
6597
6948
|
}
|
|
6598
|
-
if (
|
|
6949
|
+
if (nodes.some((node) => node.kind === "database")) {
|
|
6599
6950
|
const { url: postgresUrl } = await ensureDaemon("postgres", daemonEntry("postgres"));
|
|
6600
6951
|
console.log(`[dev] postgres emulator ready at ${postgresUrl}`);
|
|
6601
6952
|
}
|
|
6953
|
+
const scope = {
|
|
6954
|
+
devDir: input.devDir,
|
|
6955
|
+
app: appNameOf(input.container)
|
|
6956
|
+
};
|
|
6957
|
+
const previous = await selectApp(scope);
|
|
6958
|
+
if (previous !== void 0) console.log(`[dev] stopped the local services of "${previous}", the app run here before; its data is kept`);
|
|
6959
|
+
const portChanges = await ensureAppInstances(scope, nodes).catch(async (error) => {
|
|
6960
|
+
await stopAppInstances(scope).catch(() => void 0);
|
|
6961
|
+
throw error;
|
|
6962
|
+
});
|
|
6963
|
+
for (const { kind, address, previous: before, given } of portChanges) console.log(`[dev] ${kind} "${address}" now uses port ${String(given)}: its previous port ${String(before)} is in use`);
|
|
6602
6964
|
}
|
|
6603
6965
|
/** `local-placeholder-<16 lowercase hex>` — Web Crypto only, matching the extension's other local mints (ServiceKey, S3Credentials). */
|
|
6604
6966
|
function mintPlaceholder() {
|
|
@@ -6639,22 +7001,25 @@ async function runDevPreflight(input) {
|
|
|
6639
7001
|
}));
|
|
6640
7002
|
if (missing.length > 0) throw missingEnvParamsError(missing);
|
|
6641
7003
|
}
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
}
|
|
7004
|
+
const DAEMON_OF = {
|
|
7005
|
+
service: "compute",
|
|
7006
|
+
database: "postgres",
|
|
7007
|
+
bucket: "buckets"
|
|
7008
|
+
};
|
|
6647
7009
|
async function runDevTeardown(input) {
|
|
6648
7010
|
const app = prismaCloudContainerOf(input.container).input.appName;
|
|
6649
7011
|
const cwd = process.cwd();
|
|
6650
7012
|
const devDir = `${cwd}/${DEV_DIR}`;
|
|
6651
|
-
|
|
7013
|
+
const scope = {
|
|
7014
|
+
devDir,
|
|
7015
|
+
app
|
|
7016
|
+
};
|
|
6652
7017
|
try {
|
|
6653
|
-
await ensureDaemon(
|
|
6654
|
-
await
|
|
7018
|
+
for (const kind of await recordedKinds(scope)) await ensureDaemon(DAEMON_OF[kind], daemonEntry(DAEMON_OF[kind]));
|
|
7019
|
+
await deleteAppInstances(scope);
|
|
6655
7020
|
} finally {
|
|
6656
|
-
|
|
6657
|
-
removeLocalPaths([
|
|
7021
|
+
removeAppDevState(devDir, app);
|
|
7022
|
+
removeLocalPaths([`${cwd}/.alchemy/state/${app}/dev`]);
|
|
6658
7023
|
}
|
|
6659
7024
|
}
|
|
6660
7025
|
/** `localTargetProviders()`'s `ProviderCollection` doesn't structurally unify with Alchemy's inferred providers Layer (a @internal/lowering typings gap); it satisfies it at runtime — mirrors `control/extension.ts`'s own `asProvidersLayer`, duplicated rather than shared so this module imports nothing from `./control`. */
|
|
@@ -6669,11 +7034,12 @@ function asProvidersLayer(layer) {
|
|
|
6669
7034
|
function localTargetDescriptor() {
|
|
6670
7035
|
return {
|
|
6671
7036
|
container: devContainerDescriptor(),
|
|
6672
|
-
providers: (input) => asProvidersLayer(Layer.mergeAll(localTargetProviders(input), PgWarmProvider(),
|
|
7037
|
+
providers: (input) => asProvidersLayer(Layer.mergeAll(localTargetProviders(input), PgWarmProvider(), LocalOrmMigrationProvider(), S3CredentialsProvider(), GeneratedParamProvider(), ServiceKeyProvider())),
|
|
6673
7038
|
preflight: (input) => runDevPreflight(input),
|
|
6674
7039
|
emulators: (input) => runDevEmulators(input),
|
|
6675
7040
|
attach: (input) => devAttach(input),
|
|
6676
|
-
teardown: (input) => runDevTeardown(input)
|
|
7041
|
+
teardown: (input) => runDevTeardown(input),
|
|
7042
|
+
current: (input) => currentApp(input.devDir)
|
|
6677
7043
|
};
|
|
6678
7044
|
}
|
|
6679
7045
|
//#endregion
|