@prisma/composer-prisma-cloud 0.2.0-dev.13 → 0.2.0-dev.15

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.
@@ -1,4 +1,4 @@
1
- import { i as readOwnVersion, n as isValidSegment, r as readJsonFile, t as StateFile } from "./segments-NpKN-46R-B01DmCqy.mjs";
1
+ import { a as readOwnVersion, i as readJsonFile, r as isValidSegment, t as StateFile } from "./segments-NpKN-46R-C_LQ2urs.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as fs$1 from "node:fs/promises";
4
4
  import * as path from "node:path";
@@ -1,4 +1,4 @@
1
- import { i as readOwnVersion, n as isValidSegment, r as readJsonFile, t as StateFile } from "./segments-NpKN-46R-B01DmCqy.mjs";
1
+ import { a as readOwnVersion, i as readJsonFile, r as isValidSegment, t as StateFile } from "./segments-NpKN-46R-C_LQ2urs.mjs";
2
2
  import { n as portNumbers, t as getPorts } from "./get-port-BYnpbW5H.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { spawn } from "node:child_process";
@@ -1986,6 +1986,27 @@ function encodeSegment(segment) {
1986
1986
  if (!isValidSegment(segment)) throw new Error(`invalid path segment "${segment}"`);
1987
1987
  return encodeURIComponent(segment);
1988
1988
  }
1989
+ const ADMIN_FETCH_ATTEMPTS = 3;
1990
+ const ADMIN_FETCH_RETRY_DELAY_MS = 150;
1991
+ /**
1992
+ * `fetch` for the loopback admin calls, retried on transient socket errors:
1993
+ * a keep-alive reuse race (the runtime re-uses a pooled connection the
1994
+ * daemon's HTTP server just closed idle — surfaces as "fetch failed" /
1995
+ * "other side closed") or a briefly overloaded daemon. Every admin call is
1996
+ * idempotent by design, so a short blind retry is safe. A caller-driven
1997
+ * abort is never retried.
1998
+ */
1999
+ async function adminFetch(url, init) {
2000
+ let lastError;
2001
+ for (let attempt = 1; attempt <= ADMIN_FETCH_ATTEMPTS; attempt += 1) try {
2002
+ return await fetch(url, init);
2003
+ } catch (error) {
2004
+ if (init?.signal?.aborted === true) throw error;
2005
+ lastError = error;
2006
+ if (attempt < ADMIN_FETCH_ATTEMPTS) await new Promise((resolve) => setTimeout(resolve, ADMIN_FETCH_RETRY_DELAY_MS));
2007
+ }
2008
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
2009
+ }
1989
2010
  async function expectOk(res) {
1990
2011
  if (!res.ok) {
1991
2012
  const body = await res.text();
@@ -2016,33 +2037,29 @@ function computeClient(opts = {}) {
2016
2037
  return {
2017
2038
  baseUrl,
2018
2039
  async health() {
2019
- const body = await (await expectOk(await fetch(`${baseUrl}/health`))).json();
2040
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/health`))).json();
2020
2041
  if (!isHealthBody(body)) throw new Error("malformed /health response from the compute emulator");
2021
2042
  return body;
2022
2043
  },
2023
2044
  async ensureService(app, id) {
2024
- const url = `${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}`;
2025
- const body = await (await expectOk(await fetch(url, { method: "PUT" }))).json();
2045
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}`, { method: "PUT" }))).json();
2026
2046
  if (!isServiceReservation(body)) throw new Error("malformed service-reservation response from the compute emulator");
2027
2047
  return body;
2028
2048
  },
2029
2049
  async putDeployment(app, id, deployment) {
2030
- const url = `${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}/deployment`;
2031
- await expectOk(await fetch(url, {
2050
+ await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}/deployment`, {
2032
2051
  method: "PUT",
2033
2052
  headers: { "content-type": "application/json" },
2034
2053
  body: JSON.stringify(deployment)
2035
2054
  }));
2036
2055
  },
2037
2056
  async listServices(app) {
2038
- const url = `${baseUrl}/apps/${encodeSegment(app)}/services`;
2039
- const body = await (await expectOk(await fetch(url))).json();
2057
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/services`))).json();
2040
2058
  if (!isServiceInfoArray(body)) throw new Error("malformed services listing from the compute emulator");
2041
2059
  return body;
2042
2060
  },
