@cargo-ai/cdk 1.0.7 → 1.0.8

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.
Files changed (38) hide show
  1. package/README.md +0 -43
  2. package/build/src/cli/auth.d.ts.map +1 -1
  3. package/build/src/cli/auth.js +16 -12
  4. package/build/src/cli/commands/deploy.d.ts.map +1 -1
  5. package/build/src/cli/commands/deploy.js +81 -107
  6. package/build/src/cli/commands/from.d.ts.map +1 -1
  7. package/build/src/cli/commands/from.js +18 -8
  8. package/build/src/cli/commands/init.d.ts.map +1 -1
  9. package/build/src/cli/commands/init.js +13 -8
  10. package/build/src/cli/commands/inputTypes.d.ts.map +1 -1
  11. package/build/src/cli/commands/inputTypes.js +77 -6
  12. package/build/src/cli/commands/types.js +26 -10
  13. package/build/src/cli/io.js +2 -2
  14. package/build/src/cli/version.d.ts.map +1 -1
  15. package/build/src/cli/version.js +10 -6
  16. package/build/src/deploy/apply.d.ts.map +1 -1
  17. package/build/src/deploy/apply.js +37 -30
  18. package/build/src/deploy/compile.d.ts.map +1 -1
  19. package/build/src/deploy/compile.js +7 -5
  20. package/build/src/deploy/destroy.d.ts.map +1 -1
  21. package/build/src/deploy/destroy.js +24 -14
  22. package/build/src/deploy/executors.live.d.ts.map +1 -1
  23. package/build/src/deploy/executors.live.js +202 -173
  24. package/build/src/deploy/import.js +2 -2
  25. package/build/src/deploy/index.js +1 -1
  26. package/build/src/deploy/lock.d.ts.map +1 -1
  27. package/build/src/deploy/lock.js +13 -10
  28. package/build/src/deploy/readers.live.d.ts.map +1 -1
  29. package/build/src/deploy/readers.live.js +27 -15
  30. package/build/src/deploy/refresh.d.ts.map +1 -1
  31. package/build/src/deploy/refresh.js +20 -18
  32. package/build/src/refs.d.ts.map +1 -1
  33. package/build/src/refs.js +5 -1
  34. package/build/src/resources/actions.d.ts +1 -1
  35. package/build/src/resources/actions.d.ts.map +1 -1
  36. package/build/src/resources/actions.js +3 -2
  37. package/build/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +2 -2
@@ -27,6 +27,161 @@ async function waitForDeployment(api, uuid, attemptsLeft = 150) {
27
27
  await sleep(2000);
28
28
  return waitForDeployment(api, uuid, attemptsLeft - 1);
29
29
  }
