alchemy 2.0.0-beta.68 → 2.0.0-beta.69

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 (50) hide show
  1. package/bin/exec.js +124 -16
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Apply.d.ts.map +1 -1
  4. package/lib/Apply.js +33 -0
  5. package/lib/Apply.js.map +1 -1
  6. package/lib/Cli/LoggingCli.d.ts.map +1 -1
  7. package/lib/Cli/LoggingCli.js +9 -1
  8. package/lib/Cli/LoggingCli.js.map +1 -1
  9. package/lib/Cloudflare/Access/Group.d.ts +11 -2
  10. package/lib/Cloudflare/Access/Group.d.ts.map +1 -1
  11. package/lib/Cloudflare/Access/Group.js.map +1 -1
  12. package/lib/Cloudflare/Access/Policy.d.ts +11 -2
  13. package/lib/Cloudflare/Access/Policy.d.ts.map +1 -1
  14. package/lib/Cloudflare/Access/Policy.js.map +1 -1
  15. package/lib/Cloudflare/Account/Member.d.ts +1 -1
  16. package/lib/Cloudflare/Account/Member.d.ts.map +1 -1
  17. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  18. package/lib/Cloudflare/Website/StaticSite.js +8 -1
  19. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  20. package/lib/Plan.d.ts +23 -4
  21. package/lib/Plan.d.ts.map +1 -1
  22. package/lib/Plan.js +226 -22
  23. package/lib/Plan.js.map +1 -1
  24. package/lib/Rename.d.ts +132 -0
  25. package/lib/Rename.d.ts.map +1 -0
  26. package/lib/Rename.js +117 -0
  27. package/lib/Rename.js.map +1 -0
  28. package/lib/Resource.d.ts +9 -0
  29. package/lib/Resource.d.ts.map +1 -1
  30. package/lib/Resource.js +11 -0
  31. package/lib/Resource.js.map +1 -1
  32. package/lib/State/HttpStateApi.d.ts +14 -14
  33. package/lib/Sync.d.ts.map +1 -1
  34. package/lib/Sync.js +1 -0
  35. package/lib/Sync.js.map +1 -1
  36. package/lib/index.d.ts +1 -0
  37. package/lib/index.d.ts.map +1 -1
  38. package/lib/index.js +1 -0
  39. package/lib/index.js.map +1 -1
  40. package/package.json +4 -4
  41. package/src/Apply.ts +47 -0
  42. package/src/Cli/LoggingCli.ts +10 -1
  43. package/src/Cloudflare/Access/Group.ts +16 -2
  44. package/src/Cloudflare/Access/Policy.ts +16 -2
  45. package/src/Cloudflare/Website/StaticSite.ts +8 -1
  46. package/src/Plan.ts +284 -28
  47. package/src/Rename.ts +135 -0
  48. package/src/Resource.ts +27 -0
  49. package/src/Sync.ts +1 -0
  50. package/src/index.ts +1 -0
package/bin/exec.js CHANGED
@@ -1610,6 +1610,101 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
1610
1610
  stage: stage$1,
1611
1611
  fqn
1612
1612
  })), { concurrency: "unbounded" });
