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
@@ -22,7 +22,7 @@ import * as Redacted from "effect/Redacted";
22
22
  import * as Schema from "effect/Schema";
23
23
  import * as EffectSecret from "effect/Secret";
24
24
  import * as Context from "effect/Context";
25
- import * as Layer from "effect/Layer";
25
+ import * as Layer$1 from "effect/Layer";
26
26
  import * as Terminal from "@effect/platform/Terminal";
27
27
  import * as Effectable from "effect/Effectable";
28
28
  import * as Data from "effect/Data";
@@ -43,7 +43,7 @@ import * as ConfigProvider from "effect/ConfigProvider";
43
43
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
44
44
  import * as PlatformConfigProvider from "@effect/platform/PlatformConfigProvider";
45
45
  import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
46
- import { pipe as pipe$1 } from "effect";
46
+ import { Layer, pipe as pipe$1 } from "effect";
47
47
  import { omit } from "effect/Struct";
48
48
  import { PassThrough, Stream } from "node:stream";
49
49
  import process$1, { cwd, env } from "node:process";
@@ -14042,7 +14042,7 @@ const asEffect = (effect) => Effect.isEffect(effect) ? effect : Effect.succeed(e
14042
14042
 
14043
14043
  //#endregion
14044
14044
  //#region package.json
14045
- var version = "0.3.0";
14045
+ var version = "0.5.0";
14046
14046
 
14047
14047
  //#endregion
14048
14048
  //#region src/resource.ts
@@ -14054,7 +14054,7 @@ const isResource = (r) => {
14054
14054
  //#region src/state.ts
14055
14055
  var StateStoreError = class extends Data.TaggedError("StateStoreError") {};
14056
14056
  var State = class extends Context.Tag("AWS::Lambda::State")() {};
14057
- const localFs = Layer.effect(State, Effect.gen(function* () {
14057
+ const localFs = Layer$1.effect(State, Effect.gen(function* () {
14058
14058
  const fs$1 = yield* FileSystem.FileSystem;
14059
14059
  const path$3 = yield* Path$1.Path;
14060
14060
  const dotAlchemy$1 = path$3.join(process.cwd(), ".alchemy");
@@ -14064,10 +14064,17 @@ const localFs = Layer.effect(State, Effect.gen(function* () {
14064
14064
  const stage$1 = ({ stack, stage: stage$2 }) => path$3.join(stateDir, stack, stage$2);
14065
14065
  const resource = ({ stack, stage: stage$2, resourceId }) => path$3.join(stateDir, stack, stage$2, `${resourceId}.json`);
14066
14066
  const ensure = yield* Effect.cachedFunction((dir) => fs$1.makeDirectory(dir, { recursive: true }));
14067
- return {
14068
- listApps: () => fs$1.readDirectory(stateDir).pipe(recover, Effect.map((files) => files ?? [])),
14067
+ const state = {
14068
+ listStacks: () => fs$1.readDirectory(stateDir).pipe(recover, Effect.map((files) => files ?? [])),
14069
14069
  listStages: (stack) => fs$1.readDirectory(path$3.join(stateDir, stack)).pipe(recover, Effect.map((files) => files ?? [])),
14070
14070
  get: (request) => fs$1.readFile(resource(request)).pipe(Effect.map((file$5) => JSON.parse(file$5.toString())), recover),
14071
+ getReplacedResources: Effect.fnUntraced(function* (request) {
14072
+ return (yield* Effect.all((yield* state.list(request)).map((resourceId) => state.get({
14073
+ stack: request.stack,
14074
+ stage: request.stage,
14075
+ resourceId
14076
+ })))).filter((r) => r?.status === "replaced");
14077
+ }),
14071
14078
  set: (request) => ensure(stage$1(request)).pipe(Effect.flatMap(() => fs$1.writeFileString(resource(request), JSON.stringify(request.value, (k, v) => {
14072
14079
  if (isResource(v)) return {
14073
14080
  id: v.id,
@@ -14080,14 +14087,32 @@ const localFs = Layer.effect(State, Effect.gen(function* () {
14080
14087
  delete: (request) => fs$1.remove(resource(request)).pipe(recover),
14081
14088
  list: (request) => fs$1.readDirectory(stage$1(request)).pipe(recover, Effect.map((files) => files?.map((file$5) => file$5.replace(/\.json$/, "")) ?? []))
14082
14089
  };
14090
+ return state;
14083
14091
  }));
14084
14092
 
14085
14093
  //#endregion
14086
14094
  //#region src/app.ts
14087
14095
  var App$1 = class extends Context.Tag("App")() {};
14088
- const app = (input) => Layer.succeed(App$1, App$1.of(input));
14096
+ const app = (input) => Layer$1.succeed(App$1, App$1.of(input));
14089
14097
  const make = app;
14090
14098
 
14099
+ //#endregion
14100
+ //#region src/cli/service.ts
14101
+ var CLI = class extends Context.Tag("CLIService")() {};
14102
+
14103
+ //#endregion
14104
+ //#region src/instance-id.ts
14105
+ /** A 16-byte (128-bit) random hex-encoded string representing an physical instance of a logical resource */
14106
+ var InstanceId = class extends Context.Tag("instance-id")() {};
14107
+ /**
14108
+ * @returns Hex-encoded instance ID (16 random bytes)
14109
+ */
14110
+ const generateInstanceId = () => Effect.sync(() => {
14111
+ const bytes = new Uint8Array(16);
14112
+ crypto.getRandomValues(bytes);
14113
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
14114
+ });
14115
+
14091
14116
  //#endregion
14092
14117
  //#region src/data.ts
14093
14118
  const isPrimitive = (value) => value === void 0 || value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string" || typeof value === "symbol" || typeof value === "bigint";
@@ -14106,7 +14131,6 @@ const proxy = (self$1) => {
14106
14131
  if (self$1.identifier === "apply") return new ApplyExpr(self$1.expr, args[0]);
14107
14132
  else if (self$1.identifier === "effect") return new EffectExpr(self$1.expr, args[0]);
14108
14133
  }
14109
- throw new Error("Not callable");
14110
14134
  }
14111
14135
  });
14112
14136
  return proxy$1;
@@ -14201,11 +14225,18 @@ const evaluate = (expr, upstream$1) => Effect.gen(function* () {
14201
14225
  stage: stage$1,
14202
14226
  resourceId: expr.resourceId
14203
14227
  }));
14204
- return resource.output;
14228
+ return resource.attr;
14205
14229
  } else if (Array.isArray(expr)) return yield* Effect.all(expr.map((item) => evaluate(item, upstream$1)));
14206
14230
  else if (typeof expr === "object" && expr !== null) return Object.fromEntries(yield* Effect.all(Object.entries(expr).map(([key, value]) => evaluate(value, upstream$1).pipe(Effect.map((value$1) => [key, value$1])))));
14207
14231
  return expr;
14208
14232
  });
14233
+ const hasOutputs = (value) => Object.keys(upstreamAny(value)).length > 0;
14234
+ const upstreamAny = (value) => {
14235
+ if (isExpr(value)) return upstream(value);
14236
+ else if (Array.isArray(value)) return Object.assign({}, ...value.map(resolveUpstream));
14237
+ else if (value && (typeof value === "object" || typeof value === "function")) return Object.assign({}, ...Object.values(value).map((value$1) => resolveUpstream(value$1)));
14238
+ return {};
14239
+ };
14209
14240
  const upstream = (expr) => _upstream(expr);
14210
14241
  const _upstream = (expr) => {
14211
14242
  if (isResourceExpr(expr)) return { [expr.src.id]: expr.src };
@@ -14228,6 +14259,14 @@ const resolveUpstream = (value) => {
14228
14259
  return {};
14229
14260
  };
14230
14261
 
14262
+ //#endregion
14263
+ //#region src/provider.ts
14264
+ const getProviderByType = Effect.fnUntraced(function* (resourceType) {
14265
+ const provider = (yield* Effect.context()).unsafeMap.get(resourceType);
14266
+ if (!provider) return yield* Effect.die(/* @__PURE__ */ new Error(`Provider not found for ${resourceType}`));
14267
+ return provider;
14268
+ });
14269
+
14231
14270
  //#endregion
14232
14271
  //#region src/service.ts
14233
14272
  const isService = (resource) => {
@@ -14236,30 +14275,29 @@ const isService = (resource) => {
14236
14275
 
14237
14276
  //#endregion
14238
14277
  //#region src/plan.ts
14239
- const Node$1 = (node) => ({
14240
- ...node,
14241
- toString() {
14242
- return `${this.action.charAt(0).toUpperCase()}${this.action.slice(1)}(${this.resource})`;
14243
- },
14244
- [Symbol.toStringTag]() {
14245
- return this.toString();
14246
- }
14247
- });
14248
- const plan = (...resources) => Effect.gen(function* () {
14278
+ const plan = (..._resources) => Effect.gen(function* () {
14249
14279
  const state = yield* State;
14280
+ const findResources = (resource, visited) => {
14281
+ if (visited.has(resource.id)) return [];
14282
+ visited.add(resource.id);
14283
+ const upstream$1 = Object.values(upstreamAny(resource.props));
14284
+ return [
14285
+ resource,
14286
+ ...upstream$1,
14287
+ ...upstream$1.flatMap((r) => findResources(r, visited))
14288
+ ];
14289
+ };
14290
+ const resources = _resources.flatMap((r) => findResources(r, /* @__PURE__ */ new Set())).filter((r, i, arr) => arr.findIndex((r2) => r2.id === r.id) === i);
14250
14291
  const app$1 = yield* App$1;
14251
14292
  const resourceIds = yield* state.list({
14252
14293
  stack: app$1.name,
14253
14294
  stage: app$1.stage
14254
14295
  });
14255
- const downstream = (yield* Effect.all(resourceIds.map((id) => state.get({
14296
+ const oldResources = yield* Effect.all(resourceIds.map((id) => state.get({
14256
14297
  stack: app$1.name,
14257
14298
  stage: app$1.stage,
14258
14299
  resourceId: id
14259
- })))).filter((resource) => !!resource?.bindings).flatMap((resource) => resource.bindings.flatMap(({ binding }) => [[binding.capability.resource.id, binding.capability.resource]])).reduce((acc, [id, resourceId]) => ({
14260
- ...acc,
14261
- [id]: [...acc[id] ?? [], resourceId]
14262
- }), {});
14300
+ })));
14263
14301
  const resolvedResources = {};
14264
14302
  const resolveResource = (resourceExpr) => Effect.gen(function* () {
14265
14303
  return yield* resolvedResources[resourceExpr.src.id] ??= yield* Effect.cached(Effect.gen(function* () {
@@ -14271,21 +14309,23 @@ const plan = (...resources) => Effect.gen(function* () {
14271
14309
  stage: app$1.stage,
14272
14310
  resourceId: resource.id
14273
14311
  });
14274
- if (!oldState) return resourceExpr;
14312
+ if (!oldState || oldState.status === "creating") return resourceExpr;
14313
+ const oldProps = oldState.status === "created" || oldState.status === "updated" || oldState.status === "replaced" ? oldState.props : oldState.status === "updating" || oldState.status === "replacing" ? oldState.old.props : oldState.props;
14275
14314
  const diff$1 = yield* provider.diff ? provider.diff({
14276
14315
  id: resource.id,
14277
- olds: oldState.props,
14316
+ olds: oldProps,
14317
+ instanceId: oldState.instanceId,
14278
14318
  news: props,
14279
- output: oldState.output
14280
- }) : Effect.succeed(void 0);
14319
+ output: oldState.attr
14320
+ }).pipe(Effect.provide(Layer$1.succeed(InstanceId, oldState.instanceId))) : Effect.succeed(void 0);
14321
+ const stables = [...provider.stables ?? [], ...diff$1?.stables ?? []];
14322
+ const withStables = (output) => stables.length > 0 ? new ResourceExpr(resourceExpr.src, Object.fromEntries(stables.map((stable) => [stable, output?.[stable]]))) : resourceExpr;
14281
14323
  if (diff$1 == null) {
14282
- if (arePropsChanged(oldState, props)) return resourceExpr;
14283
- } else if (diff$1.action === "update") {
14284
- const output = oldState?.output;
14285
- if (diff$1.stables) return new ResourceExpr(resourceExpr.src, Object.fromEntries(diff$1.stables.map((stable) => [stable, output?.[stable]])));
14286
- else return resourceExpr;
14287
- } else if (diff$1.action === "replace") {}
14288
- return oldState?.output;
14324
+ if (arePropsChanged(oldProps, props)) return withStables(oldState?.attr);
14325
+ } else if (diff$1.action === "update") return withStables(oldState?.attr);
14326
+ else if (diff$1.action === "replace") return resourceExpr;
14327
+ if (oldState.status === "created" || oldState.status === "updated" || oldState.status === "replaced") return oldState?.attr;
14328
+ else return resourceExpr;
14289
14329
  }));
14290
14330
  });
14291
14331
  const resolveInput = (input) => Effect.gen(function* () {
@@ -14300,22 +14340,19 @@ const plan = (...resources) => Effect.gen(function* () {
14300
14340
  else if (isPropExpr(expr)) return (yield* resolveOutput(expr.expr))?.[expr.identifier];
14301
14341
  else if (isApplyExpr(expr)) {
14302
14342
  const upstream$1 = yield* resolveOutput(expr.expr);
14303
- return isOutput(upstream$1) ? expr : expr.f(upstream$1);
14343
+ return hasOutputs(upstream$1) ? expr : expr.f(upstream$1);
14304
14344
  } else if (isEffectExpr(expr)) {
14305
14345
  const upstream$1 = yield* resolveOutput(expr.expr);
14306
- return isOutput(upstream$1) ? expr : yield* expr.f(upstream$1);
14346
+ return hasOutputs(upstream$1) ? expr : yield* expr.f(upstream$1);
14307
14347
  } else if (isAllExpr(expr)) return yield* Effect.all(expr.outs.map(resolveOutput));
14308
14348
  return yield* Effect.die(/* @__PURE__ */ new Error("Not implemented yet"));
14309
14349
  });
14310
- const resolveUpstream$1 = (value) => {
14311
- if (isExpr(value)) return upstream(value);
14312
- else if (Array.isArray(value)) return Object.assign({}, ...value.map(resolveUpstream$1));
14313
- else if (value && (typeof value === "object" || typeof value === "function")) return Object.assign({}, ...Object.values(value).map((value$1) => resolveUpstream$1(value$1)));
14314
- return {};
14315
- };
14350
+ const oldDownstreamDependencies = Object.fromEntries(oldResources.filter((resource) => !!resource).map((resource) => [resource.logicalId, resource.downstream]));
14351
+ const newUpstreamDependencies = Object.fromEntries(resources.map((resource) => [resource.id, [...Object.values(upstreamAny(resource.props)).map((r) => r.id), ...isService(resource) ? resource.props.bindings.capabilities.map((cap) => cap.resource.id) : []]]));
14352
+ const newDownstreamDependencies = Object.fromEntries(resources.map((resource) => [resource.id, Object.entries(newUpstreamDependencies).filter(([_, downstream]) => downstream.includes(resource.id)).map(([id]) => id)]));
14316
14353
  const resourceGraph = Object.fromEntries((yield* Effect.all(resources.flatMap((resource) => [
14317
14354
  ...isService(resource) ? resource.props.bindings.capabilities.map((cap) => cap.resource) : [],
14318
- ...Object.values(resolveUpstream$1(resource.props)),
14355
+ ...Object.values(upstreamAny(resource.props)),
14319
14356
  resource
14320
14357
  ]).filter((node, i, arr) => arr.findIndex((n) => n.id === node.id) === i).map(Effect.fn(function* (node) {
14321
14358
  const id = node.id;
@@ -14327,68 +14364,132 @@ const plan = (...resources) => Effect.gen(function* () {
14327
14364
  resourceId: id
14328
14365
  });
14329
14366
  const provider = yield* resource.provider.tag;
14367
+ const downstream = newDownstreamDependencies[id] ?? [];
14330
14368
  const bindings = isService(node) ? yield* diffBindings({
14331
14369
  oldState,
14332
14370
  bindings: node.props.bindings.bindings,
14333
14371
  target: {
14334
14372
  id: node.id,
14335
14373
  props: node.props,
14336
- oldAttr: oldState?.output,
14374
+ oldAttr: oldState?.attr,
14337
14375
  oldProps: oldState?.props
14338
14376
  }
14339
14377
  }) : [];
14340
- if (oldState === void 0 || oldState.status === "creating") return Node$1({
14341
- action: "create",
14342
- news,
14378
+ const Node$1 = (node$1) => ({
14379
+ ...node$1,
14343
14380
  provider,
14344
14381
  resource,
14345
14382
  bindings,
14346
- attributes: void 0
14383
+ downstream
14347
14384
  });
14348
- const diff$1 = provider.diff ? yield* (() => {
14349
- const diff$2 = provider.diff({
14350
- id,
14351
- olds: oldState.props,
14352
- news,
14353
- output: oldState.output
14354
- });
14355
- return Effect.isEffect(diff$2) ? diff$2 : Effect.succeed(diff$2);
14356
- })() : void 0;
14357
- if (!diff$1 && arePropsChanged(oldState, news)) return Node$1({
14385
+ if (oldState === void 0) return Node$1({
14386
+ action: "create",
14387
+ props: news,
14388
+ state: oldState
14389
+ });
14390
+ else if (oldState.status === "creating" && oldState.attr === void 0) {
14391
+ if (provider.read) {
14392
+ const attr = yield* provider.read({
14393
+ id,
14394
+ instanceId: oldState.instanceId,
14395
+ olds: oldState.props,
14396
+ output: oldState.attr,
14397
+ bindings
14398
+ });
14399
+ if (attr) return Node$1({
14400
+ action: "create",
14401
+ props: news,
14402
+ state: {
14403
+ ...oldState,
14404
+ attr
14405
+ }
14406
+ });
14407
+ }
14408
+ }
14409
+ const oldProps = oldState.props;
14410
+ const diff$1 = yield* asEffect(provider.diff ? provider.diff({
14411
+ id,
14412
+ olds: oldProps,
14413
+ instanceId: oldState.instanceId,
14414
+ output: oldState.attr,
14415
+ news
14416
+ }).pipe(Effect.provide(Layer$1.succeed(InstanceId, oldState.instanceId))) : void 0).pipe(Effect.map((diff$2) => diff$2 ?? { action: arePropsChanged(oldProps, news) ? "update" : "noop" }));
14417
+ if (oldState.status === "creating") if (diff$1.action === "noop") return Node$1({
14418
+ action: "create",
14419
+ props: news,
14420
+ state: oldState
14421
+ });
14422
+ else if (diff$1.action === "update") return Node$1({
14423
+ action: "create",
14424
+ props: news,
14425
+ state: oldState
14426
+ });
14427
+ else return Node$1({
14428
+ action: "replace",
14429
+ props: news,
14430
+ deleteFirst: diff$1.deleteFirst ?? false,
14431
+ state: oldState
14432
+ });
14433
+ else if (oldState.status === "updating") if (diff$1.action === "update" || diff$1.action === "noop") return Node$1({
14358
14434
  action: "update",
14359
- olds: oldState.props,
14360
- news,
14361
- output: oldState.output,
14362
- provider,
14363
- resource,
14364
- bindings,
14365
- attributes: void 0
14435
+ props: news,
14436
+ state: oldState
14366
14437
  });
14367
- else if (diff$1?.action === "replace") return Node$1({
14438
+ else return Node$1({
14368
14439
  action: "replace",
14369
- olds: oldState.props,
14370
- news,
14371
- output: oldState.output,
14372
- provider,
14373
- resource,
14374
- bindings,
14375
- attributes: void 0
14440
+ deleteFirst: diff$1.deleteFirst ?? false,
14441
+ props: news,
14442
+ state: oldState
14376
14443
  });
14377
- else if (diff$1?.action === "update") return Node$1({
14444
+ else if (oldState.status === "replacing") if (diff$1.action === "noop") return Node$1({
14445
+ action: "replace",
14446
+ deleteFirst: oldState.deleteFirst,
14447
+ props: news,
14448
+ state: oldState
14449
+ });
14450
+ else if (diff$1.action === "update") return Node$1({
14451
+ action: "replace",
14452
+ deleteFirst: oldState.deleteFirst,
14453
+ props: news,
14454
+ state: oldState
14455
+ });
14456
+ else return yield* Effect.fail(new CannotReplacePartiallyReplacedResource(id));
14457
+ else if (oldState.status === "replaced") if (diff$1.action === "noop") return Node$1({
14458
+ action: "replace",
14459
+ deleteFirst: oldState.deleteFirst,
14460
+ props: news,
14461
+ state: oldState
14462
+ });
14463
+ else if (diff$1.action === "update") return Node$1({
14378
14464
  action: "update",
14379
- olds: oldState.props,
14380
- news,
14381
- output: oldState.output,
14382
- provider,
14383
- resource,
14384
- bindings,
14385
- attributes: void 0
14465
+ props: news,
14466
+ state: oldState
14467
+ });
14468
+ else return yield* Effect.fail(new CannotReplacePartiallyReplacedResource(id));
14469
+ else if (oldState.status === "deleting") if (diff$1.action === "noop" || diff$1.action === "update") return Node$1({
14470
+ action: "create",
14471
+ props: news,
14472
+ state: {
14473
+ ...oldState,
14474
+ status: "creating",
14475
+ props: news
14476
+ }
14477
+ });
14478
+ else return yield* Effect.fail(new CannotReplacePartiallyReplacedResource(id));
14479
+ else if (diff$1.action === "update") return Node$1({
14480
+ action: "update",
14481
+ props: news,
14482
+ state: oldState
14483
+ });
14484
+ else if (diff$1.action === "replace") return Node$1({
14485
+ action: "replace",
14486
+ props: news,
14487
+ state: oldState,
14488
+ deleteFirst: diff$1?.deleteFirst ?? false
14386
14489
  });
14387
14490
  else return Node$1({
14388
14491
  action: "noop",
14389
- resource,
14390
- bindings,
14391
- attributes: void 0
14492
+ state: oldState
14392
14493
  });
14393
14494
  })))).map((update) => [update.resource.id, update]));
14394
14495
  const deletions = Object.fromEntries((yield* Effect.all((yield* state.list({
@@ -14401,29 +14502,39 @@ const plan = (...resources) => Effect.gen(function* () {
14401
14502
  stage: app$1.stage,
14402
14503
  resourceId: id
14403
14504
  });
14404
- const context = yield* Effect.context();
14505
+ let attr = oldState?.attr;
14405
14506
  if (oldState) {
14406
- const provider = context.unsafeMap.get(oldState?.type);
14407
- if (!provider) yield* Effect.die(/* @__PURE__ */ new Error(`Provider not found for ${oldState?.type}`));
14507
+ const provider = yield* getProviderByType(oldState.resourceType);
14508
+ if (oldState.attr === void 0) {
14509
+ if (provider.read) attr = yield* provider.read({
14510
+ id,
14511
+ instanceId: oldState.instanceId,
14512
+ olds: oldState.props,
14513
+ output: oldState.attr,
14514
+ bindings: oldState.bindings ?? []
14515
+ }).pipe(Effect.provide(Layer$1.succeed(InstanceId, oldState.instanceId)));
14516
+ if (attr === void 0) return;
14517
+ }
14408
14518
  return [id, {
14409
14519
  action: "delete",
14410
- olds: oldState.props,
14411
- output: oldState.output,
14412
- provider,
14413
- attributes: oldState?.output,
14520
+ state: {
14521
+ ...oldState,
14522
+ attr
14523
+ },
14414
14524
  bindings: [],
14525
+ provider,
14415
14526
  resource: {
14416
14527
  id,
14417
- type: oldState.type,
14418
- attr: oldState.output,
14528
+ type: oldState.resourceType,
14529
+ attr,
14419
14530
  props: oldState.props
14420
14531
  },
14421
- downstream: downstream[id] ?? []
14532
+ downstream: oldDownstreamDependencies[id] ?? []
14422
14533
  }];
14423
14534
  }
14424
14535
  })))).filter((v) => !!v));
14425
14536
  for (const [resourceId, deletion] of Object.entries(deletions)) {
14426
- const dependencies = deletion.downstream.filter((d) => d in resourceGraph);
14537
+ const dependencies = deletion.state.downstream.filter((d) => d in resourceGraph);
14427
14538
  if (dependencies.length > 0) return yield* Effect.fail(new DeleteResourceHasDownstreamDependencies({
14428
14539
  message: `Resource ${resourceId} has downstream dependencies`,
14429
14540
  resourceId,
@@ -14435,13 +14546,20 @@ const plan = (...resources) => Effect.gen(function* () {
14435
14546
  deletions
14436
14547
  };
14437
14548
  });
14549
+ var CannotReplacePartiallyReplacedResource = class extends Data.TaggedError("CannotReplacePartiallyReplacedResource") {
14550
+ constructor(logicalId) {
14551
+ super({
14552
+ message: `Resource '${logicalId}' did not finish being replaced in a previous deployment and is expected to be replaced again in this deployment. You should revert its properties and try again after a successful deployment.`,
14553
+ logicalId
14554
+ });
14555
+ }
14556
+ };
14438
14557
  var DeleteResourceHasDownstreamDependencies = class extends Data.TaggedError("DeleteResourceHasDownstreamDependencies") {};
14439
- const arePropsChanged = (oldState, newProps) => {
14440
- return JSON.stringify(omit(oldState?.props ?? {}, "bindings")) !== JSON.stringify(omit(newProps ?? {}, "bindings"));
14558
+ const arePropsChanged = (oldProps, newProps) => {
14559
+ return hasOutputs(newProps) || JSON.stringify(omit(oldProps ?? {}, "bindings")) !== JSON.stringify(omit(newProps ?? {}, "bindings"));
14441
14560
  };
14442
14561
  const diffBindings = Effect.fn(function* ({ oldState, bindings, target }) {
14443
14562
  const oldBindings = oldState?.bindings;
14444
- new Set(oldBindings?.map(({ binding }) => binding.capability.sid));
14445
14563
  const diffBinding = Effect.fn(function* (binding) {
14446
14564
  const cap = binding.capability;
14447
14565
  const sid = cap.sid ?? `${cap.action}:${cap.resource.ID}`;
@@ -14479,34 +14597,38 @@ const isBindingDiff = Effect.fn(function* ({ target, oldBinding: { binding: oldB
14479
14597
  const provider = yield* newBinding.Tag;
14480
14598
  if (provider.diff) {
14481
14599
  const oldState = yield* (yield* State).get(oldCap.resource.id);
14482
- const diff$1 = yield* provider.diff({
14483
- source: {
14484
- id: oldCap.resource.id,
14485
- props: newCap.resource.props,
14486
- oldProps: oldState?.props,
14487
- oldAttr: oldState?.output
14488
- },
14489
- props: newBinding.props,
14490
- attr: oldBinding.attr,
14491
- target
14492
- });
14493
- if (diff$1?.action === "update" || diff$1?.action === "replace") return diff$1;
14600
+ if (oldState) {
14601
+ const diff$1 = yield* provider.diff({
14602
+ source: {
14603
+ id: oldCap.resource.id,
14604
+ props: newCap.resource.props,
14605
+ oldProps: oldState?.props,
14606
+ oldAttr: oldState?.attr
14607
+ },
14608
+ props: newBinding.props,
14609
+ attr: oldBinding.attr,
14610
+ target
14611
+ }).pipe(Effect.provide(Layer$1.succeed(InstanceId, oldState.instanceId)));
14612
+ if (diff$1?.action === "update" || diff$1?.action === "replace") return diff$1;
14613
+ }
14494
14614
  }
14495
14615
  return { action: oldBinding.capability.action !== newBinding.capability.action || oldBinding.capability?.resource?.id !== newBinding.capability?.resource?.id ? "update" : "noop" };
14496
14616
  });
14497
14617
 
14498
- //#endregion
14499
- //#region src/cli/service.ts
14500
- var CLI = class extends Context.Tag("CLIService")() {};
14501
-
14502
14618
  //#endregion
14503
14619
  //#region src/apply.ts
14504
14620
  const applyPlan = (plan$1) => Effect.gen(function* () {
14621
+ const session = yield* (yield* CLI).startApplySession(plan$1);
14622
+ const resources = yield* expandAndPivot(plan$1, session);
14623
+ yield* collectGarbage(plan$1, session);
14624
+ yield* session.done();
14625
+ if (Object.keys(plan$1.resources).length === 0) return;
14626
+ return resources;
14627
+ });
14628
+ const expandAndPivot = Effect.fnUntraced(function* (plan$1, session) {
14505
14629
  const state = yield* State;
14506
14630
  const app$1 = yield* App$1;
14507
14631
  const outputs = {};
14508
- const session = yield* (yield* CLI).startApplySession(plan$1);
14509
- const { emit: emit$1, done } = session;
14510
14632
  const resolveUpstream$1 = Effect.fn(function* (resourceId) {
14511
14633
  const upstreamNode = plan$1.resources[resourceId];
14512
14634
  return {
@@ -14574,19 +14696,12 @@ const applyPlan = (plan$1) => Effect.gen(function* () {
14574
14696
  return oldBindingOutput;
14575
14697
  })));
14576
14698
  const apply = (node) => Effect.gen(function* () {
14577
- const saveState = ({ output, bindings = node.bindings, news }) => state.set({
14699
+ const commit = (value) => state.set({
14578
14700
  stack: app$1.name,
14579
14701
  stage: app$1.stage,
14580
14702
  resourceId: node.resource.id,
14581
- value: {
14582
- id: node.resource.id,
14583
- type: node.resource.type,
14584
- status: node.action === "create" ? "created" : "updated",
14585
- props: news,
14586
- output,
14587
- bindings
14588
- }
14589
- }).pipe(Effect.map(() => output));
14703
+ value
14704
+ });
14590
14705
  const id = node.resource.id;
14591
14706
  const resource = node.resource;
14592
14707
  const scopedSession = {
@@ -14598,152 +14713,395 @@ const applyPlan = (plan$1) => Effect.gen(function* () {
14598
14713
  })
14599
14714
  };
14600
14715
  return yield* outputs[id] ??= yield* Effect.cached(Effect.gen(function* () {
14601
- const report = (status) => emit$1({
14716
+ const report = (status) => session.emit({
14602
14717
  kind: "status-change",
14603
14718
  id,
14604
14719
  type: node.resource.type,
14605
14720
  status
14606
14721
  });
14607
- const createOrUpdate = Effect.fn(function* ({ node: node$1, attr, phase }) {
14608
- const upstream$1 = Object.fromEntries(yield* Effect.all(Object.entries(resolveUpstream(node$1.news)).map(([id$1]) => resolveUpstream$1(id$1).pipe(Effect.map(({ upstreamAttr }) => [id$1, upstreamAttr])))));
14609
- const news = yield* evaluate(node$1.news, upstream$1);
14610
- yield* report(phase === "create" ? "creating" : "updating");
14611
- let bindingOutputs = yield* attachBindings({
14612
- resource,
14613
- bindings: node$1.bindings,
14614
- target: {
14615
- id,
14616
- props: news,
14617
- attr
14618
- }
14619
- });
14620
- const output = yield* (phase === "create" ? node$1.provider.create : node$1.provider.update)({
14621
- id,
14622
- news,
14623
- bindings: bindingOutputs,
14624
- session: scopedSession,
14625
- ...node$1.action === "update" ? {
14626
- output: node$1.output,
14627
- olds: node$1.olds
14628
- } : {}
14629
- }).pipe(Effect.tap(() => report(phase === "create" ? "created" : "updated")));
14630
- bindingOutputs = yield* postAttachBindings({
14631
- resource,
14632
- bindings: node$1.bindings,
14633
- bindingOutputs,
14634
- target: {
14635
- id,
14722
+ if (node.action === "noop") return node.state.attr;
14723
+ const upstream$1 = Object.fromEntries(yield* Effect.all(Object.entries(resolveUpstream(node.props)).map(([id$1]) => resolveUpstream$1(id$1).pipe(Effect.map(({ upstreamAttr }) => [id$1, upstreamAttr])))));
14724
+ const instanceId = yield* Effect.gen(function* () {
14725
+ if (node.action === "create" && !node.state?.instanceId) {
14726
+ const instanceId$1 = yield* generateInstanceId();
14727
+ yield* commit({
14728
+ status: "creating",
14729
+ instanceId: instanceId$1,
14730
+ logicalId: id,
14731
+ downstream: node.downstream,
14732
+ props: node.props,
14733
+ providerVersion: node.provider.version ?? 0,
14734
+ resourceType: node.resource.type,
14735
+ bindings: node.bindings
14736
+ });
14737
+ return instanceId$1;
14738
+ } else if (node.action === "replace") {
14739
+ if (node.state.status === "replaced" || node.state.status === "replacing") return node.state.instanceId;
14740
+ const instanceId$1 = yield* generateInstanceId();
14741
+ yield* commit({
14742
+ status: "replacing",
14743
+ instanceId: instanceId$1,
14744
+ logicalId: id,
14745
+ downstream: node.downstream,
14746
+ props: node.props,
14747
+ providerVersion: node.provider.version ?? 0,
14748
+ resourceType: node.resource.type,
14749
+ bindings: node.bindings,
14750
+ old: node.state,
14751
+ deleteFirst: node.deleteFirst
14752
+ });
14753
+ return instanceId$1;
14754
+ } else if (node.state?.instanceId) return node.state.instanceId;
14755
+ return yield* Effect.dieMessage(`Instance ID not found for resource '${id}' and action is '${node.action}'`);
14756
+ });
14757
+ return yield* Effect.gen(function* () {
14758
+ if (node.action === "create") {
14759
+ const news = yield* evaluate(node.props, upstream$1);
14760
+ const checkpoint = (attr$1) => commit({
14761
+ status: "creating",
14762
+ logicalId: id,
14763
+ instanceId,
14764
+ resourceType: node.resource.type,
14636
14765
  props: news,
14637
- attr
14766
+ attr: attr$1,
14767
+ providerVersion: node.provider.version ?? 0,
14768
+ bindings: node.bindings,
14769
+ downstream: node.downstream
14770
+ });
14771
+ if (!node.state) yield* checkpoint(void 0);
14772
+ let attr;
14773
+ if (node.action === "create" && node.provider.precreate && node.state?.attr === void 0) {
14774
+ yield* report("pre-creating");
14775
+ attr = yield* node.provider.precreate({
14776
+ id,
14777
+ news: node.props,
14778
+ session: scopedSession,
14779
+ instanceId
14780
+ });
14781
+ yield* checkpoint(attr);
14638
14782
  }
14639
- });
14640
- yield* saveState({
14641
- news,
14642
- output,
14643
- bindings: node$1.bindings.map((binding, i) => ({
14644
- ...binding,
14645
- attr: bindingOutputs[i]
14646
- }))
14647
- });
14648
- return output;
14649
- });
14650
- if (node.action === "noop") return (yield* state.get({
14651
- stack: app$1.name,
14652
- stage: app$1.stage,
14653
- resourceId: id
14654
- }))?.output;
14655
- else if (node.action === "create") {
14656
- let attr;
14657
- if (node.provider.precreate) {
14658
- yield* Effect.logDebug("precreate", id);
14659
- attr = yield* node.provider.precreate({
14783
+ yield* report("attaching");
14784
+ let bindingOutputs = yield* attachBindings({
14785
+ resource,
14786
+ bindings: node.bindings,
14787
+ target: {
14788
+ id,
14789
+ props: news,
14790
+ attr
14791
+ }
14792
+ });
14793
+ yield* report("creating");
14794
+ attr = yield* node.provider.create({
14660
14795
  id,
14661
- news: node.news,
14796
+ news,
14797
+ instanceId,
14798
+ bindings: bindingOutputs,
14662
14799
  session: scopedSession
14663
14800
  });
14664
- }
14665
- yield* Effect.logDebug("create", id);
14666
- return yield* createOrUpdate({
14667
- node,
14668
- attr,
14669
- phase: "create"
14670
- });
14671
- } else if (node.action === "update") {
14672
- yield* Effect.logDebug("update", id);
14673
- return yield* createOrUpdate({
14674
- node,
14675
- attr: node.attributes,
14676
- phase: "update"
14677
- });
14678
- } else if (node.action === "delete") {
14679
- yield* Effect.logDebug("delete", id);
14680
- yield* Effect.all(node.downstream.map((dep) => dep in plan$1.resources ? apply(plan$1.resources[dep]) : Effect.void));
14681
- yield* report("deleting");
14682
- return yield* node.provider.delete({
14683
- id,
14684
- olds: node.olds,
14685
- output: node.output,
14686
- session: scopedSession,
14687
- bindings: []
14688
- }).pipe(Effect.flatMap(() => state.delete({
14689
- stack: app$1.name,
14690
- stage: app$1.stage,
14691
- resourceId: id
14692
- })), Effect.tap(() => report("deleted")));
14693
- } else if (node.action === "replace") {
14694
- const destroy = Effect.gen(function* () {
14695
- yield* report("deleting");
14696
- return yield* node.provider.delete({
14801
+ yield* checkpoint(attr);
14802
+ yield* report("post-attach");
14803
+ bindingOutputs = yield* postAttachBindings({
14804
+ resource,
14805
+ bindings: node.bindings,
14806
+ bindingOutputs,
14807
+ target: {
14808
+ id,
14809
+ props: news,
14810
+ attr
14811
+ }
14812
+ });
14813
+ yield* commit({
14814
+ status: "created",
14815
+ logicalId: id,
14816
+ instanceId,
14817
+ resourceType: node.resource.type,
14818
+ props: news,
14819
+ attr,
14820
+ bindings: node.bindings.map((binding, i) => ({
14821
+ ...binding,
14822
+ attr: bindingOutputs[i]
14823
+ })),
14824
+ providerVersion: node.provider.version ?? 0,
14825
+ downstream: node.downstream
14826
+ });
14827
+ yield* report("created");
14828
+ return attr;
14829
+ } else if (node.action === "update") {
14830
+ const upstream$2 = Object.fromEntries(yield* Effect.all(Object.entries(resolveUpstream(node.props)).map(([id$1]) => resolveUpstream$1(id$1).pipe(Effect.map(({ upstreamAttr }) => [id$1, upstreamAttr])))));
14831
+ const news = yield* evaluate(node.props, upstream$2);
14832
+ const checkpoint = (attr$1) => {
14833
+ if (node.state.status === "replaced") return commit({
14834
+ ...node.state,
14835
+ attr: attr$1,
14836
+ props: news
14837
+ });
14838
+ else return commit({
14839
+ status: "updating",
14840
+ logicalId: id,
14841
+ instanceId,
14842
+ resourceType: node.resource.type,
14843
+ props: news,
14844
+ attr: attr$1,
14845
+ providerVersion: node.provider.version ?? 0,
14846
+ bindings: node.bindings,
14847
+ downstream: node.downstream,
14848
+ old: node.state.status === "updating" ? node.state.old : node.state
14849
+ });
14850
+ };
14851
+ yield* checkpoint(node.state.attr);
14852
+ yield* report("attaching");
14853
+ let bindingOutputs = yield* attachBindings({
14854
+ resource,
14855
+ bindings: node.bindings,
14856
+ target: {
14857
+ id,
14858
+ props: news,
14859
+ attr: node.state.attr
14860
+ }
14861
+ });
14862
+ yield* report("updating");
14863
+ const attr = yield* node.provider.update({
14697
14864
  id,
14698
- olds: node.olds,
14699
- output: node.output,
14865
+ news,
14866
+ instanceId,
14867
+ bindings: bindingOutputs,
14700
14868
  session: scopedSession,
14701
- bindings: []
14869
+ olds: node.state.status === "created" || node.state.status === "updated" || node.state.status === "replaced" ? node.state.props : node.state.old.props,
14870
+ output: node.state.attr
14702
14871
  });
14703
- });
14704
- const create$1 = Effect.gen(function* () {
14705
- yield* report("creating");
14706
- return yield* node.provider.create({
14872
+ yield* checkpoint(attr);
14873
+ yield* report("post-attach");
14874
+ bindingOutputs = yield* postAttachBindings({
14875
+ resource,
14876
+ bindings: node.bindings,
14877
+ bindingOutputs,
14878
+ target: {
14879
+ id,
14880
+ props: news,
14881
+ attr
14882
+ }
14883
+ });
14884
+ if (node.state.status === "replaced") yield* commit({
14885
+ ...node.state,
14886
+ attr,
14887
+ props: news
14888
+ });
14889
+ else yield* commit({
14890
+ status: "updated",
14891
+ logicalId: id,
14892
+ instanceId,
14893
+ resourceType: node.resource.type,
14894
+ props: news,
14895
+ attr,
14896
+ bindings: node.bindings.map((binding, i) => ({
14897
+ ...binding,
14898
+ attr: bindingOutputs[i]
14899
+ })),
14900
+ providerVersion: node.provider.version ?? 0,
14901
+ downstream: node.downstream
14902
+ });
14903
+ yield* report("updated");
14904
+ return attr;
14905
+ } else if (node.action === "replace") {
14906
+ if (node.state.status === "replaced") return node.state.attr;
14907
+ let state$1;
14908
+ if (node.state.status !== "replacing") yield* commit(state$1 = {
14909
+ status: "replacing",
14910
+ logicalId: id,
14911
+ instanceId,
14912
+ resourceType: node.resource.type,
14913
+ props: node.props,
14914
+ attr: node.state.attr,
14915
+ providerVersion: node.provider.version ?? 0,
14916
+ deleteFirst: node.deleteFirst,
14917
+ old: node.state,
14918
+ downstream: node.downstream
14919
+ });
14920
+ else state$1 = node.state;
14921
+ const upstream$2 = Object.fromEntries(yield* Effect.all(Object.entries(resolveUpstream(node.props)).map(([id$1]) => resolveUpstream$1(id$1).pipe(Effect.map(({ upstreamAttr }) => [id$1, upstreamAttr])))));
14922
+ const news = yield* evaluate(node.props, upstream$2);
14923
+ const checkpoint = ({ status, attr: attr$1, bindings }) => commit({
14924
+ status,
14925
+ logicalId: id,
14926
+ instanceId,
14927
+ resourceType: node.resource.type,
14928
+ props: news,
14929
+ attr: attr$1,
14930
+ providerVersion: node.provider.version ?? 0,
14931
+ bindings: bindings ?? node.bindings,
14932
+ downstream: node.downstream,
14933
+ old: state$1.old,
14934
+ deleteFirst: node.deleteFirst
14935
+ });
14936
+ let attr;
14937
+ if (node.provider.precreate && node.state?.attr === void 0) {
14938
+ yield* report("pre-creating");
14939
+ attr = yield* node.provider.precreate({
14940
+ id,
14941
+ news: node.props,
14942
+ session: scopedSession,
14943
+ instanceId
14944
+ });
14945
+ yield* checkpoint({
14946
+ status: "replacing",
14947
+ attr
14948
+ });
14949
+ }
14950
+ yield* report("attaching");
14951
+ let bindingOutputs = yield* attachBindings({
14952
+ resource,
14953
+ bindings: node.bindings,
14954
+ target: {
14955
+ id,
14956
+ props: news,
14957
+ attr
14958
+ }
14959
+ });
14960
+ yield* report("creating replacement");
14961
+ attr = yield* node.provider.create({
14707
14962
  id,
14708
- news: node.news,
14709
- bindings: yield* attachBindings({
14710
- resource,
14711
- bindings: node.bindings,
14712
- target: {
14713
- id,
14714
- props: node.news,
14715
- attr: node.attributes
14716
- }
14717
- }),
14963
+ news,
14964
+ instanceId,
14965
+ bindings: bindingOutputs,
14718
14966
  session: scopedSession
14719
- }).pipe(Effect.tap((output) => saveState({
14720
- news: node.news,
14721
- output
14722
- })));
14723
- });
14724
- if (!node.deleteFirst) {
14725
- yield* destroy;
14726
- return outputs;
14727
- } else {
14728
- yield* destroy;
14729
- return yield* create$1;
14967
+ });
14968
+ yield* checkpoint({
14969
+ status: "replacing",
14970
+ attr
14971
+ });
14972
+ yield* report("post-attach");
14973
+ bindingOutputs = yield* postAttachBindings({
14974
+ resource,
14975
+ bindings: node.bindings,
14976
+ bindingOutputs,
14977
+ target: {
14978
+ id,
14979
+ props: news,
14980
+ attr
14981
+ }
14982
+ });
14983
+ yield* checkpoint({
14984
+ status: "replaced",
14985
+ attr,
14986
+ bindings: node.bindings.map((binding, i) => ({
14987
+ ...binding,
14988
+ attr: bindingOutputs[i]
14989
+ }))
14990
+ });
14991
+ yield* report("created");
14992
+ return attr;
14730
14993
  }
14731
- }
14994
+ return yield* Effect.dieMessage(`Unknown action: ${node.action}`);
14995
+ }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId)));
14732
14996
  }));
14733
14997
  });
14734
- const nodes = [...Object.entries(plan$1.resources), ...Object.entries(plan$1.deletions)];
14735
- const resources = Object.fromEntries(yield* Effect.all(nodes.map(Effect.fn(function* ([id, node]) {
14998
+ return Object.fromEntries(yield* Effect.all(Object.entries(plan$1.resources).map(Effect.fn(function* ([id, node]) {
14736
14999
  return [id, yield* apply(node)];
14737
15000
  }))));
14738
- yield* done();
14739
- if (Object.keys(plan$1.resources).length === 0) return;
14740
- return resources;
15001
+ });
15002
+ const collectGarbage = Effect.fnUntraced(function* (plan$1, session) {
15003
+ const state = yield* State;
15004
+ const app$1 = yield* App$1;
15005
+ const deletions = {};
15006
+ const replacedResources = yield* state.getReplacedResources({
15007
+ stack: app$1.name,
15008
+ stage: app$1.stage
15009
+ });
15010
+ const deletionGraph = {
15011
+ ...plan$1.deletions,
15012
+ ...Object.fromEntries(replacedResources.map((replaced) => [replaced.logicalId, replaced]))
15013
+ };
15014
+ const deleteResource = Effect.fnUntraced(function* (node) {
15015
+ const isDeleteNode = (node$1) => "action" in node$1;
15016
+ const { logicalId, resourceType, instanceId, downstream, props, attr, provider } = isDeleteNode(node) ? {
15017
+ logicalId: node.resource.id,
15018
+ resourceType: node.resource.type,
15019
+ instanceId: node.state.instanceId,
15020
+ downstream: node.downstream,
15021
+ props: node.state.props,
15022
+ attr: node.state.attr,
15023
+ provider: node.provider
15024
+ } : {
15025
+ logicalId: node.logicalId,
15026
+ resourceType: node.old.resourceType,
15027
+ instanceId: node.old.instanceId,
15028
+ downstream: node.old.downstream,
15029
+ props: node.old.props,
15030
+ attr: node.old.attr,
15031
+ provider: yield* getProviderByType(node.old.resourceType)
15032
+ };
15033
+ const commit = (value) => state.set({
15034
+ stack: app$1.name,
15035
+ stage: app$1.stage,
15036
+ resourceId: logicalId,
15037
+ value
15038
+ });
15039
+ const report = (status) => session.emit({
15040
+ kind: "status-change",
15041
+ id: logicalId,
15042
+ type: resourceType,
15043
+ status
15044
+ });
15045
+ const scopedSession = {
15046
+ ...session,
15047
+ note: (note) => session.emit({
15048
+ id: logicalId,
15049
+ kind: "annotate",
15050
+ message: note
15051
+ })
15052
+ };
15053
+ return yield* deletions[logicalId] ??= yield* Effect.cached(Effect.gen(function* () {
15054
+ yield* Effect.all(downstream.map((dep) => dep in deletionGraph ? deleteResource(deletionGraph[dep]) : Effect.void));
15055
+ yield* report("deleting");
15056
+ if (isDeleteNode(node)) yield* commit({
15057
+ status: "deleting",
15058
+ logicalId,
15059
+ instanceId,
15060
+ resourceType,
15061
+ props,
15062
+ attr,
15063
+ downstream,
15064
+ providerVersion: provider.version ?? 0,
15065
+ bindings: node.bindings
15066
+ });
15067
+ yield* provider.delete({
15068
+ id: logicalId,
15069
+ instanceId,
15070
+ olds: props,
15071
+ output: attr,
15072
+ session: scopedSession,
15073
+ bindings: []
15074
+ });
15075
+ if (isDeleteNode(node)) {
15076
+ yield* state.delete({
15077
+ stack: app$1.name,
15078
+ stage: app$1.stage,
15079
+ resourceId: logicalId
15080
+ });
15081
+ yield* report("deleted");
15082
+ } else {
15083
+ yield* commit({
15084
+ status: "created",
15085
+ logicalId,
15086
+ instanceId,
15087
+ resourceType,
15088
+ props: node.props,
15089
+ attr: node.attr,
15090
+ providerVersion: provider.version ?? 0,
15091
+ downstream: node.downstream,
15092
+ bindings: node.bindings
15093
+ });
15094
+ yield* report("replaced");
15095
+ }
15096
+ }).pipe(Effect.provide(Layer.succeed(InstanceId, instanceId))));
15097
+ });
15098
+ yield* Effect.all(Object.values(deletionGraph).filter((node) => node !== void 0).map(deleteResource));
14741
15099
  });
14742
15100
 
14743
15101
  //#endregion
14744
15102
  //#region src/dot-alchemy.ts
14745
15103
  var DotAlchemy = class extends Context.Tag(".alchemy")() {};
14746
- const dotAlchemy = Layer.effect(DotAlchemy, Effect.gen(function* () {
15104
+ const dotAlchemy = Layer$1.effect(DotAlchemy, Effect.gen(function* () {
14747
15105
  const fs$1 = yield* FileSystem.FileSystem;
14748
15106
  const dir = (yield* Path$1.Path).join(process.cwd(), ".alchemy");
14749
15107
  yield* fs$1.makeDirectory(dir, { recursive: true });
@@ -40518,7 +40876,7 @@ var require_backend = /* @__PURE__ */ __commonJS({ "../node_modules/react-devtoo
40518
40876
  }),
40519
40877
  695: ((module$1, __unused_webpack_exports, __webpack_require__$1) => {
40520
40878
  module$1.exports = Yallist;
40521
- Yallist.Node = Node$2;
40879
+ Yallist.Node = Node$1;
40522
40880
  Yallist.create = Yallist;
40523
40881
  function Yallist(list$3) {
40524
40882
  var self$1 = this;
@@ -40731,24 +41089,24 @@ var require_backend = /* @__PURE__ */ __commonJS({ "../node_modules/react-devtoo
40731
41089
  return this;
40732
41090
  };
40733
41091
  function insert(self$1, node, value) {
40734
- var inserted = node === self$1.head ? new Node$2(value, null, node, self$1) : new Node$2(value, node, node.next, self$1);
41092
+ var inserted = node === self$1.head ? new Node$1(value, null, node, self$1) : new Node$1(value, node, node.next, self$1);
40735
41093
  if (inserted.next === null) self$1.tail = inserted;
40736
41094
  if (inserted.prev === null) self$1.head = inserted;
40737
41095
  self$1.length++;
40738
41096
  return inserted;
40739
41097
  }
40740
41098
  function push$2(self$1, item) {
40741
- self$1.tail = new Node$2(item, self$1.tail, null, self$1);
41099
+ self$1.tail = new Node$1(item, self$1.tail, null, self$1);
40742
41100
  if (!self$1.head) self$1.head = self$1.tail;
40743
41101
  self$1.length++;
40744
41102
  }
40745
41103
  function unshift(self$1, item) {
40746
- self$1.head = new Node$2(item, null, self$1.head, self$1);
41104
+ self$1.head = new Node$1(item, null, self$1.head, self$1);
40747
41105
  if (!self$1.tail) self$1.tail = self$1.head;
40748
41106
  self$1.length++;
40749
41107
  }
40750
- function Node$2(value, prev, next, list$3) {
40751
- if (!(this instanceof Node$2)) return new Node$2(value, prev, next, list$3);
41108
+ function Node$1(value, prev, next, list$3) {
41109
+ if (!(this instanceof Node$1)) return new Node$1(value, prev, next, list$3);
40752
41110
  this.list = list$3;
40753
41111
  this.value = value;
40754
41112
  if (prev) {
@@ -54919,7 +55277,7 @@ function useGlobalSpinner(intervalMs = 80) {
54919
55277
 
54920
55278
  //#endregion
54921
55279
  //#region src/cli/ink-service.tsx
54922
- const inkCLI = () => Layer.succeed(CLI, CLI.of({
55280
+ const inkCLI = () => Layer$1.succeed(CLI, CLI.of({
54923
55281
  approvePlan,
54924
55282
  displayPlan,
54925
55283
  startApplySession
@@ -55009,10 +55367,10 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
55009
55367
  if (!stack) return yield* Effect.die(/* @__PURE__ */ new Error(`Main file '${main$1}' must export a default stack definition (export default defineStack({...}))`));
55010
55368
  const stackName = stack.name;
55011
55369
  const configProvider = Option.isSome(envFile$1) ? ConfigProvider.orElse(yield* PlatformConfigProvider.fromDotEnv(envFile$1.value), ConfigProvider.fromEnv) : ConfigProvider.fromEnv();
55012
- const stageConfig = yield* asEffect(stack.stages.config(stage$1)).pipe(Effect.provide(stack.layers ?? Layer.empty), Effect.withConfigProvider(configProvider));
55013
- const platform$1 = Layer.mergeAll(NodeContext.layer, FetchHttpClient.layer, Logger.pretty);
55014
- const alchemy = Layer.mergeAll(stack.state ?? localFs, stack.cli ?? inkCLI(), dotAlchemy);
55015
- const layers = Layer.provideMerge(Layer.provideMerge(stack.providers, alchemy), Layer.mergeAll(platform$1, make({
55370
+ const stageConfig = yield* asEffect(stack.stages.config(stage$1)).pipe(Effect.provide(stack.layers ?? Layer$1.empty), Effect.withConfigProvider(configProvider));
55371
+ const platform$1 = Layer$1.mergeAll(NodeContext.layer, FetchHttpClient.layer, Logger.pretty);
55372
+ const alchemy = Layer$1.mergeAll(stack.state ?? localFs, stack.cli ?? inkCLI(), dotAlchemy);
55373
+ const layers = Layer$1.provideMerge(Layer$1.provideMerge(stack.providers, alchemy), Layer$1.mergeAll(platform$1, make({
55016
55374
  name: stackName,
55017
55375
  stage: stage$1,
55018
55376
  config: stageConfig
@@ -55026,7 +55384,7 @@ const execStack = Effect.fn(function* ({ main: main$1, stage: stage$1, envFile:
55026
55384
  if (!(yield* cli.approvePlan(updatePlan))) return;
55027
55385
  }
55028
55386
  const outputs = yield* applyPlan(updatePlan);
55029
- if (outputs && stack.tap) yield* stack.tap(outputs).pipe(Effect.provide(stack.layers ?? Layer.empty));
55387
+ if (outputs && stack.tap) yield* stack.tap(outputs).pipe(Effect.provide(stack.layers ?? Layer$1.empty));
55030
55388
  }
55031
55389
  }).pipe(Effect.provide(layers), Effect.withConfigProvider(configProvider));
55032
55390
  });