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
@@ -38,7 +38,13 @@ export const bucketProvider = () =>
38
38
  return { action: "replace" };
39
39
  }
40
40
  if (output.storageClass !== (news.storageClass ?? "Standard")) {
41
- return { action: "update" };
41
+ return {
42
+ action: "update",
43
+ stables:
44
+ output.name === createName(id, news.name)
45
+ ? ["name"]
46
+ : undefined,
47
+ };
42
48
  }
43
49
  return { action: "noop" };
44
50
  }),
@@ -7,8 +7,8 @@ import type { ScopedPlanStatusSession } from "../../cli/service.ts";
7
7
  import { DotAlchemy } from "../../dot-alchemy.ts";
8
8
  import { ESBuild } from "../../esbuild.ts";
9
9
  import { sha256 } from "../../sha256.ts";
10
- import { CloudflareApi } from "../api.ts";
11
10
  import { Account } from "../account.ts";
11
+ import { CloudflareApi } from "../api.ts";
12
12
  import { Assets } from "./assets.provider.ts";
13
13
  import { Worker, type WorkerAttr, type WorkerProps } from "./worker.ts";
14
14
 
@@ -187,6 +187,7 @@ export const workerProvider = () =>
187
187
  });
188
188
 
189
189
  return {
190
+ stables: ["id"],
190
191
  diff: Effect.fnUntraced(function* ({ id, olds, news, output }) {
191
192
  if (output.accountId !== accountId) {
192
193
  return { action: "replace" };
@@ -203,7 +204,10 @@ export const workerProvider = () =>
203
204
  assets?.hash !== output.hash.assets ||
204
205
  bundle.hash !== output.hash.bundle
205
206
  ) {
206
- return { action: "update" };
207
+ return {
208
+ action: "update",
209
+ stables: output.name === workerName ? ["name"] : undefined,
210
+ };
207
211
  }
208
212
  }),
209
213
  create: Effect.fnUntraced(function* ({ id, news, bindings, session }) {
package/src/diff.ts CHANGED
@@ -1,20 +1,21 @@
1
- export type Diff =
2
- | {
3
- action: "noop";
4
- deleteFirst?: undefined;
5
- stables?: undefined;
6
- }
7
- | {
8
- action: "update";
9
- deleteFirst?: undefined;
10
- /** properties that won't change as part of this update */
11
- stables?: string[];
12
- }
13
- | {
14
- action: "replace";
15
- deleteFirst?: boolean;
16
- stables?: undefined;
17
- };
1
+ export type Diff = NoopDiff | UpdateDiff | ReplaceDiff;
2
+
3
+ export interface NoopDiff {
4
+ action: "noop";
5
+ stables?: undefined;
6
+ }
7
+
8
+ export interface UpdateDiff {
9
+ action: "update";
10
+ /** properties that won't change as part of this update */
11
+ stables?: string[];
12
+ }
13
+
14
+ export interface ReplaceDiff {
15
+ action: "replace";
16
+ deleteFirst?: boolean;
17
+ stables?: undefined;
18
+ }
18
19
 
19
20
  export const somePropsAreDifferent = <Props extends Record<string, any>>(
20
21
  olds: Props,
@@ -28,3 +29,20 @@ export const somePropsAreDifferent = <Props extends Record<string, any>>(
28
29
  }
29
30
  return false;
30
31
  };
32
+
33
+ export const anyPropsAreDifferent = <Props extends Record<string, any>>(
34
+ olds: Props,
35
+ news: Props,
36
+ ) => {
37
+ for (const prop in olds) {
38
+ if (olds[prop] !== news[prop]) {
39
+ return true;
40
+ }
41
+ }
42
+ for (const prop in news) {
43
+ if (!(prop in olds)) {
44
+ return true;
45
+ }
46
+ }
47
+ return false;
48
+ };
package/src/event.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  export type ApplyStatus =
2
+ | "attaching"
3
+ | "post-attach"
2
4
  | "pending"
5
+ | "pre-creating"
3
6
  | "creating"
7
+ | "creating replacement"
4
8
  | "created"
5
9
  | "updating"
6
10
  | "updated"
7
11
  | "deleting"
8
12
  | "deleted"
13
+ | "replacing"
14
+ | "replaced"
9
15
  | "success"
10
16
  | "fail";
11
17
 
@@ -0,0 +1,16 @@
1
+ import * as Effect from "effect/Effect";
2
+
3
+ /** A 16-byte (128-bit) random hex-encoded string representing an physical instance of a logical resource */
4
+ export type InstanceId = string;
5
+
6
+ /**
7
+ * @returns Hex-encoded instance ID (16 random bytes)
8
+ */
9
+ export const generateInstanceId = () =>
10
+ Effect.sync(() => {
11
+ const bytes = new Uint8Array(16);
12
+ crypto.getRandomValues(bytes);
13
+ return Array.from(bytes)
14
+ .map((b) => b.toString(16).padStart(2, "0"))
15
+ .join("");
16
+ });
package/src/output.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { pipe } from "effect";
2
- import * as App from "./app.ts";
3
2
  import * as Data from "effect/Data";
4
3
  import * as Effect from "effect/Effect";
4
+ import * as App from "./app.ts";
5
5
  import { isPrimitive } from "./data.ts";
6
6
  import type { From } from "./policy.ts";
7
+ import { getRefMetadata, isRef, ref as stageRef, type Ref } from "./ref.ts";
7
8
  import type { AnyResource, Resource } from "./resource.ts";
8
- import type { IsAny, UnionToIntersection } from "./util.ts";
9
9
  import * as State from "./state.ts";
10
- import { isRef, type Ref, getRefMetadata, ref as stageRef } from "./ref.ts";
10
+ import type { IsAny, UnionToIntersection } from "./util.ts";
11
11
 
12
12
  // a special symbol only used at runtime to probe the Output proxy
13
13
  const ExprSymbol = Symbol.for("alchemy/Expr");
@@ -125,7 +125,7 @@ const proxy = (self: any): any => {
125
125
  return new EffectExpr(self.expr, args[0]);
126
126
  }
127
127
  }
128
- throw new Error("Not callable");
128
+ return undefined;
129
129
  },
130
130
  },
