alchemy-effect 0.6.3 → 0.7.0

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.
@@ -69,7 +69,72 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
69
69
 
70
70
  //#endregion
71
71
  //#region package.json
72
- var version = "0.6.3";
72
+ var version = "0.7.0";
73
+
74
+ //#endregion
75
+ //#region src/Artifacts.ts
76
+ /**
77
+ * Per-resource in-memory artifacts shared across a single `Plan.make -> apply`
78
+ * execution.
79
+ *
80
+ * The engine scopes this service by resource `FQN` before invoking lifecycle
81
+ * handlers, so providers should treat it as a resource-local bag for expensive,
82
+ * deterministic intermediate results that can be reused across phases.
83
+ *
84
+ * Expected usage:
85
+ *
86
+ * - `diff` computes an expensive artifact once and stores it
87
+ * - `create` / `update` reads the same artifact and skips recomputing it
88
+ * - artifacts are ephemeral and must never be required for correctness on a
89
+ * later deploy because the bag is reset between runs
90
+ *
91
+ * Example:
92
+ *
93
+ * ```ts
94
+ * const artifacts = yield* Artifacts;
95
+ * const cached = yield* artifacts.get<PreparedBundle>("bundle");
96
+ * if (cached) return cached;
97
+ *
98
+ * const bundle = yield* prepareBundle();
99
+ * yield* artifacts.set("bundle", bundle);
100
+ * return bundle;
101
+ * ```
102
+ */
103
+ var Artifacts = class extends ServiceMap.Service()("Artifacts") {};
104
+ var ArtifactStore = class extends ServiceMap.Service()("Artifacts/Store") {};
105
+ /**
106
+ * Create a fresh root store for one deploy/test run.
107
+ */
108
+ const createArtifactStore = () => /* @__PURE__ */ new Map();
109
+ const getOrCreateBag = (store, fqn) => {
110
+ const existing = store.get(fqn);
111
+ if (existing) return existing;
112
+ const bag = /* @__PURE__ */ new Map();
113
+ store.set(fqn, bag);
114
+ return bag;
115
+ };
116
+ const makeScopedArtifacts = (store, fqn) => {
117
+ const bag = getOrCreateBag(store, fqn);
118
+ return {
119
+ get: (key) => Effect.sync(() => bag.get(key)),
120
+ set: (key, value) => Effect.sync(() => {
121
+ bag.set(key, value);
122
+ }),
123
+ delete: (key) => Effect.sync(() => {
124
+ bag.delete(key);
125
+ })
126
+ };
127
+ };
128
+ /**
129
+ * Run an effect with a fresh artifact root, replacing any existing store.
130
+ * Use this at top-level entrypoints that intentionally define a new deploy run.
131
+ */
132
+ const provideFreshArtifactStore = (effect) => effect.pipe(Effect.provideServiceEffect(ArtifactStore, Effect.sync(createArtifactStore)));
133
+ /**
134
+ * Ensure an artifact root exists, reusing the ambient store when one is already
135
+ * present. This lets nested helpers participate in the same run-scoped cache.
136
+ */
137
+ const ensureArtifactStore = (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrUndefined), Effect.flatMap((existing) => effect.pipe(Effect.provideService(ArtifactStore, existing ?? createArtifactStore()))));
73
138
 
74
139
  //#endregion
75
140
  //#region src/Cli/Cli.ts
@@ -210,6 +275,9 @@ const ExprSymbol = Symbol.for("alchemy/Expr");
210
275
  const isExpr = (value) => value && (typeof value === "object" || typeof value === "function") && ExprSymbol in value;
