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.
Files changed (167) hide show
  1. package/bin/alchemy-effect.js +618 -260
  2. package/bin/alchemy-effect.js.map +1 -1
  3. package/lib/apply.d.ts +4 -4
  4. package/lib/apply.d.ts.map +1 -1
  5. package/lib/apply.js +444 -159
  6. package/lib/apply.js.map +1 -1
  7. package/lib/aws/dynamodb/table.provider.d.ts.map +1 -1
  8. package/lib/aws/dynamodb/table.provider.js +11 -2
  9. package/lib/aws/dynamodb/table.provider.js.map +1 -1
  10. package/lib/aws/ec2/index.d.ts +8 -0
  11. package/lib/aws/ec2/index.d.ts.map +1 -1
  12. package/lib/aws/ec2/index.js +8 -0
  13. package/lib/aws/ec2/index.js.map +1 -1
  14. package/lib/aws/ec2/internet-gateway.d.ts +65 -0
  15. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
  16. package/lib/aws/ec2/internet-gateway.js +4 -0
  17. package/lib/aws/ec2/internet-gateway.js.map +1 -0
  18. package/lib/aws/ec2/internet-gateway.provider.d.ts +6 -0
  19. package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -0
  20. package/lib/aws/ec2/internet-gateway.provider.js +193 -0
  21. package/lib/aws/ec2/internet-gateway.provider.js.map +1 -0
  22. package/lib/aws/ec2/route-table-association.d.ts +63 -0
  23. package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
  24. package/lib/aws/ec2/route-table-association.js +4 -0
  25. package/lib/aws/ec2/route-table-association.js.map +1 -0
  26. package/lib/aws/ec2/route-table-association.provider.d.ts +4 -0
  27. package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -0
  28. package/lib/aws/ec2/route-table-association.provider.js +121 -0
  29. package/lib/aws/ec2/route-table-association.provider.js.map +1 -0
  30. package/lib/aws/ec2/route-table.d.ts +159 -0
  31. package/lib/aws/ec2/route-table.d.ts.map +1 -0
  32. package/lib/aws/ec2/route-table.js +4 -0
  33. package/lib/aws/ec2/route-table.js.map +1 -0
  34. package/lib/aws/ec2/route-table.provider.d.ts +6 -0
  35. package/lib/aws/ec2/route-table.provider.d.ts.map +1 -0
  36. package/lib/aws/ec2/route-table.provider.js +213 -0
  37. package/lib/aws/ec2/route-table.provider.js.map +1 -0
  38. package/lib/aws/ec2/route.d.ts +155 -0
  39. package/lib/aws/ec2/route.d.ts.map +1 -0
  40. package/lib/aws/ec2/route.js +3 -0
  41. package/lib/aws/ec2/route.js.map +1 -0
  42. package/lib/aws/ec2/route.provider.d.ts +4 -0
  43. package/lib/aws/ec2/route.provider.d.ts.map +1 -0
  44. package/lib/aws/ec2/route.provider.js +166 -0
  45. package/lib/aws/ec2/route.provider.js.map +1 -0
  46. package/lib/aws/ec2/subnet.provider.d.ts.map +1 -1
  47. package/lib/aws/ec2/subnet.provider.js +1 -1
  48. package/lib/aws/ec2/subnet.provider.js.map +1 -1
  49. package/lib/aws/ec2/vpc.d.ts +1 -0
  50. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  51. package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
  52. package/lib/aws/ec2/vpc.provider.js +32 -10
  53. package/lib/aws/ec2/vpc.provider.js.map +1 -1
  54. package/lib/aws/index.d.ts +2 -3
  55. package/lib/aws/index.d.ts.map +1 -1
  56. package/lib/aws/index.js +2 -1
  57. package/lib/aws/index.js.map +1 -1
  58. package/lib/aws/lambda/function.provider.d.ts +2 -2
  59. package/lib/aws/lambda/function.provider.d.ts.map +1 -1
  60. package/lib/aws/lambda/function.provider.js +39 -42
  61. package/lib/aws/lambda/function.provider.js.map +1 -1
  62. package/lib/aws/sqs/queue.provider.d.ts +3 -4
  63. package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
  64. package/lib/aws/sqs/queue.provider.js +17 -9
  65. package/lib/aws/sqs/queue.provider.js.map +1 -1
  66. package/lib/cli/index.d.ts +183 -100
  67. package/lib/cli/index.d.ts.map +1 -1
  68. package/lib/cloudflare/kv/namespace.client.d.ts +1 -1
  69. package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
  70. package/lib/cloudflare/kv/namespace.provider.js +12 -6
  71. package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
  72. package/lib/cloudflare/r2/bucket.binding.js +1 -1
  73. package/lib/cloudflare/r2/bucket.binding.js.map +1 -1
  74. package/lib/cloudflare/r2/bucket.d.ts +1 -1
  75. package/lib/cloudflare/r2/bucket.d.ts.map +1 -1
  76. package/lib/cloudflare/r2/bucket.provider.d.ts +1 -2
  77. package/lib/cloudflare/r2/bucket.provider.d.ts.map +1 -1
  78. package/lib/cloudflare/r2/bucket.provider.js +23 -12
  79. package/lib/cloudflare/r2/bucket.provider.js.map +1 -1
  80. package/lib/cloudflare/worker/worker.d.ts +2 -2
  81. package/lib/cloudflare/worker/worker.d.ts.map +1 -1
  82. package/lib/cloudflare/worker/worker.provider.d.ts +2 -3
  83. package/lib/cloudflare/worker/worker.provider.d.ts.map +1 -1
  84. package/lib/cloudflare/worker/worker.provider.js +44 -13
  85. package/lib/cloudflare/worker/worker.provider.js.map +1 -1
  86. package/lib/diff.d.ts +8 -6
  87. package/lib/diff.d.ts.map +1 -1
  88. package/lib/diff.js +13 -0
  89. package/lib/diff.js.map +1 -1
  90. package/lib/event.d.ts +1 -1
  91. package/lib/event.d.ts.map +1 -1
  92. package/lib/instance-id.d.ts +12 -0
  93. package/lib/instance-id.d.ts.map +1 -0
  94. package/lib/instance-id.js +16 -0
  95. package/lib/instance-id.js.map +1 -0
  96. package/lib/output.d.ts +4 -2
  97. package/lib/output.d.ts.map +1 -1
  98. package/lib/output.js +18 -4
  99. package/lib/output.js.map +1 -1
  100. package/lib/physical-name.d.ts +25 -1
  101. package/lib/physical-name.d.ts.map +1 -1
  102. package/lib/physical-name.js +50 -2
  103. package/lib/physical-name.js.map +1 -1
  104. package/lib/plan.d.ts +49 -42
  105. package/lib/plan.d.ts.map +1 -1
  106. package/lib/plan.js +417 -137
  107. package/lib/plan.js.map +1 -1
  108. package/lib/provider.d.ts +26 -10
  109. package/lib/provider.d.ts.map +1 -1
  110. package/lib/provider.js +9 -0
  111. package/lib/provider.js.map +1 -1
  112. package/lib/resource.d.ts +3 -2
  113. package/lib/resource.d.ts.map +1 -1
  114. package/lib/resource.js.map +1 -1
  115. package/lib/state.d.ts +86 -9
  116. package/lib/state.d.ts.map +1 -1
  117. package/lib/state.js +21 -18
  118. package/lib/state.js.map +1 -1
  119. package/lib/tags.d.ts +15 -0
  120. package/lib/tags.d.ts.map +1 -1
  121. package/lib/tags.js +27 -0
  122. package/lib/tags.js.map +1 -1
  123. package/lib/test.d.ts +2 -2
  124. package/lib/test.d.ts.map +1 -1
  125. package/lib/test.js +4 -4
  126. package/lib/test.js.map +1 -1
  127. package/lib/todo.d.ts +3 -0
  128. package/lib/todo.d.ts.map +1 -0
  129. package/lib/todo.js +3 -0
  130. package/lib/todo.js.map +1 -0
  131. package/lib/tsconfig.test.tsbuildinfo +1 -1
  132. package/package.json +2 -2
  133. package/src/apply.ts +742 -348
  134. package/src/aws/dynamodb/table.provider.ts +16 -4
  135. package/src/aws/ec2/index.ts +8 -0
  136. package/src/aws/ec2/internet-gateway.provider.ts +316 -0
  137. package/src/aws/ec2/internet-gateway.ts +79 -0
  138. package/src/aws/ec2/route-table-association.provider.ts +214 -0
  139. package/src/aws/ec2/route-table-association.ts +82 -0
  140. package/src/aws/ec2/route-table.provider.ts +306 -0
  141. package/src/aws/ec2/route-table.ts +175 -0
  142. package/src/aws/ec2/route.provider.ts +213 -0
  143. package/src/aws/ec2/route.ts +192 -0
  144. package/src/aws/ec2/subnet.provider.ts +2 -2
  145. package/src/aws/ec2/vpc.provider.ts +36 -11
  146. package/src/aws/ec2/vpc.ts +2 -0
  147. package/src/aws/index.ts +4 -1
  148. package/src/aws/lambda/function.provider.ts +66 -53
  149. package/src/aws/sqs/queue.provider.ts +18 -11
  150. package/src/cloudflare/kv/namespace.provider.ts +19 -14
  151. package/src/cloudflare/r2/bucket.binding.ts +1 -1
  152. package/src/cloudflare/r2/bucket.provider.ts +34 -24
  153. package/src/cloudflare/r2/bucket.ts +1 -1
  154. package/src/cloudflare/worker/worker.provider.ts +43 -13
  155. package/src/cloudflare/worker/worker.ts +2 -2
  156. package/src/diff.ts +35 -17
  157. package/src/event.ts +6 -0
  158. package/src/instance-id.ts +20 -0
  159. package/src/output.ts +29 -5
  160. package/src/physical-name.ts +79 -2
  161. package/src/plan.ts +566 -214
  162. package/src/provider.ts +46 -10
  163. package/src/resource.ts +65 -8
  164. package/src/state.ts +150 -35
  165. package/src/tags.ts +31 -0
  166. package/src/test.ts +5 -5
  167. package/src/todo.ts +4 -0
