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