30
+ // Upsert the play row (create or update), returning its uuid + backing workflow
31
+ // uuid. Builds the create/update payload internally (used only here).
32
+ async function upsertPlay(api, spec, prior) {
33
+ // Play has no slug — identity is purely the prior state uuid.
34
+ const base = {
35
+ name: str(spec["name"]),
36
+ description: optStr(spec["description"]),
37
+ modelUuid: str(spec["modelUuid"]),
38
+ changeKinds: spec["changeKinds"],
39
+ runCreationRule: spec["runCreationRule"],
40
+ filter: spec["filter"],
41
+ sort: spec["sort"],
42
+ limit: spec["limit"],
43
+ trackingColumnSlugs: spec["trackingColumnSlugs"],
44
+ schedule: spec["schedule"],
45
+ folderUuid: optStr(spec["folderUuid"]),
46
+ };
47
+ if (prior !== undefined) {
48
+ // play.update rejects modelUuid (a play's model is fixed at
49
+ // creation) — strip it from the update payload.
50
+ const { modelUuid: _modelUuid, ...updateBase } = base;
51
+ await api.orchestration.play.update({
52
+ uuid: prior.uuid,
53
+ ...updateBase,
54
+ });
55
+ return {
56
+ uuid: prior.uuid,
57
+ workflowUuid: prior.outputs["workflowUuid"],
58
+ };
59
+ }
60
+ const created = await api.orchestration.play.create(base);
61
+ return {
62
+ uuid: created.play.uuid,
63
+ workflowUuid: created.play.workflowUuid,
64
+ };
65
+ }
66
+ // Ensure the agent row exists (create or update) and return its uuid. The
67
+ // config release (full desired state) is deployed separately by the caller.
68
+ async function ensureAgentRow(api, spec, prior) {
69
+ const description = optStr(spec["description"]);
70
+ if (prior !== undefined) {
71
+ await api.ai.agent.update({
72
+ uuid: prior.uuid,
73
+ name: str(spec["name"]),
74
+ icon: spec["icon"],
75
+ description: description === undefined ? null : description,
76
+ triggers: mapAgentTriggers(spec["triggers"]),
77
+ });
78
+ return prior.uuid;
79
+ }
80
+ const created = await api.ai.agent.create({
81
+ name: str(spec["name"]),
82
+ icon: spec["icon"],
83
+ description,
84
+ triggers: mapAgentTriggers(spec["triggers"]),
85
+ folderUuid: optStr(spec["folderUuid"]),
86
+ });
87
+ return created.agent.uuid;
88
+ }
89
+ // Upsert the tool row (create or update), returning its uuid + backing workflow
90
+ // uuid. Builds the create/update payload internally (used only here).
91
+ async function upsertTool(api, spec, prior) {
92
+ const triggers = mapToolTriggers(spec["triggers"]);
93
+ const publicForm = spec["publicForm"];
94
+ const description = optStr(spec["description"]);
95
+ if (prior !== undefined) {
96
+ await api.orchestration.tool.update({
97
+ uuid: prior.uuid,
98
+ name: str(spec["name"]),
99
+ icon: spec["icon"],
100
+ description: description === undefined ? null : description,
101
+ triggers,
102
+ publicForm,
103
+ });
104
+ return {
105
+ toolUuid: prior.uuid,
106
+ workflowUuid: prior.outputs["workflowUuid"],
107
+ };
108
+ }
109
+ const created = await api.orchestration.tool.create({
110
+ name: str(spec["name"]),
111
+ icon: spec["icon"],
112
+ description,
113
+ triggers,
114
+ folderUuid: optStr(spec["folderUuid"]),
115
+ });
116
+ const toolUuid = created.tool.uuid;
117
+ const workflowUuid = created.tool.workflowUuid;
118
+ // publicForm isn't a create param — apply it via update when set.
119
+ if (publicForm !== null && publicForm !== undefined) {
120
+ await api.orchestration.tool.update({ uuid: toolUuid, publicForm });
121
+ }
122
+ return { toolUuid, workflowUuid };
123
+ }
124
+ // Upsert the worker row (create, update, or recover a same-slug slot),
125
+ // returning its uuid and whether an existing slot was adopted.
126
+ async function upsertWorker(api, spec, prior, slug) {
127
+ const triggers = mapWorkerTriggers(spec["triggers"]);
128
+ const description = optStr(spec["description"]);
129
+ if (prior !== undefined) {
130
+ await api.hosting.worker.update({ uuid: prior.uuid, triggers });
131
+ return { uuid: prior.uuid, adopted: false };
132
+ }
133
+ // Recover our own slot if a prior deploy created it but crashed before
134
+ // persisting (e.g. a failed build). The slug is CDK-chosen, so a worker
135
+ // with the same slug is *probably* ours — reuse it instead of
136
+ // conflicting. We can't be certain it isn't a foreign same-slug worker,
137
+ // so mark it adopted: destroy will release (not delete) it.
138
+ const { workers } = await api.hosting.worker.list();
139
+ const existing = workers.find((w) => w.slug === slug);
140
+ if (existing !== undefined) {
141
+ await api.hosting.worker.update({ uuid: existing.uuid, triggers });
142
+ return { uuid: existing.uuid, adopted: true };
143
+ }
144
+ const created = await api.hosting.worker.create({
145
+ name: str(spec["name"]),
146
+ slug,
147
+ description: description === undefined ? null : description,
148
+ folderUuid: optStr(spec["folderUuid"]),
149
+ triggers,
150
+ });
151
+ return { uuid: created.worker.uuid, adopted: false };
152
+ }
153
+ // Resolve the app's slot: reuse the prior uuid, recover a same-slug slot, or
154
+ // create it. Returns the uuid and whether an existing slot was adopted.
155
+ async function resolveAppSlot(api, spec, prior, slug) {
156
+ if (prior !== undefined)
157
+ return { uuid: prior.uuid, adopted: false };
158
+ // Recover our own slot if a prior deploy created it but crashed before
159
+ // persisting — same-slug app is *probably* ours (the slug is
160
+ // CDK-chosen). We can't be certain, so mark it adopted: destroy will
161
+ // release (not delete) it.
162
+ const { apps } = await api.hosting.app.list();
163
+ const existing = apps.find((a) => a.slug === slug);
164
+ if (existing !== undefined) {
165
+ return { uuid: existing.uuid, adopted: true };
166
+ }
167
+ const created = await api.hosting.app.create({
168
+ name: str(spec["name"]),
169
+ slug,
170
+ description: optStr(spec["description"]),
171
+ folderUuid: optStr(spec["folderUuid"]),
172
+ });
173
+ return { uuid: created.app.uuid, adopted: false };
174
+ }
175
+ // Read a context file's current content through the runtime; undefined when it
176
+ // doesn't exist yet (read 404s).
177
+ async function readContextFile(api, path) {
178
+ try {
179
+ return (await api.context.runtime.read({ path })).content;
180
+ }
181
+ catch {
182
+ return undefined; // new file — read 404s
183
+ }
184
+ }
30
185
  export function liveExecutors(api) {
31
186
  // Resolve the dataset uuid auto-created alongside a connector — needed when a
32
187
  // connector is ADOPTED (create returns it directly; list/adopt does not).
@@ -49,9 +204,9 @@ export function liveExecutors(api) {
49
204
  await api.connection.connector.update({
50
205
  uuid: prior.uuid,
51
206
  name: str(spec["name"]),
52
- config: config ?? null,
53
- rateLimit: rateLimit ?? null,
54
- cacheTtlMilliseconds: cacheTtlMilliseconds ?? null,
207
+ config: config === undefined ? null : config,
208
+ rateLimit: rateLimit === undefined ? null : rateLimit,
209
+ cacheTtlMilliseconds: cacheTtlMilliseconds === undefined ? null : cacheTtlMilliseconds,
55
210
  });
56
211
  // Preserve the adopted marker so destroy keeps releasing (not deleting).
57
212
  const out = {
@@ -64,9 +219,9 @@ export function liveExecutors(api) {
64
219
  return out;
65
220
  }
66
221
  const explicitAdopt = spec["adopt"] === true;
67
- const adopt = explicitAdopt ||
222
+ const adopt = explicitAdopt === true ||
68
223
  (await api.connection.connector.existsBySlug({ slug })).exists;
69
- if (adopt) {
224
+ if (adopt === true) {
70
225
  const { connectors } = await api.connection.connector.list({
71
226
  integrationSlug,
72
227
  });
@@ -74,10 +229,11 @@ export function liveExecutors(api) {
74
229
  // workspace already treats as default for this integration, falling back
75
230
  // to a slug match. Implicit adopt (a same-slug connector already exists)
76
231
  // recovers that exact slot, so it stays a strict slug match.
77
- const found = explicitAdopt
78
- ? (connectors.find((c) => c.isDefault === true) ??
79
- connectors.find((c) => c.slug === slug))
80
- : connectors.find((c) => c.slug === slug);
232
+ const defaultConnector = connectors.find((c) => c.isDefault === true);
233
+ const slugMatch = connectors.find((c) => c.slug === slug);
234
+ const found = explicitAdopt === true && defaultConnector !== undefined
235
+ ? defaultConnector
236
+ : slugMatch;
81
237
  if (found === undefined) {
82
238
  throw new Error(explicitAdopt
83
239
  ? `no connector found to adopt for integration "${integrationSlug}"`
@@ -107,6 +263,7 @@ export function liveExecutors(api) {
107
263
  async model(spec, prior) {
108
264
  const slug = str(spec["slug"]);
109
265
  const datasetUuid = str(spec["datasetUuid"]);
266
+ const description = optStr(spec["description"]);
110
267
  const schedule = spec["schedule"];
111
268
  // Unification is not part of CreateModelPayload — it's set through an
112
269
  // update, both when the model is first created and on later syncs. Left
@@ -119,9 +276,9 @@ export function liveExecutors(api) {
119
276
  await api.storage.model.update({
120
277
  uuid: prior.uuid,
121
278
  name: str(spec["name"]),
122
- description: optStr(spec["description"]) ?? null,
279
+ description: description === undefined ? null : description,
123
280
  config: spec["config"] ?? {},
124
- schedule: schedule ?? null,
281
+ schedule: schedule === undefined ? null : schedule,
125
282
  unification,
126
283
  });
127
284
  const out = { uuid: prior.uuid };
@@ -147,7 +304,7 @@ export function liveExecutors(api) {
147
304
  const created = await api.storage.model.create({
148
305
  slug,
149
306
  name: str(spec["name"]),
150
- description: optStr(spec["description"]),
307
+ description,
151
308
  datasetUuid,
152
309
  extractorSlug: str(spec["extractorSlug"]),
153
310
  config: spec["config"] ?? {},
@@ -165,45 +322,16 @@ export function liveExecutors(api) {
165
322
  return { uuid: created.model.uuid };
166
323
  },
167
324
  async play(spec, prior) {
168
- // Play has no slug identity is purely the prior state uuid.
169
- const base = {
170
- name: str(spec["name"]),
171
- description: optStr(spec["description"]),
172
- modelUuid: str(spec["modelUuid"]),
173
- changeKinds: spec["changeKinds"],
174
- runCreationRule: spec["runCreationRule"],
175
- filter: spec["filter"],
176
- sort: spec["sort"],
177
- limit: spec["limit"],
178
- trackingColumnSlugs: spec["trackingColumnSlugs"],
179
- schedule: spec["schedule"],
180
- folderUuid: optStr(spec["folderUuid"]),
181
- };
182
- let uuid;
183
- let workflowUuid;
184
- if (prior !== undefined) {
185
- // play.update rejects modelUuid (a play's model is fixed at creation) —
186
- // strip it from the update payload.
187
- const { modelUuid: _modelUuid, ...updateBase } = base;
188
- await api.orchestration.play.update({
189
- uuid: prior.uuid,
190
- ...updateBase,
191
- });
192
- uuid = prior.uuid;
193
- workflowUuid = prior.outputs["workflowUuid"];
194
- }
195
- else {
196
- const created = await api.orchestration.play.create(base);
197
- uuid = created.play.uuid;
198
- workflowUuid = created.play.workflowUuid;
199
- }
325
+ const { uuid, workflowUuid } = await upsertPlay(api, spec, prior);
200
326
  // Deploy the play's backing workflow graph as its release, if provided.
201
327
  const nodes = spec["nodes"];
202
328
  if (nodes !== undefined && workflowUuid !== undefined) {
203
329
  await api.orchestration.draftRelease.deploy({
204
330
  workflowUuid,
205
331
  nodes: nodes,
206
- formFields: (spec["formFields"] ?? null),
332
+ formFields: (spec["formFields"] === undefined
333
+ ? null
334
+ : spec["formFields"]),
207
335
  });
208
336
  }
209
337
  const result = { uuid };
@@ -223,27 +351,7 @@ export function liveExecutors(api) {
223
351
  // NOTE: agent + release are two calls. A crash between them orphans the
224
352
  // agent (no slug to re-find it). Acceptable for v1; a partial-persist hook
225
353
  // would close the window.
226
- let agentUuid;
227
- if (prior !== undefined) {
228
- agentUuid = prior.uuid;
229
- await api.ai.agent.update({
230
- uuid: agentUuid,
231
- name: str(spec["name"]),
232
- icon: spec["icon"],
233
- description: optStr(spec["description"]) ?? null,
234
- triggers: mapAgentTriggers(spec["triggers"]),
235
- });
236
- }
237
- else {
238
- const created = await api.ai.agent.create({
239
- name: str(spec["name"]),
240
- icon: spec["icon"],
241
- description: optStr(spec["description"]),
242
- triggers: mapAgentTriggers(spec["triggers"]),
243
- folderUuid: optStr(spec["folderUuid"]),
244
- });
245
- agentUuid = created.agent.uuid;
246
- }
354
+ const agentUuid = await ensureAgentRow(api, spec, prior);
247
355
  // ── phase B: deploy the config release (full desired state) ──
248
356
  // No explicit version — the backend auto-bumps (an explicit wrong value
249
357
  // throws invalidReleaseVersion).
@@ -308,37 +416,7 @@ export function liveExecutors(api) {
308
416
  async tool(spec, prior) {
309
417
  const nodes = spec["nodes"];
310
418
  const formFields = spec["formFields"];
311
- const triggers = mapToolTriggers(spec["triggers"]);
312
- const publicForm = spec["publicForm"];
313
- let toolUuid;
314
- let workflowUuid;
315
- if (prior !== undefined) {
316
- toolUuid = prior.uuid;
317
- workflowUuid = prior.outputs["workflowUuid"];
318
- await api.orchestration.tool.update({
319
- uuid: toolUuid,
320
- name: str(spec["name"]),
321
- icon: spec["icon"],
322
- description: optStr(spec["description"]) ?? null,
323
- triggers,
324
- publicForm,
325
- });
326
- }
327
- else {
328
- const created = await api.orchestration.tool.create({
329
- name: str(spec["name"]),
330
- icon: spec["icon"],
331
- description: optStr(spec["description"]),
332
- triggers,
333
- folderUuid: optStr(spec["folderUuid"]),
334
- });
335
- toolUuid = created.tool.uuid;
336
- workflowUuid = created.tool.workflowUuid;
337
- // publicForm isn't a create param — apply it via update when set.
338
- if (publicForm !== null && publicForm !== undefined) {
339
- await api.orchestration.tool.update({ uuid: toolUuid, publicForm });
340
- }
341
- }
419
+ const { toolUuid, workflowUuid } = await upsertTool(api, spec, prior);
342
420
  // Deploy the backing workflow's graph as the tool's release.
343
421
  await api.orchestration.draftRelease.deploy({
344
422
  workflowUuid,
@@ -384,36 +462,7 @@ export function liveExecutors(api) {
384
462
  async worker(spec, prior) {
385
463
  const files = readBundle(str(spec["path"]));
386
464
  const slug = str(spec["slug"]);
387
- const triggers = mapWorkerTriggers(spec["triggers"]);
388
- let uuid;
389
- let adopted = false;
390
- if (prior !== undefined) {
391
- uuid = prior.uuid;
392
- await api.hosting.worker.update({ uuid, triggers });
393
- }
394
- else {
395
- // Recover our own slot if a prior deploy created it but crashed before
396
- // persisting (e.g. a failed build). The slug is CDK-chosen, so a worker
397
- // with the same slug is *probably* ours — reuse it instead of
398
- // conflicting. We can't be certain it isn't a foreign same-slug worker,
399
- // so mark it adopted: destroy will release (not delete) it.
400
- const { workers } = await api.hosting.worker.list();
401
- const existing = workers.find((w) => w.slug === slug);
402
- if (existing !== undefined) {
403
- uuid = existing.uuid;
404
- adopted = true;
405
- await api.hosting.worker.update({ uuid, triggers });
406
- }
407
- else {
408
- uuid = (await api.hosting.worker.create({
409
- name: str(spec["name"]),
410
- slug,
411
- description: optStr(spec["description"]) ?? null,
412
- folderUuid: optStr(spec["folderUuid"]),
413
- triggers,
414
- })).worker.uuid;
415
- }
416
- }
465
+ const { uuid, adopted } = await upsertWorker(api, spec, prior, slug);
417
466
  await syncEnvVars(api, { kind: "worker", workerUuid: uuid }, spec["env"]);
418
467
  const { deployment } = await api.hosting.deployment.create({
419
468
  kind: "worker",
@@ -430,38 +479,14 @@ export function liveExecutors(api) {
430
479
  uuid,
431
480
  url: promoted.url,
432
481
  };
433
- if (adopted)
482
+ if (adopted === true)
434
483
  out["adopted"] = "true";
435
484
  return out;
436
485
  },
437
486
  async app(spec, prior) {
438
487
  const files = readBundle(str(spec["path"]));
439
488
  const slug = str(spec["slug"]);
440
- let uuid;
441
- let adopted = false;
442
- if (prior !== undefined) {
443
- uuid = prior.uuid;
444
- }
445
- else {
446
- // Recover our own slot if a prior deploy created it but crashed before
447
- // persisting — same-slug app is *probably* ours (the slug is
448
- // CDK-chosen). We can't be certain, so mark it adopted: destroy will
449
- // release (not delete) it.
450
- const { apps } = await api.hosting.app.list();
451
- const existing = apps.find((a) => a.slug === slug);
452
- if (existing !== undefined) {
453
- uuid = existing.uuid;
454
- adopted = true;
455
- }
456
- else {
457
- uuid = (await api.hosting.app.create({
458
- name: str(spec["name"]),
459
- slug,
460
- description: optStr(spec["description"]),
461
- folderUuid: optStr(spec["folderUuid"]),
462
- })).app.uuid;
463
- }
464
- }
489
+ const { uuid, adopted } = await resolveAppSlot(api, spec, prior, slug);
465
490
  await syncEnvVars(api, { kind: "app", appUuid: uuid }, spec["env"]);
466
491
  const { deployment } = await api.hosting.deployment.create({
467
492
  kind: "app",
@@ -478,7 +503,7 @@ export function liveExecutors(api) {
478
503
  uuid,
479
504
  url: promoted.url,
480
505
  };
481
- if (adopted)
506
+ if (adopted === true)
482
507
  out["adopted"] = "true";
483
508
  return out;
484
509
  },
@@ -549,13 +574,7 @@ export function liveExecutors(api) {
549
574
  const files = (spec["files"] ?? {});
550
575
  const { repository } = await api.context.repository.get();
551
576
  for (const [path, content] of Object.entries(files)) {
552
- let current;
553
- try {
554
- current = (await api.context.runtime.read({ path })).content;
555
- }
556
- catch {
557
- current = undefined; // new file — read 404s
558
- }
577
+ const current = await readContextFile(api, path);
559
578
  if (current !== content) {
560
579
  await api.context.runtime.write({
561
580
  path,
@@ -586,7 +605,9 @@ export function liveExecutors(api) {
586
605
  await api.revenueOrganization.capacity.update({
587
606
  uuid: prior.uuid,
588
607
  ...base,
589
- filter: (spec["filter"] ?? null),
608
+ filter: (spec["filter"] === undefined
609
+ ? null
610
+ : spec["filter"]),
590
611
  });
591
612
  return carryAdopted({ uuid: prior.uuid }, prior);
592
613
  }
@@ -635,10 +656,14 @@ export function liveExecutors(api) {
635
656
  uuid: prior.uuid,
636
657
  name,
637
658
  filter: spec["filter"],
638
- sort: (spec["sort"] ?? null),
639
- limit: (spec["limit"] ?? null),
640
- trackingColumnSlugs: (spec["trackingColumnSlugs"] ?? null),
641
- columnSlugs: (spec["columnSlugs"] ?? null),
659
+ sort: (spec["sort"] === undefined ? null : spec["sort"]),
660
+ limit: (spec["limit"] === undefined ? null : spec["limit"]),
661
+ trackingColumnSlugs: (spec["trackingColumnSlugs"] === undefined
662
+ ? null
663
+ : spec["trackingColumnSlugs"]),
664
+ columnSlugs: (spec["columnSlugs"] === undefined
665
+ ? null
666
+ : spec["columnSlugs"]),
642
667
  });
643
668
  return carryAdopted({ uuid: prior.uuid }, prior);
644
669
  }
@@ -670,22 +695,25 @@ export function liveExecutors(api) {
670
695
  const fromModelUuid = str(spec["fromModelUuid"]);
671
696
  const { model } = await api.storage.model.get(fromModelUuid);
672
697
  const datasetUuid = model.datasetUuid;
698
+ const fromPropertySlug = optStr(spec["fromPropertySlug"]);
699
+ const toPropertySlug = optStr(spec["toPropertySlug"]);
673
700
  const row = {
674
701
  fromModelUuid,
675
702
  fromColumnSlug: str(spec["fromColumnSlug"]),
676
- fromPropertySlug: optStr(spec["fromPropertySlug"]) ?? null,
703
+ fromPropertySlug: fromPropertySlug === undefined ? null : fromPropertySlug,
677
704
  toModelUuid: str(spec["toModelUuid"]),
678
705
  toColumnSlug: str(spec["toColumnSlug"]),
679
- toPropertySlug: optStr(spec["toPropertySlug"]) ?? null,
706
+ toPropertySlug: toPropertySlug === undefined ? null : toPropertySlug,
680
707
  fromSelectedColumnSlugs: null,
681
708
  toSelectedColumnSlugs: null,
682
709
  relation: spec["relation"],
683
710
  };
684
711
  const { relationships } = await api.storage.relationship.all();
712
+ const priorUuid = prior === undefined ? undefined : prior.uuid;
685
713
  // Every row in this dataset except the one we manage — kept verbatim (with
686
714
  // its uuid) so `set` updates it in place rather than deleting it.
687
715
  const others = relationships
688
- .filter((r) => r.fromDatasetUuid === datasetUuid && r.uuid !== prior?.uuid)
716
+ .filter((r) => r.fromDatasetUuid === datasetUuid && r.uuid !== priorUuid)
689
717
  .map(toSetInput);
690
718
  const ours = prior === undefined ? row : { ...row, uuid: prior.uuid };
691
719
  const { relationships: after } = await api.storage.relationship.set({
@@ -713,16 +741,17 @@ export function liveExecutors(api) {
713
741
  // so `set` updates it in place instead of deleting it. `all()` returns nullable
714
742
  // fields as `undefined` while `set()` expects `null`, so normalize each here.
715
743
  function toSetInput(relationship) {
744
+ const { fromPropertySlug = null, toPropertySlug = null, fromSelectedColumnSlugs = null, toSelectedColumnSlugs = null, } = relationship;
716
745
  return {
717
746
  uuid: relationship.uuid,
718
747
  fromModelUuid: relationship.fromModelUuid,
719
748
  fromColumnSlug: relationship.fromColumnSlug,
720
- fromPropertySlug: relationship.fromPropertySlug ?? null,
749
+ fromPropertySlug,
721
750
  toModelUuid: relationship.toModelUuid,
722
751
  toColumnSlug: relationship.toColumnSlug,
723
- toPropertySlug: relationship.toPropertySlug ?? null,
724
- fromSelectedColumnSlugs: relationship.fromSelectedColumnSlugs ?? null,
725
- toSelectedColumnSlugs: relationship.toSelectedColumnSlugs ?? null,
752
+ toPropertySlug,
753
+ fromSelectedColumnSlugs,
754
+ toSelectedColumnSlugs,
726
755
  relation: relationship.relation,
727
756
  };
728
757
  }
@@ -58,7 +58,7 @@ export async function importResource(root, api, workspaceUuid, id, uuid,
58
58
  // The repo's resources. Defaults to loading them from `root`; injectable so
59
59
  // the import logic is testable without the tsx file loader.
60
60
  nodes) {
61
- const resolved = nodes ?? (await loadResources(root));
61
+ const resolved = nodes !== undefined ? nodes : await loadResources(root);
62
62
  const entry = compile({ nodes: resolved }).plan.find((p) => p.id === id);
63
63
  if (entry === undefined) {
64
64
  throw new Error(`no resource "${id}" found in code — import targets a code resource id like "agent:sdr".`);
@@ -71,7 +71,7 @@ nodes) {
71
71
  const stateEntry = { hash: entry.hash, uuid, outputs };
72
72
  // Confirm it's actually there (and capture a drift baseline).
73
73
  const live = await liveReaders(api)[kind](stateEntry);
74
- if (!live.exists) {
74
+ if (live.exists === false) {
75
75
  throw new Error(`${kind} ${uuid} was not found in the workspace — nothing to import.`);
76
76
  }
77
77
  if (live.fingerprint !== undefined)
@@ -40,7 +40,7 @@ export async function deploy(root, workspaceUuid, executors) {
40
40
  `selected ${workspaceUuid} — refusing to deploy (would orphan resources).`);
41
41
  }
42
42
  const nodes = await loadResources(root);
43
- const state = prior ?? emptyState(workspaceUuid);
43
+ const state = prior !== undefined ? prior : emptyState(workspaceUuid);
44
44
  const result = await apply({ nodes, state }, executors, (next) => writeState(root, next));
45
45
  return {
46
46
  state: result.state,
@@ -1 +1 @@
1
- {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/deploy/lock.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,IAAI,GAAG;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAsC3C;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,UAAQ,GACZ,IAAI,CA6DN"}
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/deploy/lock.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,IAAI,GAAG;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAsC3C;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,UAAQ,GACZ,IAAI,CAgEN"}
@@ -51,11 +51,13 @@ export function acquireLock(root, command, force = false) {
51
51
  command,
52
52
  };
53
53
  const payload = `${JSON.stringify(me, null, 2)}\n`;
54
- let attempts = 0;
55
- for (;;) {
54
+ // Create the lock file, stealing a stale/forced one and retrying per steal;
55
+ // gives up after 6 attempts. A successful write returns; the original loop's
56
+ // `break` maps to this `return`.
57
+ const acquire = (attempts) => {
56
58
  try {
57
59
  writeFileSync(path, payload, { flag: "wx" }); // atomic create-or-fail
58
- break;
60
+ return;
59
61
  }
60
62
  catch (error) {
61
63
  if (error.code !== "EEXIST")
@@ -63,7 +65,7 @@ export function acquireLock(root, command, force = false) {
63
65
  // The lock file exists (EEXIST). Only steal it when we can positively
64
66
  // confirm it's free, or the caller forced it.
65
67
  const existing = readLock(path);
66
- if (!force) {
68
+ if (force === false) {
67
69
  if (existing === undefined) {
68
70
  // Present but unreadable — a live holder mid-write, or a corrupt
69
71
  // file. Fail closed: never steal a lock we can't inspect.
@@ -77,17 +79,18 @@ export function acquireLock(root, command, force = false) {
77
79
  }
78
80
  }
79
81
  rmSync(path, { force: true }); // forced, or readable + confirmed stale
80
- attempts += 1;
81
- if (attempts > 5) {
82
+ if (attempts + 1 > 5) {
82
83
  throw new Error("could not acquire the cargo.state lock");
83
84
  }
85
+ acquire(attempts + 1);
84
86
  }
85
- }
86
- let released = false;
87
+ };
88
+ acquire(0);
89
+ const releaseState = { released: false };
87
90
  const release = () => {
88
- if (released)
91
+ if (releaseState.released)
89
92
  return;
90
- released = true;
93
+ releaseState.released = true;
91
94
  const current = readLock(path);
92
95
  if (current !== undefined &&
93
96
  current.pid === me.pid &&
@@ -1 +1 @@
1
- {"version":3,"file":"readers.live.d.ts","sourceRoot":"","sources":["../../../src/deploy/readers.live.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAoD5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CA2TjD"}
1
+ {"version":3,"file":"readers.live.d.ts","sourceRoot":"","sources":["../../../src/deploy/readers.live.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAoD5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAoUjD"}