211
276
  var BaseExpr = class {
212
277
  constructor() {}
278
+ as() {
279
+ return this;
280
+ }
213
281
  [Symbol.iterator]() {
214
282
  return new SingleShotGen(this);
215
283
  }
@@ -492,7 +560,8 @@ const LocalState = Layer.effect(State, Effect.gen(function* () {
492
560
 
493
561
  //#endregion
494
562
  //#region src/Apply.ts
495
- const apply = (plan) => Effect.gen(function* () {
563
+ const provideLifecycleScope = (fqn, instanceId) => (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrElse(createArtifactStore)), Effect.flatMap((store) => effect.pipe(Effect.provideService(Artifacts, makeScopedArtifacts(store, fqn)), Effect.provideService(InstanceId, instanceId))));
564
+ const apply = (plan) => ensureArtifactStore(Effect.gen(function* () {
496
565
  const session = yield* (yield* Cli).startApplySession(plan);
497
566
  const state = yield* State;
498
567
  const stack = yield* Stack;
@@ -513,7 +582,7 @@ const apply = (plan) => Effect.gen(function* () {
513
582
  if (!plan.output) return;
514
583
  const outputs = Object.fromEntries(Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]));
515
584
  return yield* evaluate(plan.output, outputs);
516
- });
585
+ }));
517
586
  const executePlan = Effect.fnUntraced(function* (plan, tracker, terminalStatuses, session, state, stackName, stage$1) {
518
587
  const ready = Object.fromEntries(yield* Effect.all(Object.keys(plan.resources).map((fqn) => Effect.map(Deferred.make(), (d) => [fqn, d]))));
519
588
  const getOutputs = () => Object.fromEntries(Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]));
@@ -638,7 +707,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
638
707
  news: node.props,
639
708
  session: scopedSession,
640
709
  instanceId
641
- });
710
+ }).pipe(provideLifecycleScope(fqn, instanceId));
642
711
  yield* commit({
643
712
  status: "creating",
644
713
  fqn,
@@ -671,7 +740,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
671
740
  bindings: bindingOutputs,
672
741
  session: scopedSession,
673
742
  output: attr
674
- });
743
+ }).pipe(provideLifecycleScope(fqn, instanceId));
675
744
  yield* commit({
676
745
  status: "created",
677
746
  fqn,
@@ -734,7 +803,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
734
803
  session: scopedSession,
735
804
  olds: previousProps,
736
805
  output: node.state.attr
737
- });
806
+ }).pipe(provideLifecycleScope(fqn, instanceId));
738
807
  if (node.state.status === "replaced") yield* commit({
739
808
  ...node.state,
740
809
  attr,
@@ -805,7 +874,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
805
874
  news: node.props,
806
875
  session: scopedSession,
807
876
  instanceId
808
- });
877
+ }).pipe(provideLifecycleScope(fqn, instanceId));
809
878
  yield* commit({
810
879
  status: "replacing",
811
880
  fqn,
@@ -840,7 +909,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
840
909
  bindings: bindingOutputs,
841
910
  session: scopedSession,
842
911
  output: attr
843
- });
912
+ }).pipe(provideLifecycleScope(fqn, instanceId));
844
913
  yield* commit({
845
914
  status: "replaced",
846
915
  fqn,
@@ -867,7 +936,7 @@ const executeNode = (fqn, node, tracker, ready, terminalStatuses, session, state
867
936
  return;
868
937
  }
869
938
  return yield* Effect.die(`Unknown action: ${node.action}`);
870
- }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId)));
939
+ });
871
940
  });
872
941
  const converge = Effect.fnUntraced(function* (plan, tracker, terminalStatuses, session, state, stackName, stage$1) {
873
942
  for (;;) {
@@ -903,7 +972,7 @@ const converge = Effect.fnUntraced(function* (plan, tracker, terminalStatuses, s
903
972
  session: scopedSession,
904
973
  olds: oldProps,
905
974
  output: tracker[fqn].output
906
- }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId)));
975
+ }).pipe(provideLifecycleScope(fqn, instanceId));
907
976
  tracker[fqn] = {
908
977
  output: attr,
909
978
  props: newProps,
@@ -1024,7 +1093,7 @@ const collectGarbage = Effect.fnUntraced(function* (plan, session) {
1024
1093
  output: attr,
1025
1094
  session: scopedSession,
1026
1095
  bindings: []
1027
- });
1096
+ }).pipe(provideLifecycleScope(fqn, instanceId));
1028
1097
  if (isDeleteNode(node)) {
1029
1098
  yield* state.delete({
1030
1099
  stack: stackName,
@@ -1064,7 +1133,7 @@ const collectGarbage = Effect.fnUntraced(function* (plan, session) {
1064
1133
  });
1065
1134
  yield* scopedSession.note("Replaced resource cleanup complete.");
1066
1135
  }
1067
- }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId))));
1136
+ }));
1068
1137
  });
1069
1138
  yield* Effect.all(Object.values(deletionGraph).filter((node) => node !== void 0).map((node) => deleteResource(node)), { concurrency: "unbounded" });
1070
1139
  });
@@ -44249,7 +44318,7 @@ const asEffect = (effect) => Effect.isEffect(effect) ? effect : Effect.succeed(e
44249
44318
 
44250
44319
  //#endregion
44251
44320
  //#region src/Plan.ts
44252
- const make = (stack) => Effect.gen(function* () {
44321
+ const make = (stack) => ensureArtifactStore(Effect.gen(function* () {
44253
44322
  const state = yield* State;
44254
44323
  const resources = Object.values(stack.resources);
44255
44324
  const stackName = stack.name;
@@ -44286,7 +44355,7 @@ const make = (stack) => Effect.gen(function* () {
44286
44355
  output: oldState.attr,
44287
44356
  oldBindings,
44288
44357
  newBindings
44289
- }).pipe(Effect.provideService(InstanceId, oldState.instanceId)) : Effect.succeed(void 0);
44358
+ }).pipe(providePlanScope(resource.FQN, oldState.instanceId)) : Effect.succeed(void 0);
44290
44359
  const stables = [...provider.stables ?? [], ...diff$1?.stables ?? []];
44291
44360
  const withStables = (output) => stables.length > 0 ? new ResourceExpr(resourceExpr.src, Object.fromEntries(stables.map((stable) => [stable, output?.[stable]]))) : resourceExpr;
44292
44361
  if (diff$1 == null) {
@@ -44366,7 +44435,7 @@ const make = (stack) => Effect.gen(function* () {
44366
44435
  instanceId: oldState.instanceId,
44367
44436
  olds: oldState.props,
44368
44437
  output: oldState.attr
44369
- }).pipe(Effect.provideService(InstanceId, oldState.instanceId));
44438
+ }).pipe(providePlanScope(fqn, oldState.instanceId));
44370
44439
  if (attr) return Node$1({
44371
44440
  action: "create",
44372
44441
  props: news,
@@ -44386,7 +44455,7 @@ const make = (stack) => Effect.gen(function* () {
44386
44455
  news,
44387
44456
  oldBindings,
44388
44457
  newBindings
44389
- }).pipe(Effect.provideService(InstanceId, oldState.instanceId))).pipe(Effect.map((diff$2) => diff$2 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }));
44458
+ }).pipe(providePlanScope(fqn, oldState.instanceId))).pipe(Effect.map((diff$2) => diff$2 ?? { action: havePropsChanged(oldProps, news) || bindingDiffs.some((b) => b.action !== "noop") ? "update" : "noop" }));
44390
44459
  if (oldState.status === "creating") if (diff$1.action === "noop") return Node$1({
44391
44460
  action: "create",
44392
44461
  props: news,
@@ -44524,7 +44593,7 @@ const make = (stack) => Effect.gen(function* () {
44524
44593
  instanceId: oldState.instanceId,
44525
44594
  olds: oldState.props,
44526
44595
  output: oldState.attr
44527
- }).pipe(Effect.provideService(InstanceId, oldState.instanceId));
44596
+ }).pipe(providePlanScope(fqn, oldState.instanceId));
44528
44597
  }
44529
44598
  return [fqn, {
44530
44599
  action: "delete",
@@ -44567,7 +44636,8 @@ const make = (stack) => Effect.gen(function* () {
44567
44636
  deletions,
44568
44637
  output: stack.output
44569
44638
  };
44570
- });
44639
+ }));
44640
+ const providePlanScope = (fqn, instanceId) => (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrElse(createArtifactStore)), Effect.flatMap((store) => effect.pipe(Effect.provideService(Artifacts, makeScopedArtifacts(store, fqn)), Effect.provideService(InstanceId, instanceId))));
44571
44641
  var DeleteResourceHasDownstreamDependencies = class extends Data.TaggedError("DeleteResourceHasDownstreamDependencies") {};
44572
44642
  var UnsatisfiedResourceCycle = class extends Data.TaggedError("UnsatisfiedResourceCycle") {};
44573
44643
 
@@ -44634,7 +44704,7 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
44634
44704
  yield* Effect.gen(function* () {
44635
44705
  const cli = yield* Cli;
44636
44706
  const stack = yield* stackEffect;
44637
- yield* Effect.gen(function* () {
44707
+ yield* provideFreshArtifactStore(Effect.gen(function* () {
44638
44708
  const updatePlan = yield* make(destroy ? {
44639
44709
  ...stack,
44640
44710
  resources: {},
@@ -44649,7 +44719,7 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
44649
44719
  const outputs = yield* apply(updatePlan);
44650
44720
  yield* Console.log(outputs);
44651
44721
  }
44652
- }).pipe(Effect.provide(stack.services));
44722
+ })).pipe(Effect.provide(stack.services));
44653
44723
  }).pipe(Effect.provide(Layer.provideMerge(alchemy, Layer.mergeAll(platform$1, Layer.succeed(Stage, stage$1)))), Effect.provideService(ConfigProvider.ConfigProvider, configProvider));
44654
44724
  });
44655
44725
  const resourceFilter = Flag.string("filter").pipe(Flag.withDescription("Comma-separated logical resource IDs (e.g. Api,Sandbox). Only those resources are included."), Flag.optional, Flag.map(Option.getOrUndefined));