alchemy-effect 0.3.0 → 0.5.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.
- package/bin/alchemy-effect.js +618 -260
- package/bin/alchemy-effect.js.map +1 -1
- package/lib/apply.d.ts +4 -4
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +444 -159
- package/lib/apply.js.map +1 -1
- package/lib/aws/dynamodb/table.provider.d.ts.map +1 -1
- package/lib/aws/dynamodb/table.provider.js +11 -2
- package/lib/aws/dynamodb/table.provider.js.map +1 -1
- package/lib/aws/ec2/index.d.ts +8 -0
- package/lib/aws/ec2/index.d.ts.map +1 -1
- package/lib/aws/ec2/index.js +8 -0
- package/lib/aws/ec2/index.js.map +1 -1
- package/lib/aws/ec2/internet-gateway.d.ts +65 -0
- package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway.js +4 -0
- package/lib/aws/ec2/internet-gateway.js.map +1 -0
- package/lib/aws/ec2/internet-gateway.provider.d.ts +6 -0
- package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway.provider.js +193 -0
- package/lib/aws/ec2/internet-gateway.provider.js.map +1 -0
- package/lib/aws/ec2/route-table-association.d.ts +63 -0
- package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
- package/lib/aws/ec2/route-table-association.js +4 -0
- package/lib/aws/ec2/route-table-association.js.map +1 -0
- package/lib/aws/ec2/route-table-association.provider.d.ts +4 -0
- package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -0
- package/lib/aws/ec2/route-table-association.provider.js +121 -0
- package/lib/aws/ec2/route-table-association.provider.js.map +1 -0
- package/lib/aws/ec2/route-table.d.ts +159 -0
- package/lib/aws/ec2/route-table.d.ts.map +1 -0
- package/lib/aws/ec2/route-table.js +4 -0
- package/lib/aws/ec2/route-table.js.map +1 -0
- package/lib/aws/ec2/route-table.provider.d.ts +6 -0
- package/lib/aws/ec2/route-table.provider.d.ts.map +1 -0
- package/lib/aws/ec2/route-table.provider.js +213 -0
- package/lib/aws/ec2/route-table.provider.js.map +1 -0
- package/lib/aws/ec2/route.d.ts +155 -0
- package/lib/aws/ec2/route.d.ts.map +1 -0
- package/lib/aws/ec2/route.js +3 -0
- package/lib/aws/ec2/route.js.map +1 -0
- package/lib/aws/ec2/route.provider.d.ts +4 -0
- package/lib/aws/ec2/route.provider.d.ts.map +1 -0
- package/lib/aws/ec2/route.provider.js +166 -0
- package/lib/aws/ec2/route.provider.js.map +1 -0
- package/lib/aws/ec2/subnet.provider.d.ts.map +1 -1
- package/lib/aws/ec2/subnet.provider.js +1 -1
- package/lib/aws/ec2/subnet.provider.js.map +1 -1
- package/lib/aws/ec2/vpc.d.ts +1 -0
- package/lib/aws/ec2/vpc.d.ts.map +1 -1
- package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
- package/lib/aws/ec2/vpc.provider.js +32 -10
- package/lib/aws/ec2/vpc.provider.js.map +1 -1
- package/lib/aws/index.d.ts +2 -3
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +2 -1
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/lambda/function.provider.d.ts +2 -2
- package/lib/aws/lambda/function.provider.d.ts.map +1 -1
- package/lib/aws/lambda/function.provider.js +39 -42
- package/lib/aws/lambda/function.provider.js.map +1 -1
- package/lib/aws/sqs/queue.provider.d.ts +3 -4
- package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
- package/lib/aws/sqs/queue.provider.js +17 -9
- package/lib/aws/sqs/queue.provider.js.map +1 -1
- package/lib/cli/index.d.ts +183 -100
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cloudflare/kv/namespace.client.d.ts +1 -1
- package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
- package/lib/cloudflare/kv/namespace.provider.js +12 -6
- package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
- package/lib/cloudflare/r2/bucket.binding.js +1 -1
- package/lib/cloudflare/r2/bucket.binding.js.map +1 -1
- package/lib/cloudflare/r2/bucket.d.ts +1 -1
- package/lib/cloudflare/r2/bucket.d.ts.map +1 -1
- package/lib/cloudflare/r2/bucket.provider.d.ts +1 -2
- package/lib/cloudflare/r2/bucket.provider.d.ts.map +1 -1
- package/lib/cloudflare/r2/bucket.provider.js +23 -12
- package/lib/cloudflare/r2/bucket.provider.js.map +1 -1
- package/lib/cloudflare/worker/worker.d.ts +2 -2
- package/lib/cloudflare/worker/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker/worker.provider.d.ts +2 -3
- package/lib/cloudflare/worker/worker.provider.d.ts.map +1 -1
- package/lib/cloudflare/worker/worker.provider.js +44 -13
- package/lib/cloudflare/worker/worker.provider.js.map +1 -1
- package/lib/diff.d.ts +8 -6
- package/lib/diff.d.ts.map +1 -1
- package/lib/diff.js +13 -0
- package/lib/diff.js.map +1 -1
- package/lib/event.d.ts +1 -1
- package/lib/event.d.ts.map +1 -1
- package/lib/instance-id.d.ts +12 -0
- package/lib/instance-id.d.ts.map +1 -0
- package/lib/instance-id.js +16 -0
- package/lib/instance-id.js.map +1 -0
- package/lib/output.d.ts +4 -2
- package/lib/output.d.ts.map +1 -1
- package/lib/output.js +18 -4
- package/lib/output.js.map +1 -1
- package/lib/physical-name.d.ts +25 -1
- package/lib/physical-name.d.ts.map +1 -1
- package/lib/physical-name.js +50 -2
- package/lib/physical-name.js.map +1 -1
- package/lib/plan.d.ts +49 -42
- package/lib/plan.d.ts.map +1 -1
- package/lib/plan.js +417 -137
- package/lib/plan.js.map +1 -1
- package/lib/provider.d.ts +26 -10
- package/lib/provider.d.ts.map +1 -1
- package/lib/provider.js +9 -0
- package/lib/provider.js.map +1 -1
- package/lib/resource.d.ts +3 -2
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js.map +1 -1
- package/lib/state.d.ts +86 -9
- package/lib/state.d.ts.map +1 -1
- package/lib/state.js +21 -18
- package/lib/state.js.map +1 -1
- package/lib/tags.d.ts +15 -0
- package/lib/tags.d.ts.map +1 -1
- package/lib/tags.js +27 -0
- package/lib/tags.js.map +1 -1
- package/lib/test.d.ts +2 -2
- package/lib/test.d.ts.map +1 -1
- package/lib/test.js +4 -4
- package/lib/test.js.map +1 -1
- package/lib/todo.d.ts +3 -0
- package/lib/todo.d.ts.map +1 -0
- package/lib/todo.js +3 -0
- package/lib/todo.js.map +1 -0
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/apply.ts +742 -348
- package/src/aws/dynamodb/table.provider.ts +16 -4
- package/src/aws/ec2/index.ts +8 -0
- package/src/aws/ec2/internet-gateway.provider.ts +316 -0
- package/src/aws/ec2/internet-gateway.ts +79 -0
- package/src/aws/ec2/route-table-association.provider.ts +214 -0
- package/src/aws/ec2/route-table-association.ts +82 -0
- package/src/aws/ec2/route-table.provider.ts +306 -0
- package/src/aws/ec2/route-table.ts +175 -0
- package/src/aws/ec2/route.provider.ts +213 -0
- package/src/aws/ec2/route.ts +192 -0
- package/src/aws/ec2/subnet.provider.ts +2 -2
- package/src/aws/ec2/vpc.provider.ts +36 -11
- package/src/aws/ec2/vpc.ts +2 -0
- package/src/aws/index.ts +4 -1
- package/src/aws/lambda/function.provider.ts +66 -53
- package/src/aws/sqs/queue.provider.ts +18 -11
- package/src/cloudflare/kv/namespace.provider.ts +19 -14
- package/src/cloudflare/r2/bucket.binding.ts +1 -1
- package/src/cloudflare/r2/bucket.provider.ts +34 -24
- package/src/cloudflare/r2/bucket.ts +1 -1
- package/src/cloudflare/worker/worker.provider.ts +43 -13
- package/src/cloudflare/worker/worker.ts +2 -2
- package/src/diff.ts +35 -17
- package/src/event.ts +6 -0
- package/src/instance-id.ts +20 -0
- package/src/output.ts +29 -5
- package/src/physical-name.ts +79 -2
- package/src/plan.ts +566 -214
- package/src/provider.ts +46 -10
- package/src/resource.ts +65 -8
- package/src/state.ts +150 -35
- package/src/tags.ts +31 -0
- package/src/test.ts +5 -5
- package/src/todo.ts +4 -0
package/src/apply.ts
CHANGED
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
import * as Context from "effect/Context";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import type { Simplify } from "effect/Types";
|
|
4
|
+
import { App } from "./app.ts";
|
|
4
5
|
import type { AnyBinding, BindingService } from "./binding.ts";
|
|
6
|
+
import {
|
|
7
|
+
type PlanStatusSession,
|
|
8
|
+
CLI,
|
|
9
|
+
type ScopedPlanStatusSession,
|
|
10
|
+
} from "./cli/service.ts";
|
|
5
11
|
import type { ApplyStatus } from "./event.ts";
|
|
12
|
+
import { generateInstanceId, InstanceId } from "./instance-id.ts";
|
|
6
13
|
import * as Output from "./output.ts";
|
|
7
14
|
import {
|
|
15
|
+
type Apply,
|
|
8
16
|
plan,
|
|
9
17
|
type BindNode,
|
|
10
|
-
type Create,
|
|
11
|
-
type CRUD,
|
|
12
18
|
type Delete,
|
|
13
19
|
type DerivePlan,
|
|
14
20
|
type IPlan,
|
|
15
21
|
type Providers,
|
|
16
|
-
type Update,
|
|
17
22
|
} from "./plan.ts";
|
|
18
23
|
import type { Instance } from "./policy.ts";
|
|
19
24
|
import type { AnyResource, Resource } from "./resource.ts";
|
|
20
25
|
import type { AnyService } from "./service.ts";
|
|
21
|
-
import {
|
|
22
|
-
|
|
26
|
+
import {
|
|
27
|
+
type CreatedResourceState,
|
|
28
|
+
type CreatingResourceState,
|
|
29
|
+
type DeletingResourceState,
|
|
30
|
+
type ReplacedResourceState,
|
|
31
|
+
type ReplacingResourceState,
|
|
32
|
+
type ResourceState,
|
|
33
|
+
type UpdatedResourceState,
|
|
34
|
+
type UpdatingReourceState,
|
|
35
|
+
State,
|
|
36
|
+
StateStoreError,
|
|
37
|
+
} from "./state.ts";
|
|
23
38
|
import { asEffect } from "./util.ts";
|
|
24
|
-
import {
|
|
39
|
+
import { getProviderByType } from "./provider.ts";
|
|
40
|
+
import { Layer } from "effect";
|
|
25
41
|
|
|
26
42
|
export type ApplyEffect<
|
|
27
43
|
P extends IPlan,
|
|
@@ -53,217 +69,372 @@ export const apply = <
|
|
|
53
69
|
never,
|
|
54
70
|
State | Providers<Instance<Resources[number]>>
|
|
55
71
|
// TODO(sam): don't cast to any
|
|
56
|
-
> =>
|
|
72
|
+
> =>
|
|
73
|
+
plan(...resources).pipe(
|
|
74
|
+
Effect.flatMap((p) => applyPlan(p as any as IPlan)),
|
|
75
|
+
) as any;
|
|
57
76
|
|
|
58
77
|
export const applyPlan = <P extends IPlan>(plan: P) =>
|
|
59
78
|
Effect.gen(function* () {
|
|
60
|
-
const state = yield* State;
|
|
61
|
-
// TODO(sam): rename terminology to Stack
|
|
62
|
-
const app = yield* App;
|
|
63
|
-
const outputs = {} as Record<string, Effect.Effect<any, any, State>>;
|
|
64
|
-
|
|
65
79
|
const cli = yield* CLI;
|
|
66
|
-
|
|
67
80
|
const session = yield* cli.startApplySession(plan);
|
|
68
|
-
const { emit, done } = session;
|
|
69
|
-
|
|
70
|
-
const resolveUpstream = Effect.fn(function* (resourceId: string) {
|
|
71
|
-
const upstreamNode = plan.resources[resourceId];
|
|
72
|
-
const upstreamAttr = upstreamNode
|
|
73
|
-
? yield* apply(upstreamNode)
|
|
74
|
-
: yield* Effect.dieMessage(`Resource ${resourceId} not found`);
|
|
75
|
-
return {
|
|
76
|
-
resourceId,
|
|
77
|
-
upstreamAttr,
|
|
78
|
-
upstreamNode,
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const resolveBindingUpstream = Effect.fn(function* ({
|
|
83
|
-
node,
|
|
84
|
-
}: {
|
|
85
|
-
node: BindNode;
|
|
86
|
-
resource: Resource;
|
|
87
|
-
}) {
|
|
88
|
-
const binding = node.binding as AnyBinding & {
|
|
89
|
-
// smuggled property (because it interacts poorly with inference)
|
|
90
|
-
Tag: Context.Tag<never, BindingService>;
|
|
91
|
-
};
|
|
92
|
-
const provider = yield* binding.Tag;
|
|
93
|
-
|
|
94
|
-
const resourceId: string = node.binding.capability.resource.id;
|
|
95
|
-
const { upstreamAttr, upstreamNode } = yield* resolveUpstream(resourceId);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
resourceId,
|
|
99
|
-
upstreamAttr,
|
|
100
|
-
upstreamNode,
|
|
101
|
-
provider,
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const attachBindings = ({
|
|
106
|
-
resource,
|
|
107
|
-
bindings,
|
|
108
|
-
target,
|
|
109
|
-
}: {
|
|
110
|
-
resource: Resource;
|
|
111
|
-
bindings: BindNode[];
|
|
112
|
-
target: {
|
|
113
|
-
id: string;
|
|
114
|
-
props: any;
|
|
115
|
-
attr: any;
|
|
116
|
-
};
|
|
117
|
-
}) =>
|
|
118
|
-
Effect.all(
|
|
119
|
-
bindings.map(
|
|
120
|
-
Effect.fn(function* (node) {
|
|
121
|
-
const { resourceId, upstreamAttr, upstreamNode, provider } =
|
|
122
|
-
yield* resolveBindingUpstream({ node, resource });
|
|
123
|
-
|
|
124
|
-
const input = {
|
|
125
|
-
source: {
|
|
126
|
-
id: resourceId,
|
|
127
|
-
attr: upstreamAttr,
|
|
128
|
-
props: upstreamNode.resource.props,
|
|
129
|
-
},
|
|
130
|
-
props: node.binding.props,
|
|
131
|
-
attr: node.attr,
|
|
132
|
-
target,
|
|
133
|
-
} as const;
|
|
134
|
-
if (node.action === "attach") {
|
|
135
|
-
return yield* asEffect(provider.attach(input));
|
|
136
|
-
} else if (node.action === "reattach") {
|
|
137
|
-
// reattach is optional, we fall back to attach if it's not available
|
|
138
|
-
return yield* asEffect(
|
|
139
|
-
(provider.reattach ? provider.reattach : provider.attach)(
|
|
140
|
-
input,
|
|
141
|
-
),
|
|
142
|
-
);
|
|
143
|
-
} else if (node.action === "detach" && provider.detach) {
|
|
144
|
-
return yield* asEffect(
|
|
145
|
-
provider.detach({
|
|
146
|
-
...input,
|
|
147
|
-
target,
|
|
148
|
-
}),
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
return node.attr;
|
|
152
|
-
}),
|
|
153
|
-
),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const postAttachBindings = ({
|
|
157
|
-
bindings,
|
|
158
|
-
bindingOutputs,
|
|
159
|
-
resource,
|
|
160
|
-
target,
|
|
161
|
-
}: {
|
|
162
|
-
bindings: BindNode[];
|
|
163
|
-
bindingOutputs: any[];
|
|
164
|
-
resource: Resource;
|
|
165
|
-
target: {
|
|
166
|
-
id: string;
|
|
167
|
-
props: any;
|
|
168
|
-
attr: any;
|
|
169
|
-
};
|
|
170
|
-
}) =>
|
|
171
|
-
Effect.all(
|
|
172
|
-
bindings.map(
|
|
173
|
-
Effect.fn(function* (node, i) {
|
|
174
|
-
const { resourceId, upstreamAttr, upstreamNode, provider } =
|
|
175
|
-
yield* resolveBindingUpstream({ node, resource });
|
|
176
|
-
|
|
177
|
-
const oldBindingOutput = bindingOutputs[i];
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
provider.postattach &&
|
|
181
|
-
(node.action === "attach" || node.action === "reattach")
|
|
182
|
-
) {
|
|
183
|
-
const bindingOutput = yield* asEffect(
|
|
184
|
-
provider.postattach({
|
|
185
|
-
source: {
|
|
186
|
-
id: resourceId,
|
|
187
|
-
attr: upstreamAttr,
|
|
188
|
-
props: upstreamNode.resource.props,
|
|
189
|
-
},
|
|
190
|
-
props: node.binding.props,
|
|
191
|
-
attr: oldBindingOutput,
|
|
192
|
-
target,
|
|
193
|
-
} as const),
|
|
194
|
-
);
|
|
195
|
-
return {
|
|
196
|
-
...oldBindingOutput,
|
|
197
|
-
...bindingOutput,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return oldBindingOutput;
|
|
201
|
-
}),
|
|
202
|
-
),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const apply: (node: CRUD) => Effect.Effect<any, never, never> = (node) =>
|
|
206
|
-
Effect.gen(function* () {
|
|
207
|
-
const saveState = <Output>({
|
|
208
|
-
output,
|
|
209
|
-
bindings = node.bindings,
|
|
210
|
-
news,
|
|
211
|
-
}: {
|
|
212
|
-
output: Output;
|
|
213
|
-
bindings?: BindNode[];
|
|
214
|
-
news: any;
|
|
215
|
-
}) =>
|
|
216
|
-
state
|
|
217
|
-
.set({
|
|
218
|
-
stack: app.name,
|
|
219
|
-
stage: app.stage,
|
|
220
|
-
resourceId: node.resource.id,
|
|
221
|
-
value: {
|
|
222
|
-
id: node.resource.id,
|
|
223
|
-
type: node.resource.type,
|
|
224
|
-
status: node.action === "create" ? "created" : "updated",
|
|
225
|
-
props: news,
|
|
226
|
-
output,
|
|
227
|
-
bindings,
|
|
228
|
-
},
|
|
229
|
-
})
|
|
230
|
-
.pipe(Effect.map(() => output));
|
|
231
81
|
|
|
232
|
-
|
|
233
|
-
|
|
82
|
+
// 1. expand the graph (create new resources, update existing and create replacements)
|
|
83
|
+
const resources = yield* expandAndPivot(plan, session);
|
|
84
|
+
// TODO(sam): support roll back to previous state if errors occur during expansion
|
|
85
|
+
// -> RISK: some UPDATEs may not be reverisble (i.e. trigger replacements)
|
|
86
|
+
// TODO(sam): should pivot be done separately? E.g shift traffic?
|
|
87
|
+
|
|
88
|
+
// 2. delete orphans and replaced resources
|
|
89
|
+
yield* collectGarbage(plan, session);
|
|
90
|
+
|
|
91
|
+
yield* session.done();
|
|
92
|
+
|
|
93
|
+
if (Object.keys(plan.resources).length === 0) {
|
|
94
|
+
// all resources are deleted, return undefined
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return resources as {
|
|
98
|
+
[k in keyof AppliedPlan<P>]: AppliedPlan<P>[k];
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const expandAndPivot = Effect.fnUntraced(function* (
|
|
103
|
+
plan: IPlan,
|
|
104
|
+
session: PlanStatusSession,
|
|
105
|
+
) {
|
|
106
|
+
const state = yield* State;
|
|
107
|
+
const app = yield* App;
|
|
108
|
+
|
|
109
|
+
const outputs = {} as Record<string, Effect.Effect<any, any, State>>;
|
|
110
|
+
const resolveUpstream = Effect.fn(function* (resourceId: string) {
|
|
111
|
+
const upstreamNode = plan.resources[resourceId];
|
|
112
|
+
const upstreamAttr = upstreamNode
|
|
113
|
+
? yield* apply(upstreamNode)
|
|
114
|
+
: yield* Effect.dieMessage(`Resource ${resourceId} not found`);
|
|
115
|
+
return {
|
|
116
|
+
resourceId,
|
|
117
|
+
upstreamAttr,
|
|
118
|
+
upstreamNode,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
234
121
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
122
|
+
const resolveBindingUpstream = Effect.fn(function* ({
|
|
123
|
+
node,
|
|
124
|
+
}: {
|
|
125
|
+
node: BindNode;
|
|
126
|
+
resource: Resource;
|
|
127
|
+
}) {
|
|
128
|
+
const binding = node.binding as AnyBinding & {
|
|
129
|
+
// smuggled property (because it interacts poorly with inference)
|
|
130
|
+
Tag: Context.Tag<never, BindingService>;
|
|
131
|
+
};
|
|
132
|
+
const provider = yield* binding.Tag;
|
|
133
|
+
const resourceId: string = node.binding.capability.resource.id;
|
|
134
|
+
const { upstreamAttr, upstreamNode } = yield* resolveUpstream(resourceId);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
resourceId,
|
|
138
|
+
upstreamAttr,
|
|
139
|
+
upstreamNode,
|
|
140
|
+
provider,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const attachBindings = ({
|
|
145
|
+
resource,
|
|
146
|
+
bindings,
|
|
147
|
+
target,
|
|
148
|
+
}: {
|
|
149
|
+
resource: Resource;
|
|
150
|
+
bindings: BindNode[];
|
|
151
|
+
target: {
|
|
152
|
+
id: string;
|
|
153
|
+
props: any;
|
|
154
|
+
attr: any;
|
|
155
|
+
};
|
|
156
|
+
}) =>
|
|
157
|
+
Effect.all(
|
|
158
|
+
bindings.map(
|
|
159
|
+
Effect.fn(function* (node) {
|
|
160
|
+
const { resourceId, upstreamAttr, upstreamNode, provider } =
|
|
161
|
+
yield* resolveBindingUpstream({ node, resource });
|
|
162
|
+
|
|
163
|
+
const input = {
|
|
164
|
+
source: {
|
|
165
|
+
id: resourceId,
|
|
166
|
+
attr: upstreamAttr,
|
|
167
|
+
props: upstreamNode.resource.props,
|
|
168
|
+
},
|
|
169
|
+
props: node.binding.props,
|
|
170
|
+
attr: node.attr,
|
|
171
|
+
target,
|
|
172
|
+
} as const;
|
|
173
|
+
if (node.action === "attach") {
|
|
174
|
+
return yield* asEffect(provider.attach(input));
|
|
175
|
+
} else if (node.action === "reattach") {
|
|
176
|
+
// reattach is optional, we fall back to attach if it's not available
|
|
177
|
+
return yield* asEffect(
|
|
178
|
+
(provider.reattach ? provider.reattach : provider.attach)(input),
|
|
179
|
+
);
|
|
180
|
+
} else if (node.action === "detach" && provider.detach) {
|
|
181
|
+
return yield* asEffect(
|
|
182
|
+
provider.detach({
|
|
183
|
+
...input,
|
|
184
|
+
target,
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return node.attr;
|
|
189
|
+
}),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const postAttachBindings = ({
|
|
194
|
+
bindings,
|
|
195
|
+
bindingOutputs,
|
|
196
|
+
resource,
|
|
197
|
+
target,
|
|
198
|
+
}: {
|
|
199
|
+
bindings: BindNode[];
|
|
200
|
+
bindingOutputs: any[];
|
|
201
|
+
resource: Resource;
|
|
202
|
+
target: {
|
|
203
|
+
id: string;
|
|
204
|
+
props: any;
|
|
205
|
+
attr: any;
|
|
206
|
+
};
|
|
207
|
+
}) =>
|
|
208
|
+
Effect.all(
|
|
209
|
+
bindings.map(
|
|
210
|
+
Effect.fn(function* (node, i) {
|
|
211
|
+
const { resourceId, upstreamAttr, upstreamNode, provider } =
|
|
212
|
+
yield* resolveBindingUpstream({ node, resource });
|
|
213
|
+
|
|
214
|
+
const oldBindingOutput = bindingOutputs[i];
|
|
215
|
+
|
|
216
|
+
if (
|
|
217
|
+
provider.postattach &&
|
|
218
|
+
(node.action === "attach" || node.action === "reattach")
|
|
219
|
+
) {
|
|
220
|
+
const bindingOutput = yield* asEffect(
|
|
221
|
+
provider.postattach({
|
|
222
|
+
source: {
|
|
223
|
+
id: resourceId,
|
|
224
|
+
attr: upstreamAttr,
|
|
225
|
+
props: upstreamNode.resource.props,
|
|
226
|
+
},
|
|
227
|
+
props: node.binding.props,
|
|
228
|
+
attr: oldBindingOutput,
|
|
229
|
+
target,
|
|
230
|
+
} as const),
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
...oldBindingOutput,
|
|
234
|
+
...bindingOutput,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return oldBindingOutput;
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const apply: (node: Apply) => Effect.Effect<any, never, never> = (node) =>
|
|
243
|
+
Effect.gen(function* () {
|
|
244
|
+
const commit = <State extends ResourceState>(value: State) =>
|
|
245
|
+
state.set({
|
|
246
|
+
stack: app.name,
|
|
247
|
+
stage: app.stage,
|
|
248
|
+
resourceId: node.resource.id,
|
|
249
|
+
value,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const id = node.resource.id;
|
|
253
|
+
const resource = node.resource;
|
|
254
|
+
|
|
255
|
+
const scopedSession = {
|
|
256
|
+
...session,
|
|
257
|
+
note: (note: string) =>
|
|
258
|
+
session.emit({
|
|
259
|
+
id,
|
|
260
|
+
kind: "annotate",
|
|
261
|
+
message: note,
|
|
262
|
+
}),
|
|
263
|
+
} satisfies ScopedPlanStatusSession;
|
|
264
|
+
|
|
265
|
+
return yield* (outputs[id] ??= yield* Effect.cached(
|
|
266
|
+
Effect.gen(function* () {
|
|
267
|
+
const report = (status: ApplyStatus) =>
|
|
238
268
|
session.emit({
|
|
269
|
+
kind: "status-change",
|
|
239
270
|
id,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
271
|
+
type: node.resource.type,
|
|
272
|
+
status,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (node.action === "noop") {
|
|
276
|
+
return node.state.attr;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// resolve upstream dependencies before committing any changes to state
|
|
280
|
+
const upstream = Object.fromEntries(
|
|
281
|
+
yield* Effect.all(
|
|
282
|
+
Object.entries(Output.resolveUpstream(node.props)).map(([id]) =>
|
|
283
|
+
resolveUpstream(id).pipe(
|
|
284
|
+
Effect.map(({ upstreamAttr }) => [id, upstreamAttr]),
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const instanceId = yield* Effect.gen(function* () {
|
|
291
|
+
if (node.action === "create" && !node.state?.instanceId) {
|
|
292
|
+
const instanceId = yield* generateInstanceId();
|
|
293
|
+
yield* commit<CreatingResourceState>({
|
|
294
|
+
status: "creating",
|
|
295
|
+
instanceId,
|
|
296
|
+
logicalId: id,
|
|
297
|
+
downstream: node.downstream,
|
|
298
|
+
props: node.props,
|
|
299
|
+
providerVersion: node.provider.version ?? 0,
|
|
300
|
+
resourceType: node.resource.type,
|
|
301
|
+
bindings: node.bindings,
|
|
302
|
+
});
|
|
303
|
+
return instanceId;
|
|
304
|
+
} else if (node.action === "replace") {
|
|
305
|
+
if (
|
|
306
|
+
node.state.status === "replaced" ||
|
|
307
|
+
node.state.status === "replacing"
|
|
308
|
+
) {
|
|
309
|
+
// replace has already begun and we have the new instanceId, do not re-create it
|
|
310
|
+
return node.state.instanceId;
|
|
311
|
+
}
|
|
312
|
+
const instanceId = yield* generateInstanceId();
|
|
313
|
+
yield* commit<ReplacingResourceState>({
|
|
314
|
+
status: "replacing",
|
|
315
|
+
instanceId,
|
|
316
|
+
logicalId: id,
|
|
317
|
+
downstream: node.downstream,
|
|
318
|
+
props: node.props,
|
|
319
|
+
providerVersion: node.provider.version ?? 0,
|
|
320
|
+
resourceType: node.resource.type,
|
|
321
|
+
bindings: node.bindings,
|
|
322
|
+
old: node.state,
|
|
323
|
+
deleteFirst: node.deleteFirst,
|
|
324
|
+
});
|
|
325
|
+
return instanceId;
|
|
326
|
+
} else if (node.state?.instanceId) {
|
|
327
|
+
// we're in a create, update or delete state with a stable instanceId, use it
|
|
328
|
+
return node.state.instanceId;
|
|
329
|
+
}
|
|
330
|
+
// this should never happen
|
|
331
|
+
return yield* Effect.dieMessage(
|
|
332
|
+
`Instance ID not found for resource '${id}' and action is '${node.action}'`,
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const apply = Effect.gen(function* () {
|
|
337
|
+
if (node.action === "create") {
|
|
338
|
+
const news = (yield* Output.evaluate(
|
|
339
|
+
node.props,
|
|
340
|
+
upstream,
|
|
341
|
+
)) as Record<string, any>;
|
|
342
|
+
|
|
343
|
+
const checkpoint = (attr: any) =>
|
|
344
|
+
commit<CreatingResourceState>({
|
|
345
|
+
status: "creating",
|
|
346
|
+
logicalId: id,
|
|
347
|
+
instanceId,
|
|
348
|
+
resourceType: node.resource.type,
|
|
349
|
+
props: news,
|
|
350
|
+
attr,
|
|
351
|
+
providerVersion: node.provider.version ?? 0,
|
|
352
|
+
bindings: node.bindings,
|
|
353
|
+
downstream: node.downstream,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!node.state) {
|
|
357
|
+
yield* checkpoint(undefined);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let attr: any;
|
|
361
|
+
if (
|
|
362
|
+
node.action === "create" &&
|
|
363
|
+
node.provider.precreate &&
|
|
364
|
+
// pre-create is only designed to ensure the resource exists, if we have state.attr, then it already exists and should be skipped
|
|
365
|
+
node.state?.attr === undefined
|
|
366
|
+
) {
|
|
367
|
+
yield* report("pre-creating");
|
|
368
|
+
|
|
369
|
+
// stub the resource prior to resolving upstream resources or bindings if a stub is available
|
|
370
|
+
attr = yield* node.provider.precreate({
|
|
371
|
+
id,
|
|
372
|
+
news: node.props,
|
|
373
|
+
session: scopedSession,
|
|
374
|
+
instanceId,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
yield* checkpoint(attr);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
yield* report("attaching");
|
|
381
|
+
|
|
382
|
+
let bindingOutputs = yield* attachBindings({
|
|
383
|
+
resource,
|
|
384
|
+
bindings: node.bindings,
|
|
385
|
+
target: {
|
|
386
|
+
id,
|
|
387
|
+
props: news,
|
|
388
|
+
attr,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
yield* report("creating");
|
|
393
|
+
|
|
394
|
+
attr = yield* node.provider.create({
|
|
250
395
|
id,
|
|
251
|
-
|
|
252
|
-
|
|
396
|
+
news,
|
|
397
|
+
instanceId,
|
|
398
|
+
bindings: bindingOutputs,
|
|
399
|
+
session: scopedSession,
|
|
253
400
|
});
|
|
254
401
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
402
|
+
yield* checkpoint(attr);
|
|
403
|
+
|
|
404
|
+
yield* report("post-attach");
|
|
405
|
+
bindingOutputs = yield* postAttachBindings({
|
|
406
|
+
resource,
|
|
407
|
+
bindings: node.bindings,
|
|
408
|
+
bindingOutputs,
|
|
409
|
+
target: {
|
|
410
|
+
id,
|
|
411
|
+
props: news,
|
|
412
|
+
attr,
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
yield* commit<CreatedResourceState>({
|
|
417
|
+
status: "created",
|
|
418
|
+
logicalId: id,
|
|
419
|
+
instanceId,
|
|
420
|
+
resourceType: node.resource.type,
|
|
421
|
+
props: news,
|
|
422
|
+
attr,
|
|
423
|
+
bindings: node.bindings.map((binding, i) => ({
|
|
424
|
+
...binding,
|
|
425
|
+
attr: bindingOutputs[i],
|
|
426
|
+
})),
|
|
427
|
+
providerVersion: node.provider.version ?? 0,
|
|
428
|
+
downstream: node.downstream,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
yield* report("created");
|
|
432
|
+
|
|
433
|
+
return attr;
|
|
434
|
+
} else if (node.action === "update") {
|
|
264
435
|
const upstream = Object.fromEntries(
|
|
265
436
|
yield* Effect.all(
|
|
266
|
-
Object.entries(Output.resolveUpstream(node.
|
|
437
|
+
Object.entries(Output.resolveUpstream(node.props)).map(
|
|
267
438
|
([id]) =>
|
|
268
439
|
resolveUpstream(id).pipe(
|
|
269
440
|
Effect.map(({ upstreamAttr }) => [id, upstreamAttr]),
|
|
@@ -271,9 +442,40 @@ export const applyPlan = <P extends IPlan>(plan: P) =>
|
|
|
271
442
|
),
|
|
272
443
|
),
|
|
273
444
|
);
|
|
274
|
-
const news = yield* Output.evaluate(
|
|
445
|
+
const news = (yield* Output.evaluate(
|
|
446
|
+
node.props,
|
|
447
|
+
upstream,
|
|
448
|
+
)) as Record<string, any>;
|
|
449
|
+
|
|
450
|
+
const checkpoint = (attr: any) => {
|
|
451
|
+
if (node.state.status === "replaced") {
|
|
452
|
+
return commit<ReplacedResourceState>({
|
|
453
|
+
...node.state,
|
|
454
|
+
attr,
|
|
455
|
+
props: news,
|
|
456
|
+
});
|
|
457
|
+
} else {
|
|
458
|
+
return commit<UpdatingReourceState>({
|
|
459
|
+
status: "updating",
|
|
460
|
+
logicalId: id,
|
|
461
|
+
instanceId,
|
|
462
|
+
resourceType: node.resource.type,
|
|
463
|
+
props: news,
|
|
464
|
+
attr,
|
|
465
|
+
providerVersion: node.provider.version ?? 0,
|
|
466
|
+
bindings: node.bindings,
|
|
467
|
+
downstream: node.downstream,
|
|
468
|
+
old:
|
|
469
|
+
node.state.status === "updating"
|
|
470
|
+
? node.state.old
|
|
471
|
+
: node.state,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
yield* checkpoint(node.state.attr);
|
|
275
477
|
|
|
276
|
-
yield* report(
|
|
478
|
+
yield* report("attaching");
|
|
277
479
|
|
|
278
480
|
let bindingOutputs = yield* attachBindings({
|
|
279
481
|
resource,
|
|
@@ -281,30 +483,30 @@ export const applyPlan = <P extends IPlan>(plan: P) =>
|
|
|
281
483
|
target: {
|
|
282
484
|
id,
|
|
283
485
|
props: news,
|
|
284
|
-
attr,
|
|
486
|
+
attr: node.state.attr,
|
|
285
487
|
},
|
|
286
488
|
});
|
|
287
489
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
490
|
+
yield* report("updating");
|
|
491
|
+
|
|
492
|
+
const attr = yield* node.provider.update({
|
|
291
493
|
id,
|
|
292
494
|
news,
|
|
495
|
+
instanceId,
|
|
293
496
|
bindings: bindingOutputs,
|
|
294
497
|
session: scopedSession,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
498
|
+
olds:
|
|
499
|
+
node.state.status === "created" ||
|
|
500
|
+
node.state.status === "updated" ||
|
|
501
|
+
node.state.status === "replaced"
|
|
502
|
+
? node.state.props
|
|
503
|
+
: node.state.old.props,
|
|
504
|
+
output: node.state.attr,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
yield* checkpoint(attr);
|
|
508
|
+
|
|
509
|
+
yield* report("post-attach");
|
|
308
510
|
|
|
309
511
|
bindingOutputs = yield* postAttachBindings({
|
|
310
512
|
resource,
|
|
@@ -317,148 +519,340 @@ export const applyPlan = <P extends IPlan>(plan: P) =>
|
|
|
317
519
|
},
|
|
318
520
|
});
|
|
319
521
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
522
|
+
if (node.state.status === "replaced") {
|
|
523
|
+
yield* commit<ReplacedResourceState>({
|
|
524
|
+
...node.state,
|
|
525
|
+
attr,
|
|
526
|
+
props: news,
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
yield* commit<UpdatedResourceState>({
|
|
530
|
+
status: "updated",
|
|
531
|
+
logicalId: id,
|
|
532
|
+
instanceId,
|
|
533
|
+
resourceType: node.resource.type,
|
|
534
|
+
props: news,
|
|
535
|
+
attr,
|
|
536
|
+
bindings: node.bindings.map((binding, i) => ({
|
|
537
|
+
...binding,
|
|
538
|
+
attr: bindingOutputs[i],
|
|
539
|
+
})),
|
|
540
|
+
providerVersion: node.provider.version ?? 0,
|
|
541
|
+
downstream: node.downstream,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
328
544
|
|
|
329
|
-
|
|
330
|
-
|
|
545
|
+
yield* report("updated");
|
|
546
|
+
|
|
547
|
+
return attr;
|
|
548
|
+
} else if (node.action === "replace") {
|
|
549
|
+
if (node.state.status === "replaced") {
|
|
550
|
+
// we've already created the replacement resource, return the output
|
|
551
|
+
return node.state.attr;
|
|
552
|
+
}
|
|
553
|
+
let state: ReplacingResourceState;
|
|
554
|
+
if (node.state.status !== "replacing") {
|
|
555
|
+
yield* commit<ReplacingResourceState>(
|
|
556
|
+
(state = {
|
|
557
|
+
status: "replacing",
|
|
558
|
+
logicalId: id,
|
|
559
|
+
instanceId,
|
|
560
|
+
resourceType: node.resource.type,
|
|
561
|
+
props: node.props,
|
|
562
|
+
attr: node.state.attr,
|
|
563
|
+
providerVersion: node.provider.version ?? 0,
|
|
564
|
+
deleteFirst: node.deleteFirst,
|
|
565
|
+
old: node.state,
|
|
566
|
+
downstream: node.downstream,
|
|
567
|
+
}),
|
|
568
|
+
);
|
|
569
|
+
} else {
|
|
570
|
+
state = node.state;
|
|
571
|
+
}
|
|
572
|
+
const upstream = Object.fromEntries(
|
|
573
|
+
yield* Effect.all(
|
|
574
|
+
Object.entries(Output.resolveUpstream(node.props)).map(
|
|
575
|
+
([id]) =>
|
|
576
|
+
resolveUpstream(id).pipe(
|
|
577
|
+
Effect.map(({ upstreamAttr }) => [id, upstreamAttr]),
|
|
578
|
+
),
|
|
579
|
+
),
|
|
580
|
+
),
|
|
581
|
+
);
|
|
582
|
+
const news = (yield* Output.evaluate(
|
|
583
|
+
node.props,
|
|
584
|
+
upstream,
|
|
585
|
+
)) as Record<string, any>;
|
|
586
|
+
|
|
587
|
+
const checkpoint = <
|
|
588
|
+
S extends ReplacingResourceState | ReplacedResourceState,
|
|
589
|
+
>({
|
|
590
|
+
status,
|
|
591
|
+
attr,
|
|
592
|
+
bindings,
|
|
593
|
+
}: Pick<S, "status" | "attr" | "bindings">) =>
|
|
594
|
+
commit<S>({
|
|
595
|
+
status,
|
|
596
|
+
logicalId: id,
|
|
597
|
+
instanceId,
|
|
598
|
+
resourceType: node.resource.type,
|
|
599
|
+
props: news,
|
|
600
|
+
attr,
|
|
601
|
+
providerVersion: node.provider.version ?? 0,
|
|
602
|
+
bindings: bindings ?? node.bindings,
|
|
603
|
+
downstream: node.downstream,
|
|
604
|
+
old: state.old,
|
|
605
|
+
deleteFirst: node.deleteFirst,
|
|
606
|
+
} as S);
|
|
331
607
|
|
|
332
|
-
if (node.action === "noop") {
|
|
333
|
-
return (yield* state.get({
|
|
334
|
-
stack: app.name,
|
|
335
|
-
stage: app.stage,
|
|
336
|
-
resourceId: id,
|
|
337
|
-
}))?.output;
|
|
338
|
-
} else if (node.action === "create") {
|
|
339
608
|
let attr: any;
|
|
340
|
-
if (
|
|
341
|
-
|
|
609
|
+
if (
|
|
610
|
+
node.provider.precreate &&
|
|
611
|
+
// pre-create is only designed to ensure the resource exists, if we have state.attr, then it already exists and should be skipped
|
|
612
|
+
node.state?.attr === undefined
|
|
613
|
+
) {
|
|
614
|
+
yield* report("pre-creating");
|
|
615
|
+
|
|
342
616
|
// stub the resource prior to resolving upstream resources or bindings if a stub is available
|
|
343
617
|
attr = yield* node.provider.precreate({
|
|
344
618
|
id,
|
|
345
|
-
news: node.
|
|
619
|
+
news: node.props,
|
|
346
620
|
session: scopedSession,
|
|
621
|
+
instanceId,
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
yield* checkpoint({
|
|
625
|
+
status: "replacing",
|
|
626
|
+
attr,
|
|
347
627
|
});
|
|
348
628
|
}
|
|
349
629
|
|
|
350
|
-
yield*
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
630
|
+
yield* report("attaching");
|
|
631
|
+
|
|
632
|
+
let bindingOutputs = yield* attachBindings({
|
|
633
|
+
resource,
|
|
634
|
+
bindings: node.bindings,
|
|
635
|
+
target: {
|
|
636
|
+
id,
|
|
637
|
+
props: news,
|
|
638
|
+
attr,
|
|
639
|
+
},
|
|
355
640
|
});
|
|
356
|
-
|
|
357
|
-
yield*
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
641
|
+
|
|
642
|
+
yield* report("creating replacement");
|
|
643
|
+
|
|
644
|
+
attr = yield* node.provider.create({
|
|
645
|
+
id,
|
|
646
|
+
news,
|
|
647
|
+
instanceId,
|
|
648
|
+
bindings: bindingOutputs,
|
|
649
|
+
session: scopedSession,
|
|
362
650
|
});
|
|
363
|
-
} else if (node.action === "delete") {
|
|
364
|
-
yield* Effect.logDebug("delete", id);
|
|
365
|
-
yield* Effect.all(
|
|
366
|
-
node.downstream.map((dep) =>
|
|
367
|
-
dep in plan.resources
|
|
368
|
-
? apply(plan.resources[dep] as any)
|
|
369
|
-
: Effect.void,
|
|
370
|
-
),
|
|
371
|
-
);
|
|
372
|
-
yield* report("deleting");
|
|
373
651
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
stage: app.stage,
|
|
387
|
-
resourceId: id,
|
|
388
|
-
}),
|
|
389
|
-
),
|
|
390
|
-
Effect.tap(() => report("deleted")),
|
|
391
|
-
);
|
|
392
|
-
} else if (node.action === "replace") {
|
|
393
|
-
const destroy = Effect.gen(function* () {
|
|
394
|
-
yield* report("deleting");
|
|
395
|
-
return yield* node.provider.delete({
|
|
652
|
+
yield* checkpoint({
|
|
653
|
+
status: "replacing",
|
|
654
|
+
attr,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
yield* report("post-attach");
|
|
658
|
+
|
|
659
|
+
bindingOutputs = yield* postAttachBindings({
|
|
660
|
+
resource,
|
|
661
|
+
bindings: node.bindings,
|
|
662
|
+
bindingOutputs,
|
|
663
|
+
target: {
|
|
396
664
|
id,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
bindings: [],
|
|
401
|
-
});
|
|
665
|
+
props: news,
|
|
666
|
+
attr,
|
|
667
|
+
},
|
|
402
668
|
});
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
// TODO(sam): these need to only include attach actions
|
|
412
|
-
bindings: yield* attachBindings({
|
|
413
|
-
resource,
|
|
414
|
-
bindings: node.bindings,
|
|
415
|
-
target: {
|
|
416
|
-
id,
|
|
417
|
-
// TODO(sam): resolve the news
|
|
418
|
-
props: node.news,
|
|
419
|
-
attr: node.attributes,
|
|
420
|
-
},
|
|
421
|
-
}),
|
|
422
|
-
session: scopedSession,
|
|
423
|
-
})
|
|
424
|
-
.pipe(
|
|
425
|
-
Effect.tap((output) =>
|
|
426
|
-
saveState({ news: node.news, output }),
|
|
427
|
-
),
|
|
428
|
-
);
|
|
669
|
+
|
|
670
|
+
yield* checkpoint<ReplacedResourceState>({
|
|
671
|
+
status: "replaced",
|
|
672
|
+
attr,
|
|
673
|
+
bindings: node.bindings.map((binding, i) => ({
|
|
674
|
+
...binding,
|
|
675
|
+
attr: bindingOutputs[i],
|
|
676
|
+
})),
|
|
429
677
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
} else {
|
|
434
|
-
yield* destroy;
|
|
435
|
-
return yield* create;
|
|
436
|
-
}
|
|
678
|
+
|
|
679
|
+
yield* report("created");
|
|
680
|
+
return attr;
|
|
437
681
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
)
|
|
682
|
+
// @ts-expect-error
|
|
683
|
+
return yield* Effect.dieMessage(`Unknown action: ${node.action}`);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// provide the resource-specific context (InstanceId, etc.)
|
|
687
|
+
return yield* apply.pipe(
|
|
688
|
+
Effect.provide(Layer.succeed(InstanceId, instanceId)),
|
|
689
|
+
);
|
|
690
|
+
}),
|
|
691
|
+
));
|
|
692
|
+
}) as Effect.Effect<any, never, never>;
|
|
693
|
+
|
|
694
|
+
return Object.fromEntries(
|
|
695
|
+
yield* Effect.all(
|
|
696
|
+
Object.entries(plan.resources).map(
|
|
697
|
+
Effect.fn(function* ([id, node]) {
|
|
698
|
+
return [id, yield* apply(node)];
|
|
699
|
+
}),
|
|
454
700
|
),
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const collectGarbage = Effect.fnUntraced(function* (
|
|
706
|
+
plan: IPlan,
|
|
707
|
+
session: PlanStatusSession,
|
|
708
|
+
) {
|
|
709
|
+
const state = yield* State;
|
|
710
|
+
const app = yield* App;
|
|
711
|
+
|
|
712
|
+
const deletions: {
|
|
713
|
+
[logicalId in string]: Effect.Effect<void, StateStoreError, never>;
|
|
714
|
+
} = {};
|
|
715
|
+
|
|
716
|
+
// delete all replaced resources
|
|
717
|
+
const replacedResources = yield* state.getReplacedResources({
|
|
718
|
+
stack: app.name,
|
|
719
|
+
stage: app.stage,
|
|
464
720
|
});
|
|
721
|
+
|
|
722
|
+
const deletionGraph = {
|
|
723
|
+
...plan.deletions,
|
|
724
|
+
...Object.fromEntries(
|
|
725
|
+
replacedResources.map((replaced) => [replaced.logicalId, replaced]),
|
|
726
|
+
),
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
const deleteResource: (
|
|
730
|
+
node: Delete<Resource> | ReplacedResourceState,
|
|
731
|
+
) => Effect.Effect<void, StateStoreError, never> = Effect.fnUntraced(
|
|
732
|
+
function* (node: Delete<Resource> | ReplacedResourceState) {
|
|
733
|
+
const isDeleteNode = (
|
|
734
|
+
node: Delete<Resource> | ReplacedResourceState,
|
|
735
|
+
): node is Delete<Resource> => "action" in node;
|
|
736
|
+
|
|
737
|
+
const {
|
|
738
|
+
logicalId,
|
|
739
|
+
resourceType,
|
|
740
|
+
instanceId,
|
|
741
|
+
downstream,
|
|
742
|
+
props,
|
|
743
|
+
attr,
|
|
744
|
+
provider,
|
|
745
|
+
} = isDeleteNode(node)
|
|
746
|
+
? {
|
|
747
|
+
logicalId: node.resource.id,
|
|
748
|
+
resourceType: node.resource.type,
|
|
749
|
+
instanceId: node.state.instanceId,
|
|
750
|
+
downstream: node.downstream,
|
|
751
|
+
props: node.state.props,
|
|
752
|
+
attr: node.state.attr,
|
|
753
|
+
provider: node.provider,
|
|
754
|
+
}
|
|
755
|
+
: {
|
|
756
|
+
logicalId: node.logicalId,
|
|
757
|
+
resourceType: node.old.resourceType,
|
|
758
|
+
instanceId: node.old.instanceId,
|
|
759
|
+
downstream: node.old.downstream,
|
|
760
|
+
props: node.old.props,
|
|
761
|
+
attr: node.old.attr,
|
|
762
|
+
provider: yield* getProviderByType(node.old.resourceType),
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const commit = <State extends ResourceState>(value: State) =>
|
|
766
|
+
state.set({
|
|
767
|
+
stack: app.name,
|
|
768
|
+
stage: app.stage,
|
|
769
|
+
resourceId: logicalId,
|
|
770
|
+
value,
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const report = (status: ApplyStatus) =>
|
|
774
|
+
session.emit({
|
|
775
|
+
kind: "status-change",
|
|
776
|
+
id: logicalId,
|
|
777
|
+
type: resourceType,
|
|
778
|
+
status,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const scopedSession = {
|
|
782
|
+
...session,
|
|
783
|
+
note: (note: string) =>
|
|
784
|
+
session.emit({
|
|
785
|
+
id: logicalId,
|
|
786
|
+
kind: "annotate",
|
|
787
|
+
message: note,
|
|
788
|
+
}),
|
|
789
|
+
} satisfies ScopedPlanStatusSession;
|
|
790
|
+
|
|
791
|
+
return yield* (deletions[logicalId] ??= yield* Effect.cached(
|
|
792
|
+
Effect.gen(function* () {
|
|
793
|
+
yield* Effect.all(
|
|
794
|
+
downstream.map((dep) =>
|
|
795
|
+
dep in deletionGraph
|
|
796
|
+
? deleteResource(deletionGraph[dep] as Delete<Resource>)
|
|
797
|
+
: Effect.void,
|
|
798
|
+
),
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
yield* report("deleting");
|
|
802
|
+
|
|
803
|
+
if (isDeleteNode(node)) {
|
|
804
|
+
yield* commit<DeletingResourceState>({
|
|
805
|
+
status: "deleting",
|
|
806
|
+
logicalId,
|
|
807
|
+
instanceId,
|
|
808
|
+
resourceType,
|
|
809
|
+
props,
|
|
810
|
+
attr,
|
|
811
|
+
downstream,
|
|
812
|
+
providerVersion: provider.version ?? 0,
|
|
813
|
+
bindings: node.bindings,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
yield* provider.delete({
|
|
818
|
+
id: logicalId,
|
|
819
|
+
instanceId,
|
|
820
|
+
olds: props as never,
|
|
821
|
+
output: attr,
|
|
822
|
+
session: scopedSession,
|
|
823
|
+
bindings: [],
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
if (isDeleteNode(node)) {
|
|
827
|
+
// TODO(sam): should we commit a tombstone instead? and then clean up tombstones after all deletions are complete?
|
|
828
|
+
yield* state.delete({
|
|
829
|
+
stack: app.name,
|
|
830
|
+
stage: app.stage,
|
|
831
|
+
resourceId: logicalId,
|
|
832
|
+
});
|
|
833
|
+
yield* report("deleted");
|
|
834
|
+
} else {
|
|
835
|
+
yield* commit<CreatedResourceState>({
|
|
836
|
+
status: "created",
|
|
837
|
+
logicalId,
|
|
838
|
+
instanceId,
|
|
839
|
+
resourceType,
|
|
840
|
+
props: node.props,
|
|
841
|
+
attr: node.attr,
|
|
842
|
+
providerVersion: provider.version ?? 0,
|
|
843
|
+
downstream: node.downstream,
|
|
844
|
+
bindings: node.bindings,
|
|
845
|
+
});
|
|
846
|
+
yield* report("replaced");
|
|
847
|
+
}
|
|
848
|
+
}).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId))),
|
|
849
|
+
));
|
|
850
|
+
},
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
yield* Effect.all(
|
|
854
|
+
Object.values(deletionGraph)
|
|
855
|
+
.filter((node) => node !== undefined)
|
|
856
|
+
.map(deleteResource),
|
|
857
|
+
);
|
|
858
|
+
});
|