2043
2061
  async *followLogs(app, id, signal) {
2044
- const url = `${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}/logs?follow=1`;
2045
- const body = (await expectOk(await fetch(url, signal ? { signal } : void 0))).body;
2062
+ const body = (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/services/${encodeSegment(id)}/logs?follow=1`, signal ? { signal } : void 0))).body;
2046
2063
  if (!body) return;
2047
2064
  const reader = body.getReader();
2048
2065
  const decoder = new TextDecoder();
@@ -2057,16 +2074,13 @@ function computeClient(opts = {}) {
2057
2074
  }
2058
2075
  },
2059
2076
  async stopApp(app) {
2060
- const url = `${baseUrl}/apps/${encodeSegment(app)}/stop`;
2061
- await expectOk(await fetch(url, { method: "POST" }));
2077
+ await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/stop`, { method: "POST" }));
2062
2078
  },
2063
2079
  async startApp(app) {
2064
- const url = `${baseUrl}/apps/${encodeSegment(app)}/start`;
2065
- await expectOk(await fetch(url, { method: "POST" }));
2080
+ await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/start`, { method: "POST" }));
2066
2081
  },
2067
2082
  async deleteApp(app) {
2068
- const url = `${baseUrl}/apps/${encodeSegment(app)}`;
2069
- await expectOk(await fetch(url, { method: "DELETE" }));
2083
+ await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}`, { method: "DELETE" }));
2070
2084
  }
2071
2085
  };
2072
2086
  }
@@ -2075,21 +2089,19 @@ function bucketsClient(opts = {}) {
2075
2089
  return {
2076
2090
  baseUrl,
2077
2091
  async health() {
2078
- const body = await (await expectOk(await fetch(`${baseUrl}/_pcdev/health`))).json();
2092
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/_pcdev/health`))).json();
2079
2093
  if (!isHealthBody(body)) throw new Error("malformed /_pcdev/health response from the buckets emulator");
2080
2094
  return body;
2081
2095
  },
2082
2096
  async putBucket(app, name, dir) {
2083
- const url = `${baseUrl}/_pcdev/apps/${encodeSegment(app)}/buckets/${encodeSegment(name)}`;
2084
- await expectOk(await fetch(url, {
2097
+ await expectOk(await adminFetch(`${baseUrl}/_pcdev/apps/${encodeSegment(app)}/buckets/${encodeSegment(name)}`, {
2085
2098
  method: "PUT",
2086
2099
  headers: { "content-type": "application/json" },
2087
2100
  body: JSON.stringify({ dir })
2088
2101
  }));
2089
2102
  },
2090
2103
  async putCredentials(app, accessKeyId, secretAccessKey) {
2091
- const url = `${baseUrl}/_pcdev/apps/${encodeSegment(app)}/credentials`;
2092
- await expectOk(await fetch(url, {
2104
+ await expectOk(await adminFetch(`${baseUrl}/_pcdev/apps/${encodeSegment(app)}/credentials`, {
2093
2105
  method: "PUT",
2094
2106
  headers: { "content-type": "application/json" },
2095
2107
  body: JSON.stringify({
@@ -2099,8 +2111,7 @@ function bucketsClient(opts = {}) {
2099
2111
  }));
2100
2112
  },
2101
2113
  async deleteApp(app) {
2102
- const url = `${baseUrl}/_pcdev/apps/${encodeSegment(app)}`;
2103
- await expectOk(await fetch(url, { method: "DELETE" }));
2114
+ await expectOk(await adminFetch(`${baseUrl}/_pcdev/apps/${encodeSegment(app)}`, { method: "DELETE" }));
2104
2115
  }
2105
2116
  };
2106
2117
  }
@@ -2118,13 +2129,12 @@ function postgresClient(opts = {}) {
2118
2129
  return {
2119
2130
  baseUrl,
2120
2131
  async health() {
2121
- const body = await (await expectOk(await fetch(`${baseUrl}/health`))).json();
2132
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/health`))).json();
2122
2133
  if (!isHealthBody(body)) throw new Error("malformed /health response from the postgres emulator");
2123
2134
  return body;
2124
2135
  },
