@treeseed/sdk 0.10.17 → 0.10.18

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.
@@ -288,12 +288,7 @@ export declare function ensureRailwayServiceVolume({ projectId, environmentId, s
288
288
  env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
289
289
  fetchImpl?: typeof fetch;
290
290
  }): Promise<{
291
- volume: {
292
- instances: RailwayVolumeInstanceSummary[];
293
- id: string;
294
- name: string;
295
- projectId: string | null;
296
- } | null;
291
+ volume: RailwayVolumeSummary | null;
297
292
  instance: RailwayVolumeInstanceSummary | null;
298
293
  created: boolean;
299
294
  updated: boolean;
@@ -840,7 +840,13 @@ async function ensureRailwayServiceInstanceConfiguration({
840
840
  env = process.env,
841
841
  fetchImpl = fetch
842
842
  }) {
843
- const current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
843
+ let current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
844
+ if (!current.id) {
845
+ for (let attempt = 0; attempt < 8 && !current.id; attempt += 1) {
846
+ await new Promise((resolve) => setTimeout(resolve, 1500));
847
+ current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
848
+ }
849
+ }
844
850
  if (!current.id) {
845
851
  return { instance: current, updated: false };
846
852
  }
@@ -905,12 +911,21 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
905
911
  }
906
912
  throw error;
907
913
  }