1613
+ const persistedRows = new Map(resourceFqns.map((fqn, i) => [fqn, oldResources[i]]));
1614
+ const formerFqnClaims = /* @__PURE__ */ new Map();
1615
+ for (const resource of resources) for (const formerFqn of resource.FormerFqns ?? []) {
1616
+ if (formerFqn === resource.FQN) continue;
1617
+ const claimant = formerFqnClaims.get(formerFqn);
1618
+ if (claimant !== void 0 && claimant !== resource.FQN) return yield* Effect.die(/* @__PURE__ */ new Error(`Resources '${claimant}' and '${resource.FQN}' both claim former FQN '${formerFqn}' via renamedFrom(...). A former FQN can migrate to exactly one resource — remove the decoration from one of them.`));
1619
+ formerFqnClaims.set(formerFqn, resource.FQN);
1620
+ }
1621
+ const renameMigrations = /* @__PURE__ */ new Map();
1622
+ const migratedRowFqns = /* @__PURE__ */ new Set();
1623
+ const resolveRenamer = Effect.fn(function* (resource) {
1624
+ const provider = Option.getOrUndefined(yield* tryFindProviderByType(resource.Type));
1625
+ const allowedTypes = new Set([resource.Type, ...provider?.aliases ?? []]);
1626
+ const persisted = persistedRows.get(resource.FQN);
1627
+ const persistedRow = isActionState(persisted) ? void 0 : persisted;
1628
+ const rowTaken = migratedRowFqns.has(resource.FQN);
1629
+ const ownRow = !rowTaken && persistedRow !== void 0 && allowedTypes.has(persistedRow.resourceType) ? persistedRow : void 0;
1630
+ let source = ownRow;
1631
+ let adopted = ownRow;
1632
+ const renamedFrom = [];
1633
+ for (const formerFqn of resource.FormerFqns) {
1634
+ if (formerFqnClaims.get(formerFqn) !== resource.FQN) continue;
1635
+ if (renamedFrom.includes(formerFqn)) continue;
1636
+ const formerPersisted = persistedRows.get(formerFqn);
1637
+ if (formerPersisted === void 0 || isActionState(formerPersisted)) continue;
1638
+ const formerRow = formerPersisted;
1639
+ if (!allowedTypes.has(formerRow.resourceType)) continue;
1640
+ if (source === void 0) {
1641
+ source = formerRow;
1642
+ adopted = {
1643
+ ...formerRow,
1644
+ fqn: resource.FQN,
1645
+ logicalId: resource.LogicalId,
1646
+ namespace: resource.Namespace
1647
+ };
1648
+ renamedFrom.push(formerFqn);
1649
+ } else if (source.instanceId === formerRow.instanceId) renamedFrom.push(formerFqn);
1650
+ }
1651
+ if (renamedFrom.length === 0) return;
1652
+ if (persistedRow !== void 0 && ownRow === void 0 && !rowTaken) return yield* Effect.die(/* @__PURE__ */ new Error(`Cannot migrate '${renamedFrom[0]}' to '${resource.FQN}': a state row of a different type ('${persistedRow.resourceType}') already occupies '${resource.FQN}'. Migrating over it would silently abandon that row's cloud resource. Delete or rename the conflicting resource first, then re-deploy.`));
1653
+ renameMigrations.set(resource.FQN, {
1654
+ row: adopted,
1655
+ renamedFrom,
1656
+ moved: adopted !== ownRow
1657
+ });
1658
+ for (const formerFqn of renamedFrom) migratedRowFqns.add(formerFqn);
1659
+ });
1660
+ let pendingRenamers = resources.filter((r) => r.FormerFqns?.length);
1661
+ const resolvedRenamers = /* @__PURE__ */ new Set();
1662
+ while (pendingRenamers.length > 0) {
1663
+ const ready = pendingRenamers.filter((resource) => {
1664
+ const claimant = formerFqnClaims.get(resource.FQN);
1665
+ return claimant === void 0 || resolvedRenamers.has(claimant);
1666
+ });
1667
+ if (ready.length === 0) return yield* Effect.die(/* @__PURE__ */ new Error(`Rename cycle detected among [${pendingRenamers.map((r) => `'${r.FQN}'`).join(", ")}]: their renamedFrom(...) declarations claim each other's FQNs. Swapping ids in one deploy is not supported — rename through a temporary id across two deploys instead.`));
1668
+ for (const resource of ready) {
1669
+ yield* resolveRenamer(resource);
1670
+ resolvedRenamers.add(resource.FQN);
1671
+ }
1672
+ pendingRenamers = pendingRenamers.filter((r) => !resolvedRenamers.has(r.FQN));
1673
+ }
1674
+ /**
1675
+ * Fetch the persisted row for a declared resource, resolving renames
1676
+ * (see the pre-computed `renameMigrations` above):
1677
+ *
1678
+ * - a renamer plans from its migrated row (`renamedFrom` rides onto
1679
+ * the plan node; apply persists the move before any lifecycle op)
1680
+ * - a resource declared at a former FQN whose row is migrating away
1681
+ * starts from scratch — the row is NOT its state, whatever the FQN
1682
+ * says
1683
+ */
1684
+ const getPersistedRow = Effect.fn(function* (resource) {
1685
+ const migration = renameMigrations.get(resource.FQN);
1686
+ if (migration !== void 0) return {
1687
+ row: migration.row,
1688
+ renamedFrom: migration.renamedFrom,
1689
+ renameMoved: migration.moved
1690
+ };
1691
+ const persisted = yield* state.get({
1692
+ stack: stackName,
1693
+ stage: stage$1,
1694
+ fqn: resource.FQN
1695
+ });
1696
+ const row = isActionState(persisted) ? void 0 : persisted;
1697
+ if (row !== void 0 && migratedRowFqns.has(resource.FQN)) return {
1698
+ row: void 0,
1699
+ renamedFrom: void 0,
1700
+ renameMoved: false
1701
+ };
1702
+ return {
1703
+ row,
1704
+ renamedFrom: void 0,
1705
+ renameMoved: false
1706
+ };
1707
+ });
1613
1708
  const resolvedResources = {};
