alchemy 2.0.0-beta.28 → 2.0.0-beta.29

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 (91) hide show
  1. package/bin/exec.js +98 -6
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/IAM/SAMLProvider.d.ts +2 -1
  4. package/lib/AWS/IAM/SAMLProvider.d.ts.map +1 -1
  5. package/lib/AWS/IAM/SAMLProvider.js +30 -9
  6. package/lib/AWS/IAM/SAMLProvider.js.map +1 -1
  7. package/lib/Apply.d.ts.map +1 -1
  8. package/lib/Apply.js +55 -11
  9. package/lib/Apply.js.map +1 -1
  10. package/lib/Cli/commands/bootstrap.d.ts +1 -1
  11. package/lib/Cli/commands/bootstrap.d.ts.map +1 -1
  12. package/lib/Cli/commands/bootstrap.js +2 -1
  13. package/lib/Cli/commands/bootstrap.js.map +1 -1
  14. package/lib/Cloudflare/Access.d.ts +19 -0
  15. package/lib/Cloudflare/Access.d.ts.map +1 -0
  16. package/lib/Cloudflare/Access.js +64 -0
  17. package/lib/Cloudflare/Access.js.map +1 -0
  18. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +244 -0
  19. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -0
  20. package/lib/Cloudflare/AiGateway/AiGateway.js +269 -0
  21. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -0
  22. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts +103 -0
  23. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts.map +1 -0
  24. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js +99 -0
  25. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js.map +1 -0
  26. package/lib/Cloudflare/AiGateway/index.d.ts +3 -0
  27. package/lib/Cloudflare/AiGateway/index.d.ts.map +1 -0
  28. package/lib/Cloudflare/AiGateway/index.js +3 -0
  29. package/lib/Cloudflare/AiGateway/index.js.map +1 -0
  30. package/lib/Cloudflare/EdgeSession.d.ts +2 -1
  31. package/lib/Cloudflare/EdgeSession.d.ts.map +1 -1
  32. package/lib/Cloudflare/EdgeSession.js +8 -4
  33. package/lib/Cloudflare/EdgeSession.js.map +1 -1
  34. package/lib/Cloudflare/Providers.d.ts +2 -1
  35. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  36. package/lib/Cloudflare/Providers.js +5 -1
  37. package/lib/Cloudflare/Providers.js.map +1 -1
  38. package/lib/Cloudflare/StateStore/Api.d.ts +10 -0
  39. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  40. package/lib/Cloudflare/StateStore/Api.js +12 -1
  41. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  42. package/lib/Cloudflare/StateStore/State.d.ts +4 -3
  43. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  44. package/lib/Cloudflare/StateStore/State.js +103 -13
  45. package/lib/Cloudflare/StateStore/State.js.map +1 -1
  46. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  47. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  48. package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
  49. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  50. package/lib/Cloudflare/Workers/Worker.js +40 -31
  51. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  52. package/lib/Cloudflare/index.d.ts +1 -0
  53. package/lib/Cloudflare/index.d.ts.map +1 -1
  54. package/lib/Cloudflare/index.js +1 -0
  55. package/lib/Cloudflare/index.js.map +1 -1
  56. package/lib/Plan.d.ts +8 -0
  57. package/lib/Plan.d.ts.map +1 -1
  58. package/lib/Plan.js +12 -5
  59. package/lib/Plan.js.map +1 -1
  60. package/lib/State/HttpStateApi.d.ts +19 -1
  61. package/lib/State/HttpStateApi.d.ts.map +1 -1
  62. package/lib/State/HttpStateApi.js +18 -1
  63. package/lib/State/HttpStateApi.js.map +1 -1
  64. package/lib/Util/cached-function.d.ts +38 -0
  65. package/lib/Util/cached-function.d.ts.map +1 -0
  66. package/lib/Util/cached-function.js +53 -0
  67. package/lib/Util/cached-function.js.map +1 -0
  68. package/lib/Util/scc.d.ts +12 -0
  69. package/lib/Util/scc.d.ts.map +1 -0
  70. package/lib/Util/scc.js +81 -0
  71. package/lib/Util/scc.js.map +1 -0
  72. package/lib/tsconfig.test.tsbuildinfo +1 -1
  73. package/package.json +2 -2
  74. package/src/AWS/IAM/SAMLProvider.ts +47 -18
  75. package/src/Apply.ts +72 -8
  76. package/src/Cli/commands/bootstrap.ts +2 -0
  77. package/src/Cloudflare/Access.ts +110 -0
  78. package/src/Cloudflare/AiGateway/AiGateway.ts +526 -0
  79. package/src/Cloudflare/AiGateway/AiGatewayBinding.ts +190 -0
  80. package/src/Cloudflare/AiGateway/index.ts +2 -0
  81. package/src/Cloudflare/EdgeSession.ts +15 -5
  82. package/src/Cloudflare/Providers.ts +7 -0
  83. package/src/Cloudflare/StateStore/Api.ts +18 -0
  84. package/src/Cloudflare/StateStore/State.ts +130 -12
  85. package/src/Cloudflare/Workers/InferEnv.ts +7 -5
  86. package/src/Cloudflare/Workers/Worker.ts +44 -33
  87. package/src/Cloudflare/index.ts +1 -0
  88. package/src/Plan.ts +21 -5
  89. package/src/State/HttpStateApi.ts +22 -1
  90. package/src/Util/cached-function.ts +75 -0
  91. package/src/Util/scc.ts +89 -0