131
131
  );
@@ -367,7 +367,7 @@ export const evaluate: <A, Upstream extends AnyResource, Req>(
367
367
  }),
368
368
  );
369
369
  }
370
- return resource.output;
370
+ return resource.attr;
371
371
  } else if (Array.isArray(expr)) {
372
372
  return yield* Effect.all(expr.map((item) => evaluate(item, upstream)));
373
373
  } else if (typeof expr === "object" && expr !== null) {
@@ -389,6 +389,30 @@ export type Upstream<O extends Output<any, any, any>> =
389
389
  }
390
390
  : never;
391
391
 
392
+ export const hasOutputs = (value: any): value is Output<any, any, any> =>
393
+ Object.keys(upstreamAny(value)).length > 0;
394
+
395
+ export const upstreamAny = (
396
+ value: any,
397
+ ): {
398
+ [ID in string]: Resource;
399
+ } => {
400
+ if (isExpr(value)) {
401
+ return upstream(value);
402
+ } else if (Array.isArray(value)) {
403
+ return Object.assign({}, ...value.map(resolveUpstream));
404
+ } else if (
405
+ value &&
406
+ (typeof value === "object" || typeof value === "function")
407
+ ) {
408
+ return Object.assign(
409
+ {},
410
+ ...Object.values(value).map((value) => resolveUpstream(value)),
411
+ );
412
+ }
413
+ return {};
414
+ };
415
+
392
416
  export const upstream = <E extends Output<any, AnyResource, any>>(
393
417
  expr: E,
394
418
  ): {
@@ -1,7 +1,62 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import { App } from "./app.ts";
3
3
 
4
- export const physicalName = Effect.fn(function* (id: string) {
4
+ export const physicalName = Effect.fn(function* ({
5
+ id,
6
+ instanceId,
7
+ // 16 base32 characters = 80 bits of entropy = 4 × 10⁻⁷
8
+ suffixLength = 16,
9
+ }: {
10
+ id: string;
11
+ /** Hex-encoded instance ID (16 random bytes) */
12
+ instanceId: string;
13
+ suffixLength?: number;
14
+ }) {
5
15
  const app = yield* App;
6
- return `${app.name}-${id}-${app.stage}`;
16
+ return `${app.name}-${id}-${app.stage}-${base32(Buffer.from(instanceId, "hex")).slice(0, suffixLength)}`;
7
17
  });
18
+
19
+ // Base32 is ideal for physical names because it's denser than hex (5 bits per char vs 4)
20
+ // and compatible with DNS/S3 (as opposed to base64 which contains uppercase letters and symbols).
21
+
22
+ // base32.ts
23
+ // Concise, fast RFC 4648 Base32 encoder (no padding), lowercase output.
24
+ // Charset: a-z2-7 (DNS/S3 friendly)
25
+ const ALPH = "abcdefghijklmnopqrstuvwxyz234567";
26
+
27
+ /**
28
+ * Encode bytes into RFC4648 Base32 (no padding), lowercase.
29
+ *
30
+ * Performance notes:
31
+ * - O(n) single pass, no big-int
32
+ * - Avoids per-byte string concatenation by using a char array
33
+ */
34
+ export function base32(bytes: Uint8Array): string {
35
+ const n = bytes.length;
36
+ if (n === 0) return "";
37
+
38
+ // Base32 length without padding: ceil(n*8/5)
39
+ const outLen = ((n * 8 + 4) / 5) | 0;
40
+ const out = Array.from<string>({ length: outLen });
41
+
42
+ let buffer = 0;
43
+ let bits = 0;
44
+ let o = 0;
45
+
46
+ for (let i = 0; i < n; i++) {
47
+ buffer = (buffer << 8) | bytes[i];
48
+ bits += 8;
49
+
50
+ while (bits >= 5) {
51
+ out[o++] = ALPH[(buffer >>> (bits - 5)) & 31];
52
+ bits -= 5;
53
+ }
54
+ }
55
+
56
+ if (bits > 0) {
57
+ out[o++] = ALPH[(buffer << (5 - bits)) & 31];
58
+ }
59
+
60
+ // outLen computed as exact ceiling; o should match, but slice defensively.
61
+ return o === outLen ? out.join("") : out.slice(0, o).join("");
62
+ }