1614
1709
  const resolveResource = (resourceExpr) => Effect.gen(function* () {
1615
1710
  if (isAction(resourceExpr.src)) return resourceExpr;
@@ -1617,12 +1712,7 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
1617
1712
  const resource = resourceExpr.src;
1618
1713
  const { provider, mode } = yield* resolveProviderAndMode(resource);
1619
1714
  const props = materializeStableRefs(yield* resolveInput(resource.Props));
1620
- const persisted = yield* state.get({
1621
- stack: stackName,
1622
- stage: stage$1,
1623
- fqn: resource.FQN
1624
- });
1625
- const oldState = isActionState(persisted) ? void 0 : persisted;
1715
+ const { row: oldState } = yield* getPersistedRow(resource);
1626
1716
  if (!oldState || oldState.status === "creating") return resourceExpr;
1627
1717
  if (hasModeSwitched(mode, oldState)) return resourceExpr;
1628
1718
  const oldProps = oldState.status === "updating" ? oldState.old.props : oldState.props;
@@ -1789,14 +1879,11 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
1789
1879
  const downstream = newDownstreamDependencies[fqn] ?? [];
1790
1880
  const applyBindings = dedupeBindings(yield* resolveInput(stack.bindings[fqn] ?? []));
1791
1881
  const newBindings = materializeStableRefs(applyBindings);
1792
- const persisted = yield* state.get({
1793
- stack: stackName,
1794
- stage: stage$1,
1795
- fqn
1796
- });
1797
- let oldState = isActionState(persisted) ? void 0 : persisted;
1882
+ const { row: persistedRow, renamedFrom, renameMoved } = yield* getPersistedRow(resource);
1883
+ let oldState = persistedRow;
1884
+ const reusesMigratedFqn = migratedRowFqns.has(fqn);
1798
1885
  let forceUpdateAfterAdoption = false;
1799
- if (oldState === void 0 && provider.read && isResolved(news)) {
1886
+ if (oldState === void 0 && provider.read && isResolved(news) && !reusesMigratedFqn) {
1800
1887
  const adoptInstanceId = yield* generateInstanceId();
1801
1888
  const readResult = yield* provider.read({
1802
1889
  id,
@@ -1844,7 +1931,8 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
1844
1931
  resource,
1845
1932
  bindings: bindingDiffs,
1846
1933
  downstream,
1847
- mode
1934
+ mode,
1935
+ renamedFrom
1848
1936
  });
1849
1937
  if (oldState === void 0) return Node({
1850
1938
  action: "create",
@@ -1888,7 +1976,7 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
1888
1976
  news,
1889
1977
  oldBindings,
1890
1978
  newBindings
1891
- }).pipe(providePlanScope(fqn, oldState.instanceId))).pipe(Effect.map((diff$1) => diff$1 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }), Effect.map((diff$1) => options$7.force && diff$1.action === "noop" ? { action: "update" } : diff$1), Effect.map((diff$1) => forceUpdateAfterAdoption && diff$1.action === "noop" ? { action: "update" } : diff$1));
1979
+ }).pipe(providePlanScope(fqn, oldState.instanceId))).pipe(Effect.map((diff$1) => diff$1 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }), Effect.map((diff$1) => options$7.force && diff$1.action === "noop" ? { action: "update" } : diff$1), Effect.map((diff$1) => (forceUpdateAfterAdoption || renameMoved) && diff$1.action === "noop" ? { action: "update" } : diff$1));
1892
1980
  if (oldState.status === "creating") if (diff.action === "noop") return Node({
1893
1981
  action: "create",
1894
1982
  props: applyProps,
@@ -2086,6 +2174,7 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
2086
2174
  if (isActionState(persisted)) return;
2087
2175
  const oldState = persisted;
2088
2176
  if (oldState) {
2177
+ if (migratedRowFqns.has(fqn)) return;
2089
2178
  const { logicalId } = parseFqn(fqn);
2090
2179
  const resourceType = oldState.resourceType;
2091
2180
  const providerOption = yield* tryFindProviderByType(resourceType, oldState.providerMode);
@@ -2107,6 +2196,7 @@ const make = (stack, options$7 = {}) => Effect.gen(function* () {
2107
2196
  RemovalPolicy: oldState.removalPolicy,
2108
2197
  Adopt: void 0,
2109
2198
  Mode: oldState.providerMode,
2199
+ FormerFqns: void 0,
2110
2200
  RuntimeContext: void 0,
2111
2201
  Providers: void 0
2112
2202
  },
@@ -2208,6 +2298,23 @@ const apply = (plan) => Effect.gen(function* () {
2208
2298
  const stackName = stack.name;
2209
2299
  const tracker = {};
2210
2300
  const terminalStatuses = /* @__PURE__ */ new Map();
2301
+ const migrationTargets = new Set(Object.values(plan.resources).filter((node) => node.renamedFrom?.length && node.state !== void 0).map((node) => node.resource.FQN));
2302
+ yield* Effect.forEach(Object.values(plan.resources), (node) => {
2303
+ const { renamedFrom, state: row } = node;
2304
+ return renamedFrom === void 0 || renamedFrom.length === 0 || row === void 0 ? Effect.void : Effect.gen(function* () {
2305
+ yield* state.set({
2306
+ stack: stackName,
2307
+ stage: stage$1,
2308
+ fqn: node.resource.FQN,
2309
+ value: row
2310
+ });
2311
+ yield* Effect.forEach(renamedFrom.filter((formerFqn) => !migrationTargets.has(formerFqn)), (formerFqn) => state.delete({
2312
+ stack: stackName,
2313
+ stage: stage$1,
2314
+ fqn: formerFqn
2315
+ }), { concurrency: "unbounded" });
2316
+ });
2317
+ }, { concurrency: "unbounded" });
2211
2318
  yield* executePlan(plan, tracker, terminalStatuses, session, state, stackName, stage$1);
2212
2319
  yield* collectGarbage(plan, session);
2213
2320
  yield* converge(plan, tracker, terminalStatuses, session, state, stackName, stage$1);
@@ -5523,7 +5630,8 @@ const formatPlanLines = (plan) => {
5523
5630
  priorMode: item.action === "replace" ? item.state.providerMode : void 0,
5524
5631
  defaultMode: plan.defaultMode
5525
5632
  });
5526
- lines.push(`${tag(item.resource.LogicalId)} ${action}${mode}`);
5633
+ const renamed = item.action !== "delete" && item.renamedFrom?.length ? ` ${dim(`(renamed from ${item.renamedFrom.join(", ")})`)}` : "";
5634
+ lines.push(`${tag(item.resource.LogicalId)} ${action}${mode}${renamed}`);
5527
5635
  for (const binding of item.bindings) {
5528
5636
  if (binding.action === "noop") continue;
5529
5637
  const bindingAction = actionColor$1[binding.action](binding.action);