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