2125
2136
  async ensureDatabase(app, id, prismaDevModulePath) {
2126
- const url = `${baseUrl}/apps/${encodeSegment(app)}/databases/${encodeSegment(id)}`;
2127
- const body = await (await expectOk(await fetch(url, {
2137
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/databases/${encodeSegment(id)}`, {
2128
2138
  method: "PUT",
2129
2139
  headers: { "content-type": "application/json" },
2130
2140
  body: JSON.stringify({ prismaDevModulePath })
@@ -2133,14 +2143,12 @@ function postgresClient(opts = {}) {
2133
2143
  return body;
2134
2144
  },
2135
2145
  async listDatabases(app) {
2136
- const url = `${baseUrl}/apps/${encodeSegment(app)}/databases`;
2137
- const body = await (await expectOk(await fetch(url))).json();
2146
+ const body = await (await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}/databases`))).json();
2138
2147
  if (!isDatabaseInfoArray(body)) throw new Error("malformed databases listing from the postgres emulator");
2139
2148
  return body;
2140
2149
  },
2141
2150
  async deleteApp(app) {
2142
- const url = `${baseUrl}/apps/${encodeSegment(app)}`;
2143
- await expectOk(await fetch(url, { method: "DELETE" }));
2151
+ await expectOk(await adminFetch(`${baseUrl}/apps/${encodeSegment(app)}`, { method: "DELETE" }));
2144
2152
  }
2145
2153
  };
2146
2154
  }
@@ -5668,6 +5676,32 @@ function servicePortEnvKey(address) {
5668
5676
  "PORT"
5669
5677
  ].join("_").toUpperCase();
5670
5678
  }
5679
+ /** The `COMPOSER_<ADDRESS SEGMENTS>_` prefix every env row this address OWNS carries — `configKey`'s convention (see `servicePortEnvKey`'s doc comment). */
5680
+ function ownEnvKeyPrefix(address) {
5681
+ return `${["COMPOSER", ...address.split(".").filter((s) => s.length > 0)].join("_").toUpperCase()}_`;
5682
+ }
5683
+ const COMPOSER_NAMESPACE_PREFIX = "COMPOSER_";
5684
+ /**
5685
+ * Scopes `env.json` to what THIS service is allowed to see: rows it owns
5686
+ * (`COMPOSER_<its address>_*`) plus every row OUTSIDE the `COMPOSER_`
5687
+ * namespace entirely — the poison `DATABASE_URL(_POOLED)` rows are
5688
+ * deliberately unprefixed and app-wide (local-dev spec § 4's pinned parity
5689
+ * note). The hosted platform materializes the app-wide row set into every
5690
+ * deployment but DIFFS a deployment only on its own referenced rows; an
5691
+ * app-wide LOCAL materialization restart-amplifies instead — an
5692
+ * early-deployed service's snapshot is incomplete on the first converge,
5693
+ * "completes" on the second, and diffs as changed. Scoping the content here
5694
+ * aligns local restart behavior with the platform's diff scope. The dropped
5695
+ * sibling rows have no sanctioned reader: `run()`/`load()` consume only
5696
+ * own-address rows, and ambient sibling reads are exactly what the poison
5697
+ * rows exist to punish.
5698
+ */
5699
+ function scopedEnvRows(allRows, address) {
5700
+ const ownPrefix = ownEnvKeyPrefix(address);
5701
+ const scoped = {};
5702
+ for (const [key, value] of Object.entries(allRows)) if (key.startsWith(ownPrefix) || !key.startsWith(COMPOSER_NAMESPACE_PREFIX)) scoped[key] = value;
5703
+ return scoped;
5704
+ }
5671
5705
  function manifestMissingAddressError() {
5672
5706
  return /* @__PURE__ */ new Error("artifact manifest carries no address — repackage with a current @prisma/composer.");
5673
5707
  }
@@ -5692,8 +5726,24 @@ function readManifestAddress(artifactDir) {
5692
5726
  if (!isComputeManifest(parsed) || parsed.address === void 0) throw manifestMissingAddressError();
5693
5727
  return parsed.address;
5694
5728
  }