908
- const instance = await getRailwayServiceInstance({
914
+ let instance = await getRailwayServiceInstance({
909
915
  serviceId,
910
916
  environmentId,
911
917
  env,
912
918
  fetchImpl
913
919
  });
920
+ for (let attempt = 0; attempt < 5 && serviceInstanceDrifted(instance, desired); attempt += 1) {
921
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
922
+ instance = await getRailwayServiceInstance({
923
+ serviceId,
924
+ environmentId,
925
+ env,
926
+ fetchImpl
927
+ });
928
+ }
914
929
  return {
915
930
  instance: {
916
931
  id: instance.id || current.id,
@@ -929,6 +944,9 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
929
944
  updated: true
930
945
  };
931
946
  }
947
+ function serviceInstanceDrifted(current, desired) {
948
+ return desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.cronSchedule !== null && desired.cronSchedule !== current.cronSchedule || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode;
949
+ }
932
950
  async function listRailwayVariables({
933
951
  projectId,
934
952
  environmentId,
@@ -1193,23 +1211,37 @@ async function ensureRailwayServiceVolume({
1193
1211
  ...candidate,
1194
1212
  instances: candidate.instances.filter(isActiveRailwayVolumeInstance)
1195
1213
  })).filter((candidate) => candidate.instances.length > 0);
1196
- let volume = activeVolumes.find(
1197
- (candidate) => candidate.instances.some((instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId)
1198
- ) ?? activeVolumes.find(
1214
+ let volume = findRailwayVolumeForService(volumes, serviceId, environmentId) ?? activeVolumes.find(
1199
1215
  (candidate) => candidate.name === name && candidate.instances.some((instance2) => instance2.environmentId === environmentId)
1200
1216
  ) ?? null;
1201
1217
  let created = false;
1202
1218
  let updated = false;
1203
1219
  const createReplacementVolume = async () => {
1204
- const replacement = await createRailwayVolume({
1205
- projectId,
1206
- environmentId,
1207
- serviceId,
1208
- name,
1209
- mountPath,
1210
- env,
1211
- fetchImpl
1212
- });
1220
+ let replacement;
1221
+ try {
1222
+ replacement = await createRailwayVolume({
1223
+ projectId,
1224
+ environmentId,
1225
+ serviceId,
1226
+ name,
1227
+ mountPath,
1228
+ env,
1229
+ fetchImpl
1230
+ });
1231
+ } catch (error) {
1232
+ if (!looksLikeRailwayVolumeCreateRace(error)) {
1233
+ throw error;
1234
+ }
1235
+ for (let attempt = 0; attempt < 8; attempt += 1) {
1236
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1237
+ const refreshed = await listRailwayVolumes({ projectId, env, fetchImpl });
1238
+ const existing = findRailwayVolumeForService(refreshed, serviceId, environmentId);
1239
+ if (existing) {
1240
+ return existing;
1241
+ }
1242
+ }
1243
+ throw error;
1244
+ }
1213
1245
  created = true;
1214
1246
  return replacement;
1215
1247
  };
@@ -1259,6 +1291,17 @@ async function ensureRailwayServiceVolume({
1259
1291
  }
1260
1292
  return { volume, instance, created, updated };
1261
1293
  }
1294
+ function findRailwayVolumeForService(volumes, serviceId, environmentId) {
1295
+ return volumes.find(
1296
+ (candidate) => candidate.instances.some(
1297
+ (instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && isActiveRailwayVolumeInstance(instance)
1298
+ )
1299
+ ) ?? null;
1300
+ }
1301
+ function looksLikeRailwayVolumeCreateRace(error) {
1302
+ const message = error instanceof Error ? error.message : String(error ?? "");
1303
+ return /would have \d+ volumes attached|can only have one volume|not authorized/iu.test(message);
1304
+ }
1262
1305
  async function listRailwayCustomDomains({
1263
1306
  projectId,
1264
1307
  environmentId,
@@ -144,6 +144,23 @@ export declare function planRailwayServiceLink(service: any, { env }?: {
144
144
  args: string[];
145
145
  cwd: any;
146
146
  };
147
+ export declare function ensureRailwayServiceVolumeWithCliFallback({ tenantRoot, projectId, environmentId, environmentName, serviceId, serviceName, name, mountPath, preferCli, env, }: {
148
+ tenantRoot: any;
149
+ projectId: any;
150
+ environmentId: any;
151
+ environmentName: any;
152
+ serviceId: any;
153
+ serviceName: any;
154
+ name: any;
155
+ mountPath: any;
156
+ preferCli?: boolean | undefined;
157
+ env?: NodeJS.ProcessEnv | undefined;
158
+ }): Promise<{
159
+ volume: any;
160
+ instance: any;
161
+ created: boolean;
162
+ updated: boolean;
163
+ }>;
147
164
  export declare function deployRailwayService(tenantRoot: any, service: any, { dryRun, write, prefix, env, }?: {
148
165
  dryRun?: boolean;
149
166
  write?: TreeseedBootstrapWriter;
@@ -1925,6 +1925,7 @@ export {
1925
1925
  ensureRailwayProjectExists,
1926
1926
  ensureRailwayScheduledJobs,
1927
1927
  ensureRailwayServiceExists,
1928
+ ensureRailwayServiceVolumeWithCliFallback,
1928
1929
  isRailwayTransientFailure,
1929
1930
  isUsableRailwayToken,
1930
1931
  planRailwayServiceDeploy,
@@ -32,6 +32,7 @@ import {
32
32
  configuredRailwayServices,
33
33
  deriveRailwayMarketOperationsRunnerVolumeName,
34
34
  ensureRailwayProjectContext,
35
+ ensureRailwayServiceVolumeWithCliFallback,
35
36
  runRailway,
36
37
  validateRailwayDeployPrerequisites
37
38
  } from "../operations/services/railway-deploy.js";
@@ -42,7 +43,6 @@ import {
42
43
  ensureRailwayProject,
43
44
  ensureRailwayService,
44
45
  ensureRailwayServiceInstanceConfiguration,
45
- ensureRailwayServiceVolume,
46
46
  getRailwayServiceInstance,
47
47
  getRailwayProject,
48
48
  listRailwayCustomDomains,
@@ -1568,12 +1568,16 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
1568
1568
  });
1569
1569
  if (entry.configuredService.volumeMountPath) {
1570
1570
  const volumeName = entry.configuredService.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(entry.service.name, entry.environment.name) : `${entry.service.name}-volume`;
1571
- const volume = await ensureRailwayServiceVolume({
1571
+ const volume = await ensureRailwayServiceVolumeWithCliFallback({
1572
+ tenantRoot: input.context.tenantRoot,
1572
1573
  projectId: entry.project.id,
1573
1574
  environmentId: entry.environment.id,
1575
+ environmentName: entry.environment.name,
1574
1576
  serviceId: entry.service.id,
1577
+ serviceName: entry.service.name,
1575
1578
  name: volumeName,
1576
1579
  mountPath: entry.configuredService.volumeMountPath,
1580
+ preferCli: entry.configuredService.key === "marketOperationsRunner",
1577
1581
  env: topology.env
1578
1582
  });
1579
1583
  if (!volume.instance?.serviceId) {
@@ -1660,7 +1664,7 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
1660
1664
  env: topology.env
1661
1665
  });
1662
1666
  }
1663
- for (let attempt = 0; attempt < 8; attempt += 1) {
1667
+ for (let attempt = 0; attempt < 20; attempt += 1) {
1664
1668
  const existingVolumes = await listRailwayVolumes({
1665
1669
  projectId: firstService.project.id,
1666
1670
  env: topology.env
@@ -1669,18 +1673,10 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
1669
1673
  (instance) => instance.serviceId === postgresService.id && instance.environmentId === firstService.environment?.id && instance.mountPath === "/var/lib/postgresql/data"
1670
1674
  ));
1671
1675
  if (attached) {
1672
- break;
1676
+ return;
1673
1677
  }
1674
- await new Promise((resolve2) => setTimeout(resolve2, 1500));
1678
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
1675
1679
  }
1676
- await ensureRailwayServiceVolume({
1677
- projectId: firstService.project.id,
1678
- environmentId: firstService.environment.id,
1679
- serviceId: postgresService.id,
1680
- name: `postgres-${topology.scope === "prod" ? "prod" : topology.scope}-data`,
1681
- mountPath: "/var/lib/postgresql/data",
1682
- env: topology.env
1683
- });
1684
1680
  }
1685
1681
  async function observeRailwayUnit(input, { refresh = false } = {}) {
1686
1682
  let attempt = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.10.17",
3
+ "version": "0.10.18",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {