alchemy 2.0.0-beta.27 → 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.
- package/bin/alchemy.js +1 -1
- package/bin/alchemy.js.map +1 -1
- package/bin/alchemy.ts +1 -1
- package/bin/exec.js +185 -93
- package/bin/exec.js.map +1 -1
- package/bin/exec.ts +1 -1
- package/lib/AWS/IAM/SAMLProvider.d.ts +2 -1
- package/lib/AWS/IAM/SAMLProvider.d.ts.map +1 -1
- package/lib/AWS/IAM/SAMLProvider.js +30 -9
- package/lib/AWS/IAM/SAMLProvider.js.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +55 -11
- package/lib/Apply.js.map +1 -1
- package/lib/Cli/commands/bootstrap.d.ts +1 -1
- package/lib/Cli/commands/bootstrap.d.ts.map +1 -1
- package/lib/Cli/commands/bootstrap.js +2 -1
- package/lib/Cli/commands/bootstrap.js.map +1 -1
- package/lib/Cli/exec.d.ts.map +1 -1
- package/lib/Cli/exec.js +2 -2
- package/lib/Cli/exec.js.map +1 -1
- package/lib/Cloudflare/Access.d.ts +19 -0
- package/lib/Cloudflare/Access.d.ts.map +1 -0
- package/lib/Cloudflare/Access.js +64 -0
- package/lib/Cloudflare/Access.js.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts +244 -0
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGateway.js +269 -0
- package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts +103 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.js +99 -0
- package/lib/Cloudflare/AiGateway/AiGatewayBinding.js.map +1 -0
- package/lib/Cloudflare/AiGateway/index.d.ts +3 -0
- package/lib/Cloudflare/AiGateway/index.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/index.js +3 -0
- package/lib/Cloudflare/AiGateway/index.js.map +1 -0
- package/lib/Cloudflare/EdgeSession.d.ts +2 -1
- package/lib/Cloudflare/EdgeSession.d.ts.map +1 -1
- package/lib/Cloudflare/EdgeSession.js +8 -4
- package/lib/Cloudflare/EdgeSession.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts +2 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +5 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +10 -0
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.js +12 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/State.d.ts +4 -3
- package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/State.js +103 -13
- package/lib/Cloudflare/StateStore/State.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +40 -31
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +1 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +1 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Plan.d.ts +8 -0
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +12 -5
- package/lib/Plan.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +19 -1
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +18 -1
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/Util/cached-function.d.ts +38 -0
- package/lib/Util/cached-function.d.ts.map +1 -0
- package/lib/Util/cached-function.js +53 -0
- package/lib/Util/cached-function.js.map +1 -0
- package/lib/Util/index.d.ts +0 -1
- package/lib/Util/index.d.ts.map +1 -1
- package/lib/Util/index.js +0 -1
- package/lib/Util/index.js.map +1 -1
- package/lib/Util/scc.d.ts +12 -0
- package/lib/Util/scc.d.ts.map +1 -0
- package/lib/Util/scc.js +81 -0
- package/lib/Util/scc.js.map +1 -0
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -2
- package/src/AWS/IAM/SAMLProvider.ts +47 -18
- package/src/Apply.ts +72 -8
- package/src/Cli/commands/bootstrap.ts +2 -0
- package/src/Cli/exec.ts +3 -2
- package/src/Cloudflare/Access.ts +110 -0
- package/src/Cloudflare/AiGateway/AiGateway.ts +526 -0
- package/src/Cloudflare/AiGateway/AiGatewayBinding.ts +190 -0
- package/src/Cloudflare/AiGateway/index.ts +2 -0
- package/src/Cloudflare/EdgeSession.ts +15 -5
- package/src/Cloudflare/Providers.ts +7 -0
- package/src/Cloudflare/StateStore/Api.ts +18 -0
- package/src/Cloudflare/StateStore/State.ts +130 -12
- package/src/Cloudflare/Workers/InferEnv.ts +7 -5
- package/src/Cloudflare/Workers/Worker.ts +44 -33
- package/src/Cloudflare/index.ts +1 -0
- package/src/Plan.ts +21 -5
- package/src/State/HttpStateApi.ts +22 -1
- package/src/Util/cached-function.ts +75 -0
- package/src/Util/index.ts +0 -1
- package/src/Util/scc.ts +89 -0
package/bin/alchemy.js
CHANGED
package/bin/alchemy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alchemy.js","sourceRoot":"","sources":["alchemy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"alchemy.js","sourceRoot":"","sources":["alchemy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC"}
|
package/bin/alchemy.ts
CHANGED
package/bin/exec.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as FileSystem from "effect/FileSystem";
|
|
4
3
|
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as S from "effect/Schema";
|
|
5
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
6
|
+
import * as EffectContext from "effect/Context";
|
|
7
|
+
import * as FileSystem from "effect/FileSystem";
|
|
5
8
|
import * as Path$1 from "effect/Path";
|
|
6
9
|
import { Path } from "effect/Path";
|
|
10
|
+
import * as Console from "effect/Console";
|
|
11
|
+
import * as Logger from "effect/Logger";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as Command from "effect/unstable/cli/Command";
|
|
14
|
+
import * as Flag from "effect/unstable/cli/Flag";
|
|
15
|
+
import * as Cause from "effect/Cause";
|
|
16
|
+
import * as Deferred from "effect/Deferred";
|
|
7
17
|
import * as Data from "effect/Data";
|
|
8
18
|
import { pipe } from "effect/Function";
|
|
9
19
|
import * as Redacted from "effect/Redacted";
|
|
10
20
|
import { SingleShotGen } from "effect/Utils";
|
|
11
|
-
import * as Option from "effect/Option";
|
|
12
21
|
import { pipeArguments } from "effect/Pipeable";
|
|
13
|
-
import * as ConfigProvider from "effect/ConfigProvider";
|
|
14
|
-
import * as Logger from "effect/Logger";
|
|
15
|
-
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
16
|
-
import * as Deferred from "effect/Deferred";
|
|
17
|
-
import * as S from "effect/Schema";
|
|
18
22
|
import path from "pathe";
|
|
19
23
|
import * as Config from "effect/Config";
|
|
20
24
|
import os from "node:os";
|
|
21
|
-
import * as Console from "effect/Console";
|
|
22
|
-
import * as Command from "effect/unstable/cli/Command";
|
|
23
|
-
import * as Flag from "effect/unstable/cli/Flag";
|
|
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";
|
|
@@ -44,10 +44,6 @@ const AlchemyContextLive = Layer.effect(AlchemyContext, Effect.gen(function* ()
|
|
|
44
44
|
};
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region lib/Util/data.js
|
|
49
|
-
const isPrimitive = (value) => value === void 0 || value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string" || typeof value === "symbol" || typeof value === "bigint";
|
|
50
|
-
|
|
51
47
|
//#endregion
|
|
52
48
|
//#region lib/Util/PlatformServices.js
|
|
53
49
|
const isBun = typeof Bun !== "undefined";
|
|
@@ -65,6 +61,74 @@ const runMain = (effect, options) => {
|
|
|
65
61
|
else import("@effect/platform-node/NodeRuntime").then((NodeRuntime) => NodeRuntime.runMain(effect, options));
|
|
66
62
|
};
|
|
67
63
|
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region lib/AdoptPolicy.js
|
|
66
|
+
var AdoptPolicy = class extends EffectContext.Service()("AdoptPolicy") {};
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region lib/Artifacts.js
|
|
70
|
+
/**
|
|
71
|
+
* Per-resource in-memory artifacts shared across a single `Plan.make -> apply`
|
|
72
|
+
* execution.
|
|
73
|
+
*
|
|
74
|
+
* The engine scopes this service by resource `FQN` before invoking lifecycle
|
|
75
|
+
* handlers, so providers should treat it as a resource-local bag for expensive,
|
|
76
|
+
* deterministic intermediate results that can be reused across phases.
|
|
77
|
+
*
|
|
78
|
+
* Expected usage:
|
|
79
|
+
*
|
|
80
|
+
* - `diff` computes an expensive artifact once and stores it
|
|
81
|
+
* - `create` / `update` reads the same artifact and skips recomputing it
|
|
82
|
+
* - artifacts are ephemeral and must never be required for correctness on a
|
|
83
|
+
* later deploy because the bag is reset between runs
|
|
84
|
+
*
|
|
85
|
+
* Example:
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* const artifacts = yield* Artifacts;
|
|
89
|
+
* const cached = yield* artifacts.get<PreparedBundle>("bundle");
|
|
90
|
+
* if (cached) return cached;
|
|
91
|
+
*
|
|
92
|
+
* const bundle = yield* prepareBundle();
|
|
93
|
+
* yield* artifacts.set("bundle", bundle);
|
|
94
|
+
* return bundle;
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
var Artifacts = class extends EffectContext.Service()("Artifacts") {};
|
|
98
|
+
var ArtifactStore = class extends EffectContext.Service()("Artifacts/Store") {};
|
|
99
|
+
/**
|
|
100
|
+
* Create a fresh root store for one deploy/test run.
|
|
101
|
+
*/
|
|
102
|
+
const createArtifactStore = () => /* @__PURE__ */ new Map();
|
|
103
|
+
const getOrCreateBag = (store, fqn) => {
|
|
104
|
+
const existing = store.get(fqn);
|
|
105
|
+
if (existing) return existing;
|
|
106
|
+
const bag = /* @__PURE__ */ new Map();
|
|
107
|
+
store.set(fqn, bag);
|
|
108
|
+
return bag;
|
|
109
|
+
};
|
|
110
|
+
const makeScopedArtifacts = (store, fqn) => {
|
|
111
|
+
const bag = getOrCreateBag(store, fqn);
|
|
112
|
+
return {
|
|
113
|
+
get: (key) => Effect.sync(() => bag.get(key)),
|
|
114
|
+
set: (key, value) => Effect.sync(() => {
|
|
115
|
+
bag.set(key, value);
|
|
116
|
+
}),
|
|
117
|
+
delete: (key) => Effect.sync(() => {
|
|
118
|
+
bag.delete(key);
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Ensure an artifact root exists, reusing the ambient store when one is already
|
|
124
|
+
* present. This lets nested helpers participate in the same run-scoped cache.
|
|
125
|
+
*/
|
|
126
|
+
const ensureArtifactStore = (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrUndefined), Effect.flatMap((existing) => effect.pipe(Effect.provideService(ArtifactStore, existing ?? createArtifactStore()))));
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region lib/Cli/Cli.js
|
|
130
|
+
var Cli = class extends EffectContext.Service()("CLI") {};
|
|
131
|
+
|
|
68
132
|
//#endregion
|
|
69
133
|
//#region lib/Util/service.js
|
|
70
134
|
const GenericService = () => (Kind) => {
|
|
@@ -73,10 +137,6 @@ const GenericService = () => (Kind) => {
|
|
|
73
137
|
return Object.assign(Object.setPrototypeOf(make$2, service), service);
|
|
74
138
|
};
|
|
75
139
|
|
|
76
|
-
//#endregion
|
|
77
|
-
//#region lib/Util/types.js
|
|
78
|
-
const asEffect = (effect) => Effect.isEffect(effect) ? effect : Effect.succeed(effect);
|
|
79
|
-
|
|
80
140
|
//#endregion
|
|
81
141
|
//#region lib/ExecutionContext.js
|
|
82
142
|
const ExecutionContext = GenericService()("Alchemy::ExecutionContext");
|
|
@@ -179,66 +239,6 @@ const tryFindProviderByType = Effect.fnUntraced(function* (resourceType) {
|
|
|
179
239
|
return Option.none();
|
|
180
240
|
});
|
|
181
241
|
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region lib/Artifacts.js
|
|
184
|
-
/**
|
|
185
|
-
* Per-resource in-memory artifacts shared across a single `Plan.make -> apply`
|
|
186
|
-
* execution.
|
|
187
|
-
*
|
|
188
|
-
* The engine scopes this service by resource `FQN` before invoking lifecycle
|
|
189
|
-
* handlers, so providers should treat it as a resource-local bag for expensive,
|
|
190
|
-
* deterministic intermediate results that can be reused across phases.
|
|
191
|
-
*
|
|
192
|
-
* Expected usage:
|
|
193
|
-
*
|
|
194
|
-
* - `diff` computes an expensive artifact once and stores it
|
|
195
|
-
* - `create` / `update` reads the same artifact and skips recomputing it
|
|
196
|
-
* - artifacts are ephemeral and must never be required for correctness on a
|
|
197
|
-
* later deploy because the bag is reset between runs
|
|
198
|
-
*
|
|
199
|
-
* Example:
|
|
200
|
-
*
|
|
201
|
-
* ```ts
|
|
202
|
-
* const artifacts = yield* Artifacts;
|
|
203
|
-
* const cached = yield* artifacts.get<PreparedBundle>("bundle");
|
|
204
|
-
* if (cached) return cached;
|
|
205
|
-
*
|
|
206
|
-
* const bundle = yield* prepareBundle();
|
|
207
|
-
* yield* artifacts.set("bundle", bundle);
|
|
208
|
-
* return bundle;
|
|
209
|
-
* ```
|
|
210
|
-
*/
|
|
211
|
-
var Artifacts = class extends EffectContext.Service()("Artifacts") {};
|
|
212
|
-
var ArtifactStore = class extends EffectContext.Service()("Artifacts/Store") {};
|
|
213
|
-
/**
|
|
214
|
-
* Create a fresh root store for one deploy/test run.
|
|
215
|
-
*/
|
|
216
|
-
const createArtifactStore = () => /* @__PURE__ */ new Map();
|
|
217
|
-
const getOrCreateBag = (store, fqn) => {
|
|
218
|
-
const existing = store.get(fqn);
|
|
219
|
-
if (existing) return existing;
|
|
220
|
-
const bag = /* @__PURE__ */ new Map();
|
|
221
|
-
store.set(fqn, bag);
|
|
222
|
-
return bag;
|
|
223
|
-
};
|
|
224
|
-
const makeScopedArtifacts = (store, fqn) => {
|
|
225
|
-
const bag = getOrCreateBag(store, fqn);
|
|
226
|
-
return {
|
|
227
|
-
get: (key) => Effect.sync(() => bag.get(key)),
|
|
228
|
-
set: (key, value) => Effect.sync(() => {
|
|
229
|
-
bag.set(key, value);
|
|
230
|
-
}),
|
|
231
|
-
delete: (key) => Effect.sync(() => {
|
|
232
|
-
bag.delete(key);
|
|
233
|
-
})
|
|
234
|
-
};
|
|
235
|
-
};
|
|
236
|
-
/**
|
|
237
|
-
* Ensure an artifact root exists, reusing the ambient store when one is already
|
|
238
|
-
* present. This lets nested helpers participate in the same run-scoped cache.
|
|
239
|
-
*/
|
|
240
|
-
const ensureArtifactStore = (effect) => Effect.serviceOption(ArtifactStore).pipe(Effect.map(Option.getOrUndefined), Effect.flatMap((existing) => effect.pipe(Effect.provideService(ArtifactStore, existing ?? createArtifactStore()))));
|
|
241
|
-
|
|
242
242
|
//#endregion
|
|
243
243
|
//#region lib/Auth/Profile.js
|
|
244
244
|
const rootDir = path.join(os.homedir(), ".alchemy");
|
|
@@ -281,10 +281,6 @@ const withProfileOverride = (base, profile$1) => {
|
|
|
281
281
|
//#region lib/Auth/AuthProvider.js
|
|
282
282
|
var AuthProviders = class extends EffectContext.Service()("AuthProviders") {};
|
|
283
283
|
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region lib/Cli/Cli.js
|
|
286
|
-
var Cli = class extends EffectContext.Service()("CLI") {};
|
|
287
|
-
|
|
288
284
|
//#endregion
|
|
289
285
|
//#region lib/Stage.js
|
|
290
286
|
var Stage = class extends EffectContext.Service()("Stage") {};
|
|
@@ -368,6 +364,10 @@ const isResource = (value) => {
|
|
|
368
364
|
//#region lib/State/State.js
|
|
369
365
|
var State = class extends EffectContext.Service()("alchemy/State") {};
|
|
370
366
|
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region lib/Util/data.js
|
|
369
|
+
const isPrimitive = (value) => value === void 0 || value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string" || typeof value === "symbol" || typeof value === "bigint";
|
|
370
|
+
|
|
371
371
|
//#endregion
|
|
372
372
|
//#region lib/Output.js
|
|
373
373
|
const inspect = Symbol.for("nodejs.util.inspect.custom");
|
|
@@ -603,10 +603,6 @@ const toObject = (acc, v) => ({
|
|
|
603
603
|
...v
|
|
604
604
|
});
|
|
605
605
|
|
|
606
|
-
//#endregion
|
|
607
|
-
//#region lib/AdoptPolicy.js
|
|
608
|
-
var AdoptPolicy = class extends EffectContext.Service()("AdoptPolicy") {};
|
|
609
|
-
|
|
610
606
|
//#endregion
|
|
611
607
|
//#region lib/Diff.js
|
|
612
608
|
const havePropsChanged = (oldProps, newProps) => hasOutputs(newProps) || JSON.stringify(canonicalize(oldProps ?? {})) !== JSON.stringify(canonicalize(newProps ?? {}));
|
|
@@ -645,6 +641,83 @@ const generateInstanceId = () => Effect.sync(() => {
|
|
|
645
641
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
646
642
|
});
|
|
647
643
|
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region lib/Util/types.js
|
|
646
|
+
const asEffect = (effect) => Effect.isEffect(effect) ? effect : Effect.succeed(effect);
|
|
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
|
-
|
|
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.
|
|
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,
|