package/bin/exec.js CHANGED
@@ -12,6 +12,7 @@ import * as Logger from "effect/Logger";
12
12
  import * as Option from "effect/Option";
13
13
  import * as Command from "effect/unstable/cli/Command";
14
14
  import * as Flag from "effect/unstable/cli/Flag";
15
+ import * as Cause from "effect/Cause";
15
16
  import * as Deferred from "effect/Deferred";
16
17
  import * as Data from "effect/Data";
17
18
  import { pipe } from "effect/Function";
@@ -24,7 +25,6 @@ import os from "node:os";
24
25
  import * as Duration from "effect/Duration";
25
26
  import * as Exit from "effect/Exit";
26
27
  import * as Metric from "effect/Metric";
27
- import "effect/Cause";
28
28
  import * as Argument from "effect/unstable/cli/Argument";
29
29
  import * as CliError from "effect/unstable/cli/CliError";
30
30
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -645,6 +645,79 @@ const generateInstanceId = () => Effect.sync(() => {
645
645
  //#region lib/Util/types.js
646
646
  const asEffect = (effect) => Effect.isEffect(effect) ? effect : Effect.succeed(effect);
647
647
 
648
+ //#endregion
649
+ //#region lib/Util/scc.js
650
+ /**
651
+ * Compute the set of graph nodes that participate in a dependency cycle.
652
+ *
653
+ * A node is considered "in a cycle" iff it sits in a strongly-connected
654
+ * component (SCC) of size > 1, or has a self-edge (size-1 SCC that loops
655
+ * back to itself).
656
+ *
657
+ * Implementation is iterative Tarjan's algorithm to avoid blowing the JS
658
+ * call stack on very wide graphs.
659
+ */
660
+ const findCycleMembers = (edges) => {
661
+ const cycleMembers = /* @__PURE__ */ new Set();
662
+ let index = 0;
663
+ const indexOf = /* @__PURE__ */ new Map();
664
+ const lowlink = /* @__PURE__ */ new Map();
665
+ const onStack = /* @__PURE__ */ new Set();
666
+ const stack = [];
667
+ const callStack = [];
668
+ const startNode = (node) => {
669
+ indexOf.set(node, index);
670
+ lowlink.set(node, index);
671
+ index += 1;
672
+ stack.push(node);
673
+ onStack.add(node);
674
+ callStack.push({
675
+ node,
676
+ childIdx: 0
677
+ });
678
+ };
679
+ for (const root of Object.keys(edges)) {
680
+ if (indexOf.has(root)) continue;
681
+ startNode(root);
682
+ while (callStack.length > 0) {
683
+ const frame = callStack[callStack.length - 1];
684
+ const children = edges[frame.node] ?? [];
685
+ if (frame.childIdx < children.length) {
686
+ const child = children[frame.childIdx];
687
+ frame.childIdx += 1;
688
+ if (!indexOf.has(child)) {
689
+ startNode(child);
690
+ continue;
691
+ }
692
+ if (onStack.has(child)) lowlink.set(frame.node, Math.min(lowlink.get(frame.node), indexOf.get(child)));
693
+ continue;
694
+ }
695
+ if (lowlink.get(frame.node) === indexOf.get(frame.node)) {
696
+ const scc = [];
697
+ for (;;) {
698
+ const w = stack.pop();
699
+ onStack.delete(w);
700
+ scc.push(w);
701
+ if (w === frame.node) break;
702
+ }
703
+ if (scc.length > 1) for (const fqn of scc) cycleMembers.add(fqn);
704
+ else {
705
+ const only = scc[0];
706
+ if ((edges[only] ?? []).includes(only)) cycleMembers.add(only);
707
+ }
708
+ }
709
+ callStack.pop();
710
+ const parent = callStack[callStack.length - 1];
711
+ if (parent) {
712
+ const cur = lowlink.get(parent.node);
713
+ const childLow = lowlink.get(frame.node);
714
+ if (childLow < cur) lowlink.set(parent.node, childLow);
715
+ }
716
+ }
717
+ }
718
+ return cycleMembers;
719
+ };
720
+
648
721
  //#endregion
649
722
  //#region lib/Plan.js
650
723
  const make = (stack, options = {}) => Effect.gen(function* () {
@@ -875,6 +948,7 @@ const make = (stack, options = {}) => Effect.gen(function* () {
875
948
  state: oldState
876
949
  });
877
950
  })), { concurrency: "unbounded" })).map((update) => [update.resource.FQN, update]));