package/lib/apply.js CHANGED
@@ -1,20 +1,36 @@
1
1
  import * as Context from "effect/Context";
2
2
  import * as Effect from "effect/Effect";
3
+ import { App } from "./app.js";
4
+ import { CLI, } from "./cli/service.js";
5
+ import { generateInstanceId, InstanceId } from "./instance-id.js";
3
6
  import * as Output from "./output.js";
4
7
  import { plan, } from "./plan.js";
5
- import { State } from "./state.js";
6
- import { App } from "./app.js";
8
+ import { State, StateStoreError, } from "./state.js";
7
9
  import { asEffect } from "./util.js";
8
- import { CLI } from "./cli/service.js";
9
- export const apply = (...resources) => plan(...resources).pipe(Effect.flatMap(applyPlan));
10
+ import { getProviderByType } from "./provider.js";
11
+ import { Layer } from "effect";
12
+ export const apply = (...resources) => plan(...resources).pipe(Effect.flatMap((p) => applyPlan(p)));
10
13
  export const applyPlan = (plan) => Effect.gen(function* () {
14
+ const cli = yield* CLI;
15
+ const session = yield* cli.startApplySession(plan);
16
+ // 1. expand the graph (create new resources, update existing and create replacements)
17
+ const resources = yield* expandAndPivot(plan, session);
18
+ // TODO(sam): support roll back to previous state if errors occur during expansion
19
+ // -> RISK: some UPDATEs may not be reverisble (i.e. trigger replacements)
20
+ // TODO(sam): should pivot be done separately? E.g shift traffic?
21
+ // 2. delete orphans and replaced resources
22
+ yield* collectGarbage(plan, session);
23
+ yield* session.done();
24
+ if (Object.keys(plan.resources).length === 0) {
25
+ // all resources are deleted, return undefined
26
+ return undefined;
27
+ }
28
+ return resources;
29
+ });
30
+ const expandAndPivot = Effect.fnUntraced(function* (plan, session) {
11
31
  const state = yield* State;
12
- // TODO(sam): rename terminology to Stack
13
32
  const app = yield* App;
14
33
  const outputs = {};
15
- const cli = yield* CLI;
16
- const session = yield* cli.startApplySession(plan);
17
- const { emit, done } = session;
18
34
  const resolveUpstream = Effect.fn(function* (resourceId) {
19
35
  const upstreamNode = plan.resources[resourceId];
20
36
  const upstreamAttr = upstreamNode
@@ -88,21 +104,12 @@ export const applyPlan = (plan) => Effect.gen(function* () {
88
104
  return oldBindingOutput;
89
105
  })));
90
106
  const apply = (node) => Effect.gen(function* () {
91
- const saveState = ({ output, bindings = node.bindings, news, }) => state
92
- .set({
107
+ const commit = (value) => state.set({
93
108
  stack: app.name,
94
109
  stage: app.stage,
95
110
  resourceId: node.resource.id,
96
- value: {
97
- id: node.resource.id,
98
- type: node.resource.type,
99
- status: node.action === "create" ? "created" : "updated",
100
- props: news,
101
- output,
102
- bindings,
103
- },
104
- })
105
- .pipe(Effect.map(() => output));
111
+ value,
112
+ });
106
113
  const id = node.resource.id;
107
114
  const resource = node.resource;
108
115
  const scopedSession = {
@@ -114,169 +121,447 @@ export const applyPlan = (plan) => Effect.gen(function* () {
114
121
  }),
115
122
  };
116
123
  return yield* (outputs[id] ??= yield* Effect.cached(Effect.gen(function* () {
117
- const report = (status) => emit({
124
+ const report = (status) => session.emit({
118
125
  kind: "status-change",
119
126
  id,
120
127
  type: node.resource.type,
121
128
  status,
122
129
  });
123
- const createOrUpdate = Effect.fn(function* ({ node, attr, phase, }) {
124
- const upstream = Object.fromEntries(yield* Effect.all(Object.entries(Output.resolveUpstream(node.news)).map(([id]) => resolveUpstream(id).pipe(Effect.map(({ upstreamAttr }) => [id, upstreamAttr])))));
125
- const news = yield* Output.evaluate(node.news, upstream);
126
- yield* report(phase === "create" ? "creating" : "updating");
127
- let bindingOutputs = yield* attachBindings({
128
- resource,
129
- bindings: node.bindings,
130
- target: {
130
+ if (node.action === "noop") {
131
+ return node.state.attr;
132
+ }
133
+ // resolve upstream dependencies before committing any changes to state
134
+ const upstream = Object.fromEntries(yield* Effect.all(Object.entries(Output.resolveUpstream(node.props)).map(([id]) => resolveUpstream(id).pipe(Effect.map(({ upstreamAttr }) => [id, upstreamAttr])))));
135
+ const instanceId = yield* Effect.gen(function* () {
136
+ if (node.action === "create" && !node.state?.instanceId) {
137
+ const instanceId = yield* generateInstanceId();
138
+ yield* commit({
139
+ status: "creating",
140
+ instanceId,
141
+ logicalId: id,
142
+ downstream: node.downstream,
143
+ props: node.props,
144
+ providerVersion: node.provider.version ?? 0,
145
+ resourceType: node.resource.type,
146
+ bindings: node.bindings,
147
+ });
148
+ return instanceId;
149
+ }
150
+ else if (node.action === "replace") {
151
+ if (node.state.status === "replaced" ||
152
+ node.state.status === "replacing") {
153
+ // replace has already begun and we have the new instanceId, do not re-create it
154
+ return node.state.instanceId;
155
+ }
156
+ const instanceId = yield* generateInstanceId();
157
+ yield* commit({
158
+ status: "replacing",
159
+ instanceId,
160
+ logicalId: id,
161
+ downstream: node.downstream,
162
+ props: node.props,
163
+ providerVersion: node.provider.version ?? 0,
164
+ resourceType: node.resource.type,
165
+ bindings: node.bindings,
166
+ old: node.state,
167
+ deleteFirst: node.deleteFirst,
168
+ });
169
+ return instanceId;
170
+ }
171
+ else if (node.state?.instanceId) {
172
+ // we're in a create, update or delete state with a stable instanceId, use it
173
+ return node.state.instanceId;
174
+ }
175
+ // this should never happen
176
+ return yield* Effect.dieMessage(`Instance ID not found for resource '${id}' and action is '${node.action}'`);
177
+ });
178
+ const apply = Effect.gen(function* () {
179
+ if (node.action === "create") {
180
+ const news = (yield* Output.evaluate(node.props, upstream));
181
+ const checkpoint = (attr) => commit({
182
+ status: "creating",
183
+ logicalId: id,
184
+ instanceId,
185
+ resourceType: node.resource.type,
186
+ props: news,
187
+ attr,
188
+ providerVersion: node.provider.version ?? 0,
189
+ bindings: node.bindings,
190
+ downstream: node.downstream,
191
+ });
192
+ if (!node.state) {
193
+ yield* checkpoint(undefined);
194
+ }
195
+ let attr;
196
+ if (node.action === "create" &&
197
+ node.provider.precreate &&
198
+ // pre-create is only designed to ensure the resource exists, if we have state.attr, then it already exists and should be skipped
199
+ node.state?.attr === undefined) {
200
+ yield* report("pre-creating");
201
+ // stub the resource prior to resolving upstream resources or bindings if a stub is available
202
+ attr = yield* node.provider.precreate({
203
+ id,
204
+ news: node.props,
205
+ session: scopedSession,
206
+ instanceId,
207
+ });
208
+ yield* checkpoint(attr);
209
+ }
210
+ yield* report("attaching");
211
+ let bindingOutputs = yield* attachBindings({
212
+ resource,
213
+ bindings: node.bindings,
214
+ target: {
215
+ id,
216
+ props: news,
217
+ attr,
218
+ },
219
+ });
220
+ yield* report("creating");
221
+ attr = yield* node.provider.create({
131
222
  id,
223
+ news,
224
+ instanceId,
225
+ bindings: bindingOutputs,
226
+ session: scopedSession,
227
+ });
228
+ yield* checkpoint(attr);
229
+ yield* report("post-attach");
230
+ bindingOutputs = yield* postAttachBindings({
231
+ resource,
232
+ bindings: node.bindings,
233
+ bindingOutputs,
234
+ target: {
235
+ id,
236
+ props: news,
237
+ attr,
238
+ },
239
+ });
240
+ yield* commit({
241
+ status: "created",
242
+ logicalId: id,
243
+ instanceId,
244
+ resourceType: node.resource.type,
132
245
  props: news,
133
246
  attr,
134
- },
135
- });
136
- const output = yield* (phase === "create" ? node.provider.create : node.provider.update)({
137
- id,
138
- news,
139
- bindings: bindingOutputs,
140
- session: scopedSession,
141
- ...(node.action === "update"
142
- ? {
143
- output: node.output,
144
- olds: node.olds,
247
+ bindings: node.bindings.map((binding, i) => ({
248
+ ...binding,
249
+ attr: bindingOutputs[i],
250
+ })),
251
+ providerVersion: node.provider.version ?? 0,
252
+ downstream: node.downstream,
253
+ });
254
+ yield* report("created");
255
+ return attr;
256
+ }
257
+ else if (node.action === "update") {
258
+ const upstream = Object.fromEntries(yield* Effect.all(Object.entries(Output.resolveUpstream(node.props)).map(([id]) => resolveUpstream(id).pipe(Effect.map(({ upstreamAttr }) => [id, upstreamAttr])))));
259
+ const news = (yield* Output.evaluate(node.props, upstream));
260
+ const checkpoint = (attr) => {
261
+ if (node.state.status === "replaced") {
262
+ return commit({
263
+ ...node.state,
264
+ attr,
265
+ props: news,
266
+ });
145
267
  }
146
- : {}),
147
- }).pipe(
148
- // TODO(sam): partial checkpoints
149
- // checkpoint,
150
- Effect.tap(() => report(phase === "create" ? "created" : "updated")));
151
- bindingOutputs = yield* postAttachBindings({
152
- resource,
153
- bindings: node.bindings,
154
- bindingOutputs,
155
- target: {
268
+ else {
269
+ return commit({
270
+ status: "updating",
271
+ logicalId: id,
272
+ instanceId,
273
+ resourceType: node.resource.type,
274
+ props: news,
275
+ attr,
276
+ providerVersion: node.provider.version ?? 0,
277
+ bindings: node.bindings,
278
+ downstream: node.downstream,
279
+ old: node.state.status === "updating"
280
+ ? node.state.old
281
+ : node.state,
282
+ });
283
+ }
284
+ };
285
+ yield* checkpoint(node.state.attr);
286
+ yield* report("attaching");
287
+ let bindingOutputs = yield* attachBindings({
288
+ resource,
289
+ bindings: node.bindings,
290
+ target: {
291
+ id,
292
+ props: news,
293
+ attr: node.state.attr,
294
+ },
295
+ });
296
+ yield* report("updating");
297
+ const attr = yield* node.provider.update({
156
298
  id,
299
+ news,
300
+ instanceId,
301
+ bindings: bindingOutputs,
302
+ session: scopedSession,
303
+ olds: node.state.status === "created" ||
304
+ node.state.status === "updated" ||
305
+ node.state.status === "replaced"
306
+ ? node.state.props
307
+ : node.state.old.props,
308
+ output: node.state.attr,
309
+ });
310
+ yield* checkpoint(attr);
311
+ yield* report("post-attach");
312
+ bindingOutputs = yield* postAttachBindings({
313
+ resource,
314
+ bindings: node.bindings,
315
+ bindingOutputs,
316
+ target: {
317
+ id,
318
+ props: news,
319
+ attr,
320
+ },
321
+ });
322
+ if (node.state.status === "replaced") {
323
+ yield* commit({
324
+ ...node.state,
325
+ attr,
326
+ props: news,
327
+ });
328
+ }
329
+ else {
330
+ yield* commit({
331
+ status: "updated",
332
+ logicalId: id,
333
+ instanceId,
334
+ resourceType: node.resource.type,
335
+ props: news,
336
+ attr,
337
+ bindings: node.bindings.map((binding, i) => ({
338
+ ...binding,
339
+ attr: bindingOutputs[i],
340
+ })),
341
+ providerVersion: node.provider.version ?? 0,
342
+ downstream: node.downstream,
343
+ });
344
+ }
345
+ yield* report("updated");
346
+ return attr;
347
+ }
348
+ else if (node.action === "replace") {
349
+ if (node.state.status === "replaced") {
350
+ // we've already created the replacement resource, return the output
351
+ return node.state.attr;
352
+ }
353
+ let state;
354
+ if (node.state.status !== "replacing") {
355
+ yield* commit((state = {
356
+ status: "replacing",
357
+ logicalId: id,
358
+ instanceId,
359
+ resourceType: node.resource.type,
360
+ props: node.props,
361
+ attr: node.state.attr,
362
+ providerVersion: node.provider.version ?? 0,
363
+ deleteFirst: node.deleteFirst,
364
+ old: node.state,
365
+ downstream: node.downstream,
366
+ }));
367
+ }
368
+ else {
369
+ state = node.state;
370
+ }
371
+ const upstream = Object.fromEntries(yield* Effect.all(Object.entries(Output.resolveUpstream(node.props)).map(([id]) => resolveUpstream(id).pipe(Effect.map(({ upstreamAttr }) => [id, upstreamAttr])))));
372
+ const news = (yield* Output.evaluate(node.props, upstream));
373
+ const checkpoint = ({ status, attr, bindings, }) => commit({
374
+ status,
375
+ logicalId: id,
376
+ instanceId,
377
+ resourceType: node.resource.type,
157
378
  props: news,
158
379
  attr,
159
- },
160
- });
161
- yield* saveState({
162
- news,
163
- output,
164
- bindings: node.bindings.map((binding, i) => ({
165
- ...binding,
166
- attr: bindingOutputs[i],
167
- })),
168
- });
169
- return output;
170
- });
171
- if (node.action === "noop") {
172
- return (yield* state.get({
173
- stack: app.name,
174
- stage: app.stage,
175
- resourceId: id,
176
- }))?.output;
177
- }
178
- else if (node.action === "create") {
179
- let attr;
180
- if (node.provider.precreate) {
181
- yield* Effect.logDebug("precreate", id);
182
- // stub the resource prior to resolving upstream resources or bindings if a stub is available
183
- attr = yield* node.provider.precreate({
380
+ providerVersion: node.provider.version ?? 0,
381
+ bindings: bindings ?? node.bindings,
382
+ downstream: node.downstream,
383
+ old: state.old,
384
+ deleteFirst: node.deleteFirst,
385
+ });
386
+ let attr;
387
+ if (node.provider.precreate &&
388
+ // pre-create is only designed to ensure the resource exists, if we have state.attr, then it already exists and should be skipped
389
+ node.state?.attr === undefined) {
390
+ yield* report("pre-creating");
391
+ // stub the resource prior to resolving upstream resources or bindings if a stub is available
392
+ attr = yield* node.provider.precreate({
393
+ id,
394
+ news: node.props,
395
+ session: scopedSession,
396
+ instanceId,
397
+ });
398
+ yield* checkpoint({
399
+ status: "replacing",
400
+ attr,
401
+ });
402
+ }
403
+ yield* report("attaching");
404
+ let bindingOutputs = yield* attachBindings({
405
+ resource,
406
+ bindings: node.bindings,
407
+ target: {
408
+ id,
409
+ props: news,
410
+ attr,
411
+ },
412
+ });
413
+ yield* report("creating replacement");
414
+ attr = yield* node.provider.create({
184
415
  id,
185
- news: node.news,
416
+ news,
417
+ instanceId,
418
+ bindings: bindingOutputs,
186
419
  session: scopedSession,
187
420
  });
421
+ yield* checkpoint({
422
+ status: "replacing",
423
+ attr,
424
+ });
425
+ yield* report("post-attach");
426
+ bindingOutputs = yield* postAttachBindings({
427
+ resource,
428
+ bindings: node.bindings,
429
+ bindingOutputs,
430
+ target: {
431
+ id,
432
+ props: news,
433
+ attr,
434
+ },
435
+ });
436
+ yield* checkpoint({
437
+ status: "replaced",
438
+ attr,
439
+ bindings: node.bindings.map((binding, i) => ({
440
+ ...binding,
441
+ attr: bindingOutputs[i],
442
+ })),
443
+ });
444
+ yield* report("created");
445
+ return attr;
188
446
  }
189
- yield* Effect.logDebug("create", id);
190
- return yield* createOrUpdate({
191
- node,
192
- attr,
193
- phase: "create",
194
- });
447
+ // @ts-expect-error
448
+ return yield* Effect.dieMessage(`Unknown action: ${node.action}`);
449
+ });
450
+ // provide the resource-specific context (InstanceId, etc.)
451
+ return yield* apply.pipe(Effect.provide(Layer.succeed(InstanceId, instanceId)));
452
+ })));
453
+ });
454
+ return Object.fromEntries(yield* Effect.all(Object.entries(plan.resources).map(Effect.fn(function* ([id, node]) {
455
+ return [id, yield* apply(node)];
456
+ }))));
457
+ });
458
+ const collectGarbage = Effect.fnUntraced(function* (plan, session) {
459
+ const state = yield* State;
460
+ const app = yield* App;
461
+ const deletions = {};
462
+ // delete all replaced resources
463
+ const replacedResources = yield* state.getReplacedResources({
464
+ stack: app.name,
465
+ stage: app.stage,
466
+ });
467
+ const deletionGraph = {
468
+ ...plan.deletions,
469
+ ...Object.fromEntries(replacedResources.map((replaced) => [replaced.logicalId, replaced])),
470
+ };
471
+ const deleteResource = Effect.fnUntraced(function* (node) {
472
+ const isDeleteNode = (node) => "action" in node;
473
+ const { logicalId, resourceType, instanceId, downstream, props, attr, provider, } = isDeleteNode(node)
474
+ ? {
475
+ logicalId: node.resource.id,
476
+ resourceType: node.resource.type,
477
+ instanceId: node.state.instanceId,
478
+ downstream: node.downstream,
479
+ props: node.state.props,
480
+ attr: node.state.attr,
481
+ provider: node.provider,
195
482
  }
196
- else if (node.action === "update") {
197
- yield* Effect.logDebug("update", id);
198
- return yield* createOrUpdate({
199
- node,
200
- attr: node.attributes,
201
- phase: "update",
483
+ : {
484
+ logicalId: node.logicalId,
485
+ resourceType: node.old.resourceType,
486
+ instanceId: node.old.instanceId,
487
+ downstream: node.old.downstream,
488
+ props: node.old.props,
489
+ attr: node.old.attr,
490
+ provider: yield* getProviderByType(node.old.resourceType),
491
+ };
492
+ const commit = (value) => state.set({
493
+ stack: app.name,
494
+ stage: app.stage,
495
+ resourceId: logicalId,
496
+ value,
497
+ });
498
+ const report = (status) => session.emit({
499
+ kind: "status-change",
500
+ id: logicalId,
501
+ type: resourceType,
502
+ status,
503
+ });
504
+ const scopedSession = {
505
+ ...session,
506
+ note: (note) => session.emit({
507
+ id: logicalId,
508
+ kind: "annotate",
509
+ message: note,
510
+ }),
511
+ };
512
+ return yield* (deletions[logicalId] ??= yield* Effect.cached(Effect.gen(function* () {
513
+ yield* Effect.all(downstream.map((dep) => dep in deletionGraph
514
+ ? deleteResource(deletionGraph[dep])
515
+ : Effect.void));
516
+ yield* report("deleting");
517
+ if (isDeleteNode(node)) {
518
+ yield* commit({
519
+ status: "deleting",
520
+ logicalId,
521
+ instanceId,
522
+ resourceType,
523
+ props,
524
+ attr,
525
+ downstream,
526
+ providerVersion: provider.version ?? 0,
527
+ bindings: node.bindings,
202
528
  });
203
529
  }
204
- else if (node.action === "delete") {
205
- yield* Effect.logDebug("delete", id);
206
- yield* Effect.all(node.downstream.map((dep) => dep in plan.resources
207
- ? apply(plan.resources[dep])
208
- : Effect.void));
209
- yield* report("deleting");
210
- return yield* node.provider
211
- .delete({
212
- id,
213
- olds: node.olds,
214
- output: node.output,
215
- session: scopedSession,
216
- bindings: [],
217
- })
218
- .pipe(Effect.flatMap(() => state.delete({
530
+ yield* provider.delete({
531
+ id: logicalId,
532
+ instanceId,
533
+ olds: props,
534
+ output: attr,
535
+ session: scopedSession,
536
+ bindings: [],
537
+ });
538
+ if (isDeleteNode(node)) {
539
+ // TODO(sam): should we commit a tombstone instead? and then clean up tombstones after all deletions are complete?
540
+ yield* state.delete({
219
541
  stack: app.name,
220
542
  stage: app.stage,
221
- resourceId: id,
222
- })), Effect.tap(() => report("deleted")));
223
- }
224
- else if (node.action === "replace") {
225
- const destroy = Effect.gen(function* () {
226
- yield* report("deleting");
227
- return yield* node.provider.delete({
228
- id,
229
- olds: node.olds,
230
- output: node.output,
231
- session: scopedSession,
232
- bindings: [],
233
- });
543
+ resourceId: logicalId,
234
544
  });
235
- const create = Effect.gen(function* () {
236
- yield* report("creating");
237
- // TODO(sam): delete and create will conflict here, we need to extend the state store for replace
238
- return yield* node.provider
239
- .create({
240
- id,
241
- news: node.news,
242
- // TODO(sam): these need to only include attach actions
243
- bindings: yield* attachBindings({
244
- resource,
245
- bindings: node.bindings,
246
- target: {
247
- id,
248
- // TODO(sam): resolve the news
249
- props: node.news,
250
- attr: node.attributes,
251
- },
252
- }),
253
- session: scopedSession,
254
- })
255
- .pipe(Effect.tap((output) => saveState({ news: node.news, output })));
545
+ yield* report("deleted");
546
+ }
547
+ else {
548
+ yield* commit({
549
+ status: "created",
550
+ logicalId,
551
+ instanceId,
552
+ resourceType,
553
+ props: node.props,
554
+ attr: node.attr,
555
+ providerVersion: provider.version ?? 0,
556
+ downstream: node.downstream,
557
+ bindings: node.bindings,
256
558
  });
257
- if (!node.deleteFirst) {
258
- yield* destroy;
259
- return outputs;
260
- }
261
- else {
262
- yield* destroy;
263
- return yield* create;
264
- }
559
+ yield* report("replaced");
265
560
  }
266
- })));
561
+ }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId)))));
267
562
  });
268
- const nodes = [
269
- ...Object.entries(plan.resources),
270
- ...Object.entries(plan.deletions),
271
- ];
272
- const resources = Object.fromEntries(yield* Effect.all(nodes.map(Effect.fn(function* ([id, node]) {
273
- return [id, yield* apply(node)];
274
- }))));
275
- yield* done();
276
- if (Object.keys(plan.resources).length === 0) {
277
- // all resources are deleted, return undefined
278
- return undefined;
279
- }
280
- return resources;
563
+ yield* Effect.all(Object.values(deletionGraph)
564
+ .filter((node) => node !== undefined)
565
+ .map(deleteResource));
281
566
  });
282
567
  //# sourceMappingURL=apply.js.map