5729
+ /**
5730
+ * The Compute emulator's `<id>` path segment must match
5731
+ * `/^[a-z0-9][a-z0-9-]*$/` (its API hygiene rule, local-dev spec § 2) — but a
5732
+ * service's own address (`news.name`/`news.computeServiceId`) is
5733
+ * hierarchical and dot-separated (e.g. `"orders.service"`, a nested
5734
+ * module's service). This is the seam: every dot (or other disallowed char)
5735
+ * becomes a dash, runs collapse, and the result is what both `ensureService`
5736
+ * and `putDeployment` address the emulator with — the REAL address still
5737
+ * rides the deployment body's `address` field untouched, so the front door
5738
+ * and every listing still show it verbatim (compute-main.ts's `svc.address`
5739
+ * is set from that field, not from the id).
5740
+ */
5741
+ function slugServiceId(address) {
5742
+ const slug = address.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5743
+ return slug.length > 0 ? slug : "svc";
5744
+ }
5695
5745
  async function materializeEnv(devDir, address, port) {
5696
- const env = { ...await envStore(devDir).read() };
5746
+ const env = scopedEnvRows(await envStore(devDir).read(), address);
5697
5747
  env[servicePortEnvKey(address)] = JSON.stringify(port);
5698
5748
  const secrets = await secretsStore(devDir).read();
5699
5749
  for (const [key, value] of Object.entries(secrets)) env[key] = value;
@@ -5712,7 +5762,7 @@ function LocalComputeServiceProvider(input) {
5712
5762
  reconcile: ({ news }) => Effect.tryPromise({
5713
5763
  try: async () => {
5714
5764
  const app = appNameOf(input.container);
5715
- const { url } = await computeClient().ensureService(app, news.name);
5765
+ const { url } = await computeClient().ensureService(app, slugServiceId(news.name));
5716
5766
  return {
5717
5767
  id: news.name,
5718
5768
  name: news.name,
@@ -5767,12 +5817,13 @@ function LocalDeploymentProvider(input) {
5767
5817
  try: async () => {
5768
5818
  const app = appNameOf(input.container);
5769
5819
  const id = news.computeServiceId;
5820
+ const emulatorId = slugServiceId(id);
5770
5821
  const artifactDir = path.join(input.devDir, "artifacts", news.artifactHash);
5771
5822
  if (!fs$1.existsSync(artifactDir)) extractComputeArtifact(news.artifactPath, artifactDir);
5772
5823
  const address = readManifestAddress(artifactDir);
5773
- const { port } = await computeClient().ensureService(app, id);
5824
+ const { port } = await computeClient().ensureService(app, emulatorId);
5774
5825
  const env = await materializeEnv(input.devDir, address, port);
5775
- await computeClient().putDeployment(app, id, {
5826
+ await computeClient().putDeployment(app, emulatorId, {
5776
5827
  address,
5777
5828
  artifactDir,
5778
5829
  artifactHash: news.artifactHash,
@@ -5860,7 +5911,7 @@ function LocalDatabaseProvider(input) {
5860
5911
  try: async () => {
5861
5912
  const app = appNameOf(input.container);
5862
5913
  const prismaDevModulePath = resolvePrismaDevModulePath(process.cwd());
5863
- const { url } = await postgresClient().ensureDatabase(app, news.name, prismaDevModulePath);
5914
+ const { url } = await postgresClient().ensureDatabase(app, slug(news.name), prismaDevModulePath);
5864
5915
  return {
5865
5916
  id: instanceNameFor(app, news.name),
5866
5917
  name: news.name,
@@ -5953,7 +6004,7 @@ async function* mergedLogs(app, signal) {
5953
6004
  wake?.();
5954
6005
  wake = void 0;
5955
6006
  };
5956
- const follow = (id) => {
6007
+ const follow = (id, address) => {
5957
6008
  if (followed.has(id)) return;
5958
6009
  followed.add(id);
5959
6010
  (async () => {
@@ -5964,7 +6015,7 @@ async function* mergedLogs(app, signal) {
5964
6015
  let newlineAt = buffer.indexOf("\n");
5965
6016
  while (newlineAt !== -1) {
5966
6017
  push({
5967
- service: id,
6018
+ service: address,
5968
6019
  line: buffer.slice(0, newlineAt)
5969
6020
  });
5970
6021
  buffer = buffer.slice(newlineAt + 1);
@@ -5978,7 +6029,7 @@ async function* mergedLogs(app, signal) {
5978
6029
  };
5979
6030
  const relist = async () => {
5980
6031
  const services = await client.listServices(app);
5981
- for (const svc of services) follow(svc.id);
6032
+ for (const svc of services) follow(svc.id, svc.address);
5982
6033
  };
5983
6034
  await relist();
5984
6035
  const timer = setInterval(() => void relist(), RELIST_INTERVAL_MS);
@@ -6006,6 +6057,7 @@ async function devAttach(input) {
6006
6057
  const app = prismaCloudContainerOf(input.container).input.appName;
6007
6058
  const client = computeClient();
6008
6059
  return {
6060
+ startServices: () => client.startApp(app),
6009
6061
  endpoints: async () => {
6010
6062
  return (await client.listServices(app)).map((svc) => ({
6011
6063
  address: svc.address,