951
+ const cycleMembers = findCycleMembers(allUpstreamDependencies);
878
952
  {
879
953
  const createReplaceNodes = new Set(Object.entries(resourceGraph).filter(([_, node]) => node.action === "create" || node.action === "replace").map(([fqn]) => fqn));
880
954
  if (createReplaceNodes.size > 0) {
@@ -965,7 +1039,8 @@ const make = (stack, options = {}) => Effect.gen(function* () {
965
1039
  return {
966
1040
  resources: resourceGraph,
967
1041
  deletions,
968
- output: stack.output
1042
+ output: stack.output,
1043
+ cycleMembers
969
1044
  };
970
1045
  }).pipe(ensureArtifactStore, Effect.withSpan("plan.make", { attributes: {
971
1046
  "alchemy.stack": stack.name,
@@ -1093,9 +1168,11 @@ const executePlan = Effect.fnUntraced(function* (plan, tracker, terminalStatuses
1093
1168
  const ready = Object.fromEntries(yield* Effect.all(Object.keys(plan.resources).map((fqn) => Effect.map(Deferred.make(), (d) => [fqn, d]))));
1094
1169
  const getOutputs = () => Object.fromEntries(Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]));
1095
1170
  const waitForDeps = (fqns) => Effect.all(fqns.filter((fqn) => fqn in ready).map((fqn) => Deferred.await(ready[fqn])), { concurrency: "unbounded" });
1096
- yield* Effect.all(Object.entries(plan.resources).map(([fqn, node]) => executeNode(fqn, node, tracker, ready, terminalStatuses, session, state, stackName, stage$1, getOutputs, waitForDeps)), { concurrency: "unbounded" });
1171
+ const failures = [];
1172
+ yield* Effect.all(Object.entries(plan.resources).map(([fqn, node]) => executeNode(fqn, node, tracker, ready, terminalStatuses, session, state, stackName, stage$1, getOutputs, waitForDeps, failures, plan.cycleMembers.has(fqn))), { concurrency: "unbounded" });
1173
+ if (failures.length > 0) yield* Effect.failCause(failures.map((f) => f.cause).reduce(Cause.combine));
1097
1174
  });
1098
- const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state, stackName, stage$1, getOutputs, waitForDeps) => Effect.gen(function* () {
1175
+ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state, stackName, stage$1, getOutputs, waitForDeps, failures, inCycle) => Effect.gen(function* () {
1099
1176
  const logicalId = node.resource.LogicalId;
1100
1177
  const namespace = node.resource.Namespace;
1101
1178
  const commit = (value) => state.set({
@@ -1272,7 +1349,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
1272
1349
  return;
1273
1350
  }
1274
1351
  if (node.action === "update") {
1275
- yield* storeAndSignal({
1352
+ if (inCycle) yield* storeAndSignal({
1276
1353
  output: node.state.attr,
1277
1354
  props: node.state.props,
1278
1355
  bindings: node.state.bindings ?? [],
@@ -1335,6 +1412,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
1335
1412
  bindings: bindingOutputs,
1336
1413
  instanceId
1337
1414
  };
1415
+ yield* signalReady;
1338
1416
  yield* markTerminal("updated");
1339
1417
  return;
1340
1418
  }
@@ -1445,7 +1523,21 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
1445
1523
  }
1446
1524
  return yield* Effect.die(`Unknown action: ${node.action}`);
1447
1525
  });
1448
- }).pipe(Effect.withSpan("apply.resource", { attributes: {
1526
+ }).pipe(Effect.catchCause((cause) => Effect.gen(function* () {
1527
+ failures.push({
1528
+ fqn,
1529
+ logicalId: node.resource.LogicalId,
1530
+ type: node.resource.Type,
1531
+ cause
1532
+ });
1533
+ yield* Deferred.failCause(ready[fqn], cause);
1534
+ yield* session.emit({
1535
+ kind: "status-change",
1536
+ id: node.resource.LogicalId,
1537
+ type: node.resource.Type,
1538
+ status: "fail"
1539
+ });
1540
+ })), Effect.withSpan("apply.resource", { attributes: {
1449
1541
  "alchemy.resource.fqn": fqn,
1450
1542
  "alchemy.resource.type": node.resource.Type,
1451
1543
  "alchemy.resource.logical_id": node.resource.LogicalId,