alchemy-effect 0.6.4 → 0.7.1
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.
- package/bin/alchemy-effect.js +96 -24
- package/bin/alchemy-effect.js.map +1 -1
- package/bin/alchemy-effect.ts +36 -24
- package/lib/cli/index.d.ts +1 -0
- package/lib/cli/index.d.ts.map +1 -1
- package/package.json +13 -3
- package/src/AWS/Website/StaticSite.ts +5 -7
- package/src/AWS/Website/shared.ts +15 -3
- package/src/Apply.ts +143 -107
- package/src/Artifacts.ts +147 -0
- package/src/Build/Command.ts +21 -99
- package/src/Build/Memo.ts +187 -0
- package/src/Bundle/Bundle.ts +23 -17
- package/src/Cloudflare/Website/Vite.ts +65 -0
- package/src/Cloudflare/Website/index.ts +1 -1
- package/src/Cloudflare/Workers/Assets.ts +7 -2
- package/src/Cloudflare/Workers/Worker.ts +189 -59
- package/src/Destroy.ts +3 -2
- package/src/Output.ts +4 -0
- package/src/Plan.ts +618 -585
- package/src/Stack.ts +18 -6
- package/src/Test/Vitest.ts +35 -13
- package/src/Util/gitignore-rules-to-globs.ts +80 -0
- package/src/Cloudflare/Website/TanstackStart.ts +0 -14
package/bin/alchemy-effect.js
CHANGED
|
@@ -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.
|
|
72
|
+
var version = "0.7.1";
|
|
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
|
|
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
|
-
})
|
|
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(
|
|
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
|
-
})
|
|
1136
|
+
}));
|
|
1068
1137
|
});
|
|
1069
1138
|
yield* Effect.all(Object.values(deletionGraph).filter((node) => node !== void 0).map((node) => deleteResource(node)), { concurrency: "unbounded" });
|
|
1070
1139
|
});
|
|
@@ -41369,8 +41438,8 @@ cliCursor.hide = (writableStream = process$1.stderr) => {
|
|
|
41369
41438
|
isHidden = true;
|
|
41370
41439
|
writableStream.write("\x1B[?25l");
|
|
41371
41440
|
};
|
|
41372
|
-
cliCursor.toggle = (force, writableStream) => {
|
|
41373
|
-
if (force !== void 0) isHidden = force;
|
|
41441
|
+
cliCursor.toggle = (force$1, writableStream) => {
|
|
41442
|
+
if (force$1 !== void 0) isHidden = force$1;
|
|
41374
41443
|
if (isHidden) cliCursor.show(writableStream);
|
|
41375
41444
|
else cliCursor.hide(writableStream);
|
|
41376
41445
|
};
|
|
@@ -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, options = {}) => 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(
|
|
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(
|
|
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(
|
|
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" }), Effect.map((diff$2) => options.force && diff$2.action === "noop" ? { action: "update" } : diff$2));
|
|
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(
|
|
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
|
|
|
@@ -44582,6 +44652,7 @@ const stage = Flag.string("stage").pipe(Flag.withSchema(S.String.check(S.isPatte
|
|
|
44582
44652
|
const envFile = Flag.file("env-file").pipe(Flag.optional, Flag.withDescription("File to load environment variables from, defaults to .env"));
|
|
44583
44653
|
const dryRun = Flag.boolean("dry-run").pipe(Flag.withDescription("Dry run the deployment, do not actually deploy"), Flag.withDefault(false));
|
|
44584
44654
|
const yes = Flag.boolean("yes").pipe(Flag.withDescription("Yes to all prompts"), Flag.withDefault(false));
|
|
44655
|
+
const force = Flag.boolean("force").pipe(Flag.withDescription("Force updates for resources that would otherwise no-op"), Flag.withDefault(false));
|
|
44585
44656
|
const fileLogger = Effect.fnUntraced(function* (...segments) {
|
|
44586
44657
|
const dotAlchemy$1 = yield* DotAlchemy;
|
|
44587
44658
|
const fs$2 = yield* FileSystem.FileSystem;
|
|
@@ -44600,6 +44671,7 @@ const loadConfigProvider = (envFile$1) => {
|
|
|
44600
44671
|
const main = Argument.file("main", { mustExist: true }).pipe(Argument.withDescription("Main file to deploy, defaults to alchemy.run.ts"), Argument.withDefault("alchemy.run.ts"));
|
|
44601
44672
|
const deployCommand = Command.make("deploy", {
|
|
44602
44673
|
dryRun,
|
|
44674
|
+
force,
|
|
44603
44675
|
main,
|
|
44604
44676
|
envFile,
|
|
44605
44677
|
stage,
|
|
@@ -44623,7 +44695,7 @@ const planCommand = Command.make("plan", {
|
|
|
44623
44695
|
...args,
|
|
44624
44696
|
dryRun: true
|
|
44625
44697
|
}));
|
|
44626
|
-
const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile: envFile$1, dryRun: dryRun$1 = false, yes: yes$1 = false, destroy = false }) {
|
|
44698
|
+
const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile: envFile$1, dryRun: dryRun$1 = false, force: force$1 = false, yes: yes$1 = false, destroy = false }) {
|
|
44627
44699
|
const path = yield* Path;
|
|
44628
44700
|
const stackEffect = (yield* Effect.promise(() => import(path.resolve(process.cwd(), main$1)))).default;
|
|
44629
44701
|
if (!stackEffect) return yield* Effect.die(/* @__PURE__ */ new Error(`Main file '${main$1}' must export a default stack definition (export default defineStack({...}))`));
|
|
@@ -44634,13 +44706,13 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
|
|
|
44634
44706
|
yield* Effect.gen(function* () {
|
|
44635
44707
|
const cli = yield* Cli;
|
|
44636
44708
|
const stack = yield* stackEffect;
|
|
44637
|
-
yield* Effect.gen(function* () {
|
|
44709
|
+
yield* provideFreshArtifactStore(Effect.gen(function* () {
|
|
44638
44710
|
const updatePlan = yield* make(destroy ? {
|
|
44639
44711
|
...stack,
|
|
44640
44712
|
resources: {},
|
|
44641
44713
|
bindings: {},
|
|
44642
44714
|
output: {}
|
|
44643
|
-
} : stack);
|
|
44715
|
+
} : stack, { force: force$1 });
|
|
44644
44716
|
if (dryRun$1) yield* cli.displayPlan(updatePlan);
|
|
44645
44717
|
else {
|
|
44646
44718
|
if (!yes$1) {
|
|
@@ -44649,7 +44721,7 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
|
|
|
44649
44721
|
const outputs = yield* apply(updatePlan);
|
|
44650
44722
|
yield* Console.log(outputs);
|
|
44651
44723
|
}
|
|
44652
|
-
}).pipe(Effect.provide(stack.services));
|
|
44724
|
+
})).pipe(Effect.provide(stack.services));
|
|
44653
44725
|
}).pipe(Effect.provide(Layer.provideMerge(alchemy, Layer.mergeAll(platform$1, Layer.succeed(Stage, stage$1)))), Effect.provideService(ConfigProvider.ConfigProvider, configProvider));
|
|
44654
44726
|
});
|
|
44655
44727
|
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));
|