alchemy 0.1.18 → 0.2.1

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 (330) hide show
  1. package/README.md +506 -0
  2. package/lib/alchemy.d.ts +67 -0
  3. package/lib/alchemy.js +48 -0
  4. package/lib/apply.d.ts +3 -20
  5. package/lib/apply.js +95 -82
  6. package/lib/aws/account-id.d.ts +0 -1
  7. package/lib/aws/account-id.js +0 -1
  8. package/lib/aws/bucket.d.ts +4 -7
  9. package/lib/aws/bucket.js +8 -12
  10. package/lib/aws/function.d.ts +4 -7
  11. package/lib/aws/function.js +9 -30
  12. package/lib/aws/index.d.ts +1 -1
  13. package/lib/aws/index.js +1 -1
  14. package/lib/aws/oidc/github-oidc-provider.d.ts +2 -6
  15. package/lib/aws/oidc/github-oidc-provider.js +10 -15
  16. package/lib/aws/oidc/index.d.ts +0 -1
  17. package/lib/aws/oidc/index.js +0 -1
  18. package/lib/aws/oidc/oidc-provider.d.ts +4 -6
  19. package/lib/aws/oidc/oidc-provider.js +9 -11
  20. package/lib/aws/policy-attachment.d.ts +9 -0
  21. package/lib/aws/policy-attachment.js +20 -0
  22. package/lib/aws/policy.d.ts +4 -16
  23. package/lib/aws/policy.js +9 -40
  24. package/lib/aws/queue.d.ts +4 -34
  25. package/lib/aws/queue.js +10 -20
  26. package/lib/aws/role.d.ts +4 -7
  27. package/lib/aws/role.js +17 -26
  28. package/lib/aws/ses.d.ts +4 -10
  29. package/lib/aws/ses.js +118 -131
  30. package/lib/aws/table.d.ts +4 -7
  31. package/lib/aws/table.js +8 -11
  32. package/lib/cloudflare/api.d.ts +0 -1
  33. package/lib/cloudflare/api.js +0 -1
  34. package/lib/cloudflare/asset-manifest.d.ts +0 -1
  35. package/lib/cloudflare/asset-manifest.js +0 -1
  36. package/lib/cloudflare/auth.d.ts +0 -1
  37. package/lib/cloudflare/auth.js +0 -1
  38. package/lib/cloudflare/bindings.d.ts +1 -1
  39. package/lib/cloudflare/bindings.js +3 -2
  40. package/lib/cloudflare/bound.d.ts +0 -1
  41. package/lib/cloudflare/bound.js +0 -1
  42. package/lib/cloudflare/bucket.d.ts +7 -15
  43. package/lib/cloudflare/bucket.js +12 -19
  44. package/lib/cloudflare/durable-object-namespace.d.ts +0 -3
  45. package/lib/cloudflare/durable-object-namespace.js +0 -4
  46. package/lib/cloudflare/generate-asset-manifest.d.ts +0 -1
  47. package/lib/cloudflare/generate-asset-manifest.js +1 -2
  48. package/lib/cloudflare/index.d.ts +2 -1
  49. package/lib/cloudflare/index.js +2 -1
  50. package/lib/cloudflare/kv-namespace.d.ts +6 -9
  51. package/lib/cloudflare/kv-namespace.js +16 -20
  52. package/lib/cloudflare/state.d.ts +0 -1
  53. package/lib/cloudflare/state.js +0 -1
  54. package/lib/cloudflare/static-site-router.d.ts +0 -1
  55. package/lib/cloudflare/static-site-router.js +0 -4
  56. package/lib/cloudflare/static-site.d.ts +5 -11
  57. package/lib/cloudflare/static-site.js +25 -37
  58. package/lib/cloudflare/types.d.ts +0 -1
  59. package/lib/cloudflare/types.js +0 -1
  60. package/lib/cloudflare/upload-asset-manifest.d.ts +0 -1
  61. package/lib/cloudflare/upload-asset-manifest.js +0 -1
  62. package/lib/cloudflare/worker-metadata.d.ts +0 -1
  63. package/lib/cloudflare/worker-metadata.js +0 -1
  64. package/lib/cloudflare/worker-migration.d.ts +0 -1
  65. package/lib/cloudflare/worker-migration.js +0 -1
  66. package/lib/cloudflare/worker.d.ts +8 -14
  67. package/lib/cloudflare/worker.js +38 -50
  68. package/lib/cloudflare/wrangler.json.d.ts +243 -0
  69. package/lib/cloudflare/wrangler.json.js +35 -0
  70. package/lib/cloudflare/zone-settings.d.ts +301 -0
  71. package/lib/cloudflare/zone-settings.js +1 -0
  72. package/lib/cloudflare/zone.d.ts +196 -0
  73. package/lib/cloudflare/zone.js +175 -0
  74. package/lib/context.d.ts +58 -0
  75. package/lib/context.js +36 -0
  76. package/lib/destroy.d.ts +10 -13
  77. package/lib/destroy.js +104 -43
  78. package/lib/{esbuild.d.ts → esbuild/bundle.d.ts} +4 -18
  79. package/lib/{esbuild.js → esbuild/bundle.js} +10 -24
  80. package/lib/esbuild/index.d.ts +1 -0
  81. package/lib/esbuild/index.js +1 -0
  82. package/lib/fs/file.d.ts +13 -0
  83. package/lib/fs/file.js +20 -0
  84. package/lib/fs/folder.d.ts +10 -0
  85. package/lib/fs/folder.js +16 -0
  86. package/lib/fs/index.d.ts +2 -0
  87. package/lib/fs/index.js +2 -0
  88. package/lib/github/client.d.ts +0 -1
  89. package/lib/github/client.js +0 -1
  90. package/lib/github/index.d.ts +0 -1
  91. package/lib/github/index.js +0 -1
  92. package/lib/github/secret.d.ts +4 -6
  93. package/lib/github/secret.js +10 -12
  94. package/lib/index.d.ts +6 -10
  95. package/lib/index.js +5 -9
  96. package/lib/project/index.d.ts +1 -0
  97. package/lib/project/index.js +1 -0
  98. package/lib/project/vite.d.ts +39 -0
  99. package/lib/project/vite.js +68 -0
  100. package/lib/resource.d.ts +34 -62
  101. package/lib/resource.js +29 -270
  102. package/lib/scope.d.ts +37 -14
  103. package/lib/scope.js +97 -23
  104. package/lib/secret.d.ts +2 -7
  105. package/lib/secret.js +7 -20
  106. package/lib/state.d.ts +14 -10
  107. package/lib/state.js +20 -14
  108. package/lib/stripe/index.d.ts +0 -1
  109. package/lib/stripe/index.js +0 -1
  110. package/lib/stripe/price.d.ts +4 -5
  111. package/lib/stripe/price.js +10 -20
  112. package/lib/stripe/product.d.ts +4 -5
  113. package/lib/stripe/product.js +10 -20
  114. package/lib/stripe/webhook.d.ts +4 -6
  115. package/lib/stripe/webhook.js +12 -25
  116. package/lib/test/bun.d.ts +19 -0
  117. package/lib/test/bun.js +55 -0
  118. package/lib/{utils → util}/content-type.d.ts +0 -1
  119. package/lib/{utils → util}/content-type.js +0 -1
  120. package/lib/{encrypt.d.ts → util/encrypt.d.ts} +0 -1
  121. package/lib/{encrypt.js → util/encrypt.js} +0 -1
  122. package/lib/{error.d.ts → util/ignore.d.ts} +0 -1
  123. package/lib/{error.js → util/ignore.js} +0 -1
  124. package/lib/{utils → util}/retry.d.ts +0 -1
  125. package/lib/{utils → util}/retry.js +0 -1
  126. package/lib/util/rm.d.ts +1 -0
  127. package/lib/util/rm.js +11 -0
  128. package/lib/util/serde.d.ts +3 -0
  129. package/lib/{serde.js → util/serde.js} +19 -10
  130. package/lib/util/slugify.d.ts +1 -0
  131. package/lib/util/slugify.js +3 -0
  132. package/package.json +7 -5
  133. package/src/alchemy.ts +127 -0
  134. package/src/apply.ts +116 -115
  135. package/src/aws/bucket.ts +11 -14
  136. package/src/aws/function.ts +13 -33
  137. package/src/aws/index.ts +1 -0
  138. package/src/aws/oidc/github-oidc-provider.ts +14 -19
  139. package/src/aws/oidc/oidc-provider.ts +19 -12
  140. package/src/aws/policy-attachment.ts +51 -0
  141. package/src/aws/policy.ts +15 -60
  142. package/src/aws/queue.ts +17 -21
  143. package/src/aws/role.ts +25 -28
  144. package/src/aws/ses.ts +150 -164
  145. package/src/aws/table.ts +15 -15
  146. package/src/cloudflare/bindings.ts +8 -0
  147. package/src/cloudflare/bucket.ts +39 -47
  148. package/src/cloudflare/durable-object-namespace.ts +0 -10
  149. package/src/cloudflare/generate-asset-manifest.ts +1 -1
  150. package/src/cloudflare/index.ts +2 -0
  151. package/src/cloudflare/kv-namespace.ts +35 -33
  152. package/src/cloudflare/static-site-router.ts +0 -3
  153. package/src/cloudflare/static-site.ts +55 -71
  154. package/src/cloudflare/worker.ts +106 -112
  155. package/src/cloudflare/wrangler.json.ts +309 -0
  156. package/src/cloudflare/zone-settings.ts +368 -0
  157. package/src/cloudflare/zone.ts +491 -0
  158. package/src/context.ts +122 -0
  159. package/src/destroy.ts +124 -73
  160. package/src/{esbuild.ts → esbuild/bundle.ts} +16 -28
  161. package/src/esbuild/index.ts +1 -0
  162. package/src/fs/file.ts +39 -0
  163. package/src/fs/folder.ts +32 -0
  164. package/src/fs/index.ts +2 -0
  165. package/src/github/secret.ts +19 -16
  166. package/src/index.ts +7 -15
  167. package/src/project/index.ts +1 -0
  168. package/src/project/vite.ts +152 -0
  169. package/src/resource.ts +101 -462
  170. package/src/scope.ts +125 -36
  171. package/src/secret.ts +9 -32
  172. package/src/state.ts +38 -21
  173. package/src/stripe/price.ts +17 -22
  174. package/src/stripe/product.ts +17 -22
  175. package/src/stripe/webhook.ts +21 -27
  176. package/src/test/bun.ts +105 -0
  177. package/src/util/rm.ts +11 -0
  178. package/src/util/serde.ts +53 -0
  179. package/src/util/slugify.ts +3 -0
  180. package/lib/$.d.ts +0 -12
  181. package/lib/$.d.ts.map +0 -1
  182. package/lib/$.js +0 -42
  183. package/lib/$.js.map +0 -1
  184. package/lib/alchemize.d.ts +0 -47
  185. package/lib/alchemize.d.ts.map +0 -1
  186. package/lib/alchemize.js +0 -116
  187. package/lib/alchemize.js.map +0 -1
  188. package/lib/apply.d.ts.map +0 -1
  189. package/lib/apply.js.map +0 -1
  190. package/lib/aws/account-id.d.ts.map +0 -1
  191. package/lib/aws/account-id.js.map +0 -1
  192. package/lib/aws/bucket.d.ts.map +0 -1
  193. package/lib/aws/bucket.js.map +0 -1
  194. package/lib/aws/function.d.ts.map +0 -1
  195. package/lib/aws/function.js.map +0 -1
  196. package/lib/aws/index.d.ts.map +0 -1
  197. package/lib/aws/index.js.map +0 -1
  198. package/lib/aws/oidc/github-oidc-provider.d.ts.map +0 -1
  199. package/lib/aws/oidc/github-oidc-provider.js.map +0 -1
  200. package/lib/aws/oidc/index.d.ts.map +0 -1
  201. package/lib/aws/oidc/index.js.map +0 -1
  202. package/lib/aws/oidc/oidc-provider.d.ts.map +0 -1
  203. package/lib/aws/oidc/oidc-provider.js.map +0 -1
  204. package/lib/aws/policy.d.ts.map +0 -1
  205. package/lib/aws/policy.js.map +0 -1
  206. package/lib/aws/queue.d.ts.map +0 -1
  207. package/lib/aws/queue.js.map +0 -1
  208. package/lib/aws/role.d.ts.map +0 -1
  209. package/lib/aws/role.js.map +0 -1
  210. package/lib/aws/ses.d.ts.map +0 -1
  211. package/lib/aws/ses.js.map +0 -1
  212. package/lib/aws/table.d.ts.map +0 -1
  213. package/lib/aws/table.js.map +0 -1
  214. package/lib/cloudflare/api.d.ts.map +0 -1
  215. package/lib/cloudflare/api.js.map +0 -1
  216. package/lib/cloudflare/asset-manifest.d.ts.map +0 -1
  217. package/lib/cloudflare/asset-manifest.js.map +0 -1
  218. package/lib/cloudflare/auth.d.ts.map +0 -1
  219. package/lib/cloudflare/auth.js.map +0 -1
  220. package/lib/cloudflare/bindings.d.ts.map +0 -1
  221. package/lib/cloudflare/bindings.js.map +0 -1
  222. package/lib/cloudflare/bound.d.ts.map +0 -1
  223. package/lib/cloudflare/bound.js.map +0 -1
  224. package/lib/cloudflare/bucket.d.ts.map +0 -1
  225. package/lib/cloudflare/bucket.js.map +0 -1
  226. package/lib/cloudflare/durable-object-namespace.d.ts.map +0 -1
  227. package/lib/cloudflare/durable-object-namespace.js.map +0 -1
  228. package/lib/cloudflare/generate-asset-manifest.d.ts.map +0 -1
  229. package/lib/cloudflare/generate-asset-manifest.js.map +0 -1
  230. package/lib/cloudflare/index.d.ts.map +0 -1
  231. package/lib/cloudflare/index.js.map +0 -1
  232. package/lib/cloudflare/kv-namespace.d.ts.map +0 -1
  233. package/lib/cloudflare/kv-namespace.js.map +0 -1
  234. package/lib/cloudflare/state.d.ts.map +0 -1
  235. package/lib/cloudflare/state.js.map +0 -1
  236. package/lib/cloudflare/static-site-router.d.ts.map +0 -1
  237. package/lib/cloudflare/static-site-router.js.map +0 -1
  238. package/lib/cloudflare/static-site.d.ts.map +0 -1
  239. package/lib/cloudflare/static-site.js.map +0 -1
  240. package/lib/cloudflare/types.d.ts.map +0 -1
  241. package/lib/cloudflare/types.js.map +0 -1
  242. package/lib/cloudflare/upload-asset-manifest.d.ts.map +0 -1
  243. package/lib/cloudflare/upload-asset-manifest.js.map +0 -1
  244. package/lib/cloudflare/worker-metadata.d.ts.map +0 -1
  245. package/lib/cloudflare/worker-metadata.js.map +0 -1
  246. package/lib/cloudflare/worker-migration.d.ts.map +0 -1
  247. package/lib/cloudflare/worker-migration.js.map +0 -1
  248. package/lib/cloudflare/worker.d.ts.map +0 -1
  249. package/lib/cloudflare/worker.js.map +0 -1
  250. package/lib/destroy.d.ts.map +0 -1
  251. package/lib/destroy.js.map +0 -1
  252. package/lib/encrypt.d.ts.map +0 -1
  253. package/lib/encrypt.js.map +0 -1
  254. package/lib/error.d.ts.map +0 -1
  255. package/lib/error.js.map +0 -1
  256. package/lib/esbuild.d.ts.map +0 -1
  257. package/lib/esbuild.js.map +0 -1
  258. package/lib/fs.d.ts +0 -12
  259. package/lib/fs.d.ts.map +0 -1
  260. package/lib/fs.js +0 -41
  261. package/lib/fs.js.map +0 -1
  262. package/lib/github/client.d.ts.map +0 -1
  263. package/lib/github/client.js.map +0 -1
  264. package/lib/github/index.d.ts.map +0 -1
  265. package/lib/github/index.js.map +0 -1
  266. package/lib/github/secret.d.ts.map +0 -1
  267. package/lib/github/secret.js.map +0 -1
  268. package/lib/global.d.ts +0 -16
  269. package/lib/global.d.ts.map +0 -1
  270. package/lib/global.js +0 -42
  271. package/lib/global.js.map +0 -1
  272. package/lib/index.d.ts.map +0 -1
  273. package/lib/index.js.map +0 -1
  274. package/lib/input.d.ts +0 -7
  275. package/lib/input.d.ts.map +0 -1
  276. package/lib/input.js +0 -2
  277. package/lib/input.js.map +0 -1
  278. package/lib/main.d.ts +0 -3
  279. package/lib/main.d.ts.map +0 -1
  280. package/lib/main.js +0 -32
  281. package/lib/main.js.map +0 -1
  282. package/lib/output.d.ts +0 -25
  283. package/lib/output.d.ts.map +0 -1
  284. package/lib/output.js +0 -23
  285. package/lib/output.js.map +0 -1
  286. package/lib/print.d.ts +0 -7
  287. package/lib/print.d.ts.map +0 -1
  288. package/lib/print.js +0 -10
  289. package/lib/print.js.map +0 -1
  290. package/lib/resource.d.ts.map +0 -1
  291. package/lib/resource.js.map +0 -1
  292. package/lib/scope.d.ts.map +0 -1
  293. package/lib/scope.js.map +0 -1
  294. package/lib/secret.d.ts.map +0 -1
  295. package/lib/secret.js.map +0 -1
  296. package/lib/serde.d.ts +0 -3
  297. package/lib/serde.d.ts.map +0 -1
  298. package/lib/serde.js.map +0 -1
  299. package/lib/slug.d.ts +0 -2
  300. package/lib/slug.d.ts.map +0 -1
  301. package/lib/slug.js +0 -4
  302. package/lib/slug.js.map +0 -1
  303. package/lib/state.d.ts.map +0 -1
  304. package/lib/state.js.map +0 -1
  305. package/lib/stripe/index.d.ts.map +0 -1
  306. package/lib/stripe/index.js.map +0 -1
  307. package/lib/stripe/price.d.ts.map +0 -1
  308. package/lib/stripe/price.js.map +0 -1
  309. package/lib/stripe/product.d.ts.map +0 -1
  310. package/lib/stripe/product.js.map +0 -1
  311. package/lib/stripe/webhook.d.ts.map +0 -1
  312. package/lib/stripe/webhook.js.map +0 -1
  313. package/lib/utils/content-type.d.ts.map +0 -1
  314. package/lib/utils/content-type.js.map +0 -1
  315. package/lib/utils/retry.d.ts.map +0 -1
  316. package/lib/utils/retry.js.map +0 -1
  317. package/src/$.ts +0 -69
  318. package/src/alchemize.ts +0 -195
  319. package/src/fs.ts +0 -47
  320. package/src/global.ts +0 -63
  321. package/src/input.ts +0 -19
  322. package/src/main.ts +0 -34
  323. package/src/output.ts +0 -75
  324. package/src/print.ts +0 -18
  325. package/src/serde.ts +0 -46
  326. package/src/slug.ts +0 -3
  327. /package/src/{utils → util}/content-type.ts +0 -0
  328. /package/src/{encrypt.ts → util/encrypt.ts} +0 -0
  329. /package/src/{error.ts → util/ignore.ts} +0 -0
  330. /package/src/{utils → util}/retry.ts +0 -0
package/src/alchemy.ts ADDED
@@ -0,0 +1,127 @@
1
+ import { DestroyedSignal, destroy } from "./destroy";
2
+ import { Scope } from "./scope";
3
+ import { secret } from "./secret";
4
+ import type { StateStoreType } from "./state";
5
+
6
+ // TODO: support browser
7
+ const DEFAULT_STAGE = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
8
+
9
+ function _alchemy(appName: string, options: Omit<AlchemyOptions, "appName">) {
10
+ return scope(undefined, {
11
+ ...options,
12
+ appName,
13
+ stage: options.stage,
14
+ });
15
+ }
16
+ _alchemy.destroy = destroy;
17
+ _alchemy.run = run;
18
+ _alchemy.scope = scope;
19
+ _alchemy.secret = secret;
20
+
21
+ // alchemy type is to semantically highlight `alchemy` as a type (keyword)
22
+ export type alchemy = Alchemy;
23
+
24
+ export const alchemy: Alchemy = _alchemy as any;
25
+
26
+ // Alchemy is for module augmentation
27
+ export interface Alchemy {
28
+ scope: typeof scope;
29
+ run: typeof run;
30
+ destroy: typeof destroy;
31
+ secret: typeof secret;
32
+ (...parameters: Parameters<typeof scope>): ReturnType<typeof scope>;
33
+ }
34
+
35
+ export interface AlchemyOptions {
36
+ /**
37
+ * The name of the application.
38
+ */
39
+ appName?: string;
40
+ /**
41
+ * Determines whether the resources will be created/updated or deleted.
42
+ *
43
+ * @default "up"
44
+ */
45
+ phase?: "up" | "destroy";
46
+ /**
47
+ * Name to scope the resource state under (e.g. `.alchemy/{stage}/..`).
48
+ *
49
+ * @default - your POSIX username
50
+ */
51
+ stage?: string;
52
+ /**
53
+ * If true, will not prune resources that were dropped from the root stack.
54
+ *
55
+ * @default true
56
+ */
57
+ destroyOrphans?: boolean;
58
+ /**
59
+ * A custom state store to use instead of the default file system store.
60
+ */
61
+ stateStore?: StateStoreType;
62
+ /**
63
+ * A custom scope to use as a parent.
64
+ */
65
+ parent?: Scope;
66
+ /**
67
+ * If true, will not print any Create/Update/Delete messages.
68
+ *
69
+ * @default false
70
+ */
71
+ quiet?: boolean;
72
+
73
+ /**
74
+ * A passphrase to use to encrypt/decrypt secrets.
75
+ */
76
+ password?: string;
77
+ }
78
+
79
+ /**
80
+ * Enter a new scope synchronously.
81
+ * @param options
82
+ * @returns
83
+ */
84
+ function scope(
85
+ id: string | undefined,
86
+ options?: AlchemyOptions,
87
+ // TODO: maybe we want to allow using _ = await alchemy.scope(import.meta)
88
+ // | [meta: ImportMeta]
89
+ ): Scope {
90
+ const scope = new Scope({
91
+ ...options,
92
+ appName: options?.appName,
93
+ stage: options?.stage ?? DEFAULT_STAGE,
94
+ scopeName: id,
95
+ parent: options?.parent ?? Scope.get(),
96
+ });
97
+ scope.enter();
98
+ return scope;
99
+ }
100
+
101
+ async function run<T>(
102
+ ...args:
103
+ | [id: string, fn: (this: Scope, scope: Scope) => Promise<T>]
104
+ | [
105
+ id: string,
106
+ options: AlchemyOptions,
107
+ fn: (this: Scope, scope: Scope) => Promise<T>,
108
+ ]
109
+ ): Promise<T> {
110
+ const [id, options, fn] =
111
+ typeof args[1] === "function"
112
+ ? [args[0], undefined, args[1]]
113
+ : (args as [
114
+ string,
115
+ AlchemyOptions | undefined,
116
+ (this: Scope, scope: Scope) => Promise<T>,
117
+ ]);
118
+ await using scope = alchemy.scope(id, options);
119
+ try {
120
+ return await fn.bind(scope)(scope);
121
+ } catch (error) {
122
+ if (!(error instanceof DestroyedSignal)) {
123
+ scope.fail();
124
+ }
125
+ throw error;
126
+ }
127
+ }
package/src/apply.ts CHANGED
@@ -1,135 +1,136 @@
1
- import { defaultStage, defaultStateStore } from "./global";
2
- import { type Output, OutputChain, type Resolved } from "./output";
1
+ import { alchemy } from "./alchemy";
2
+ import { context } from "./context";
3
3
  import {
4
- Input,
5
- Provider,
4
+ PROVIDERS,
5
+ type PendingResource,
6
+ type Provider,
6
7
  type Resource,
7
- ResourceID,
8
- isResource,
8
+ type ResourceProps,
9
9
  } from "./resource";
10
- import { type Scope as IScope, getScope } from "./scope";
11
- import { Secret } from "./secret";
12
- import type { StateStore } from "./state";
10
+ import type { State } from "./state";
11
+ import { serialize } from "./util/serde";
13
12
 
14
13
  export interface ApplyOptions {
15
- stage: string;
16
- scope: IScope;
17
- stateStore: StateStore;
18
14
  quiet?: boolean;
15
+ alwaysUpdate?: boolean;
19
16
  }
20
17
 
21
- /**
22
- * Apply a sub-graph to produce a resource.
23
- * @param output A sub-graph that produces a resource.
24
- * @returns The resource properties.
25
- */
26
- export async function apply<T>(
27
- output: T,
28
- options?: Partial<ApplyOptions>,
29
- ): Promise<Resolved<T>> {
30
- const stage = options?.stage ?? defaultStage;
31
- const scope = options?.scope ?? getScope();
32
- const statePath = scope.getScopePath(stage);
33
- const stateStore = options?.stateStore ?? new defaultStateStore(statePath);
34
-
35
- return (
36
- await evaluate(output, { stage, scope, stateStore, quiet: options?.quiet })
37
- ).value as Resolved<T>;
38
- }
18
+ export async function apply<Out extends Resource>(
19
+ resource: PendingResource<Out>,
20
+ props: ResourceProps,
21
+ options?: ApplyOptions,
22
+ ): Promise<Awaited<Out>> {
23
+ const scope = resource.Scope;
24
+ try {
25
+ const quiet = props.quiet ?? scope.quiet;
26
+ await scope.init();
27
+ let state: State | undefined = (await scope.state.get(resource.ID))!;
28
+ const provider: Provider = PROVIDERS.get(resource.Kind);
29
+ if (provider === undefined) {
30
+ throw new Error(`Provider "${resource.Kind}" not found`);
31
+ }
32
+ if (state === undefined) {
33
+ state = {
34
+ kind: resource.Kind,
35
+ id: resource.ID,
36
+ fqn: resource.FQN,
37
+ seq: resource.Seq,
38
+ status: "creating",
39
+ data: {},
40
+ output: {
41
+ ID: resource.ID,
42
+ FQN: resource.FQN,
43
+ Kind: resource.Kind,
44
+ Scope: scope,
45
+ Seq: resource.Seq,
46
+ },
47
+ // deps: [...deps],
48
+ props,
49
+ };
50
+ await scope.state.set(resource.ID, state);
51
+ }
39
52
 
40
- class Evaluated<T> {
41
- constructor(
42
- public readonly value: T,
43
- public readonly deps: string[] = [],
44
- ) {}
45
- }
53
+ const alwaysUpdate =
54
+ options?.alwaysUpdate ?? provider.options?.alwaysUpdate ?? false;
46
55
 
47
- const cache = new WeakMap<Resource, Promise<Evaluated<any>>>();
56
+ // Skip update if inputs haven't changed and resource is in a stable state
57
+ if (state.status === "created" || state.status === "updated") {
58
+ if (
59
+ JSON.stringify(state.props) ===
60
+ JSON.stringify(await serialize(scope, props)) &&
61
+ alwaysUpdate !== true
62
+ ) {
63
+ if (!quiet) {
64
+ console.log(`Skip: "${resource.FQN}" (no changes)`);
65
+ }
66
+ // if (resourceState.output !== undefined) {
67
+ // resource[Provide](resourceState.output);
68
+ // }
69
+ return state.output as Awaited<Out>;
70
+ }
71
+ }
48
72
 
49
- export async function evaluate<T>(
50
- output: T | Output<T>,
51
- options: ApplyOptions,
52
- ): Promise<Evaluated<T>> {
53
- const stage = options.stage;
54
- const stateStore = options.stateStore;
55
- if (isResource(output)) {
56
- const resource = output;
57
- const resourceID = resource[ResourceID];
58
- const evaluated = await Promise.all(
59
- resource[Input].map((r) => evaluate<any>(r, options)),
60
- );
73
+ const phase = state.status === "creating" ? "create" : "update";
74
+ state.status = phase === "create" ? "creating" : "updating";
75
+ state.oldProps = state.props;
76
+ state.props = props;
61
77
 
62
- if (cache.has(resource)) {
63
- return await cache.get(resource)!;
78
+ if (!quiet) {
79
+ console.log(
80
+ `${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"`,
81
+ );
64
82
  }
65
- let resolve: (value: Evaluated<any>) => void;
66
- let reject: (reason?: any) => void;
67
- const promise = new Promise<Evaluated<any>>((res, rej) => {
68
- resolve = res;
69
- reject = rej;
83
+
84
+ await scope.state.set(resource.ID, state);
85
+
86
+ let isReplaced = false;
87
+
88
+ const ctx = context({
89
+ scope,
90
+ phase,
91
+ kind: resource.Kind,
92
+ id: resource.ID,
93
+ fqn: resource.FQN,
94
+ seq: resource.Seq,
95
+ state,
96
+ replace: () => {
97
+ if (isReplaced) {
98
+ console.warn(
99
+ `Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE`,
100
+ );
101
+ return;
102
+ }
103
+ isReplaced = true;
104
+ },
105
+ });
106
+
107
+ const output = await alchemy.run(resource.ID, async () => {
108
+ return provider.handler.bind(ctx)(resource.ID, props);
70
109
  });
71
- // set this eagerly to avoid double-apply (caused by recursive applies to inputs)
72
- cache.set(resource, promise);
73
110
 
74
- const deps = new Set(evaluated.flatMap((input) => input.deps));
75
- const inputs = evaluated.map((input) => input.value);
76
- try {
77
- const result: T = await resource[Provider].update(
78
- stage,
79
- resource,
80
- deps,
81
- inputs as [],
82
- stateStore,
83
- options,
111
+ if (!quiet) {
112
+ console.log(
113
+ `${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`,
84
114
  );
85
- resolve!(new Evaluated(result, [resourceID, ...deps]));
86
- } catch (error) {
87
- console.error(error);
88
- reject!(error);
89
115
  }
90
- return promise;
91
- } else if (output instanceof OutputChain) {
92
- const inside = output as unknown as {
93
- parent: Output<any>;
94
- fn: (value: any) => T;
95
- };
96
- const parent = await evaluate(inside.parent, options);
97
- const ret = inside.fn(parent.value);
98
- // the ret may be an Output (e.g. in the flatMap case), so we need to evaluate it and include its deps
99
- const evaluated = await evaluate(ret, options);
100
- return new Evaluated<T>(evaluated.value, [
101
- ...parent.deps,
102
- ...evaluated.deps,
103
- ]);
104
- } else if (Array.isArray(output)) {
105
- const evaluatedItems = await Promise.all(
106
- output.map((item) => evaluate(item, options)),
107
- );
108
- return new Evaluated(
109
- evaluatedItems.map((e) => e.value) as unknown as T,
110
- evaluatedItems.flatMap((e) => e.deps),
111
- );
112
- } else if (output instanceof Date) {
113
- return new Evaluated(output as T);
114
- } else if (output instanceof Secret) {
115
- return new Evaluated(output as T);
116
- } else if (output && typeof output === "object") {
117
- const entries = Object.entries(output);
118
- const evaluatedEntries = await Promise.all(
119
- entries.map(
120
- async ([key, value]) => [key, await evaluate(value, options)] as const,
121
- ),
122
- );
123
116
 
124
- const result = Object.fromEntries(
125
- evaluatedEntries.map(([key, evaluated]) => [key, evaluated.value]),
126
- ) as unknown as T;
127
-
128
- const deps = evaluatedEntries.flatMap(([_, evaluated]) => evaluated.deps);
129
-
130
- return new Evaluated(result, deps);
117
+ await scope.state.set(resource.ID, {
118
+ kind: resource.Kind,
119
+ id: resource.ID,
120
+ fqn: resource.FQN,
121
+ seq: resource.Seq,
122
+ data: state.data,
123
+ status: phase === "create" ? "created" : "updated",
124
+ output,
125
+ props,
126
+ // deps: [...deps],
127
+ });
128
+ // if (output !== undefined) {
129
+ // resource[Provide](output as Out);
130
+ // }
131
+ return output as any;
132
+ } catch (error) {
133
+ scope.fail();
134
+ throw error;
131
135
  }
132
-
133
- // Base case: primitive value
134
- return new Evaluated<T>(output as T);
135
136
  }
package/src/aws/bucket.ts CHANGED
@@ -10,16 +10,16 @@ import {
10
10
  PutBucketTaggingCommand,
11
11
  S3Client,
12
12
  } from "@aws-sdk/client-s3";
13
- import { ignore } from "../error";
13
+ import type { Context } from "../context";
14
14
  import { Resource } from "../resource";
15
+ import { ignore } from "../util/ignore";
15
16
 
16
17
  export interface BucketProps {
17
18
  bucketName: string;
18
19
  tags?: Record<string, string>;
19
20
  }
20
21
 
21
- export interface BucketOutput extends BucketProps {
22
- id: string; // Same as bucketName for AWS
22
+ export interface Bucket extends Resource<"s3::Bucket">, BucketProps {
23
23
  arn: string;
24
24
  bucketDomainName: string; // Format: bucket-name.s3.amazonaws.com
25
25
  bucketRegionalDomainName?: string; // Format: bucket-name.s3.region.amazonaws.com
@@ -31,12 +31,12 @@ export interface BucketOutput extends BucketProps {
31
31
  acl?: string;
32
32
  }
33
33
 
34
- export class Bucket extends Resource(
34
+ export const Bucket = Resource(
35
35
  "s3::Bucket",
36
- async (ctx, props: BucketProps) => {
36
+ async function (this: Context<Bucket>, id: string, props: BucketProps) {
37
37
  const client = new S3Client({});
38
38
 
39
- if (ctx.event === "delete") {
39
+ if (this.phase === "delete") {
40
40
  await ignore(NoSuchBucket.name, () =>
41
41
  client.send(
42
42
  new DeleteBucketCommand({
@@ -44,7 +44,7 @@ export class Bucket extends Resource(
44
44
  }),
45
45
  ),
46
46
  );
47
- return props;
47
+ return this.destroy();
48
48
  } else {
49
49
  try {
50
50
  // Check if bucket exists
@@ -55,7 +55,7 @@ export class Bucket extends Resource(
55
55
  );
56
56
 
57
57
  // Update tags if they changed and bucket exists
58
- if (ctx.event === "update" && props.tags) {
58
+ if (this.phase === "update" && props.tags) {
59
59
  await client.send(
60
60
  new PutBucketTaggingCommand({
61
61
  Bucket: props.bucketName,
@@ -121,9 +121,8 @@ export class Bucket extends Resource(
121
121
  }
122
122
  }
123
123
 
124
- const output: BucketOutput = {
124
+ return this({
125
125
  bucketName: props.bucketName,
126
- id: props.bucketName,
127
126
  arn: `arn:aws:s3:::${props.bucketName}`,
128
127
  bucketDomainName: `${props.bucketName}.s3.amazonaws.com`,
129
128
  bucketRegionalDomainName: `${props.bucketName}.s3.${region}.amazonaws.com`,
@@ -132,12 +131,10 @@ export class Bucket extends Resource(
132
131
  versioningEnabled: versioningResponse.Status === "Enabled",
133
132
  acl: aclResponse.Grants?.[0]?.Permission?.toLowerCase(),
134
133
  ...(tags && { tags }),
135
- };
136
-
137
- return output;
134
+ });
138
135
  }
139
136
  },
140
- ) {}
137
+ );
141
138
 
142
139
  // Helper function to get S3 hosted zone IDs by region
143
140
  function getHostedZoneId(region: string): string {
@@ -12,8 +12,9 @@ import {
12
12
  } from "@aws-sdk/client-lambda";
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
15
- import { ignore } from "../error";
16
- import { type Context, Resource } from "../resource";
15
+ import type { Context } from "../context";
16
+ import { Resource } from "../resource";
17
+ import { ignore } from "../util/ignore";
17
18
 
18
19
  async function resolveRegion(client: LambdaClient): Promise<string> {
19
20
  const region = client.config.region;
@@ -47,8 +48,7 @@ export interface FunctionProps {
47
48
  };
48
49
  }
49
50
 
50
- export interface FunctionOutput extends FunctionProps {
51
- id: string; // Same as functionName
51
+ export interface Function extends Resource<"lambda::Function">, FunctionProps {
52
52
  arn: string;
53
53
  lastModified: string;
54
54
  version: string;
@@ -71,15 +71,15 @@ export interface FunctionOutput extends FunctionProps {
71
71
  signingJobArn?: string;
72
72
  }
73
73
 
74
- export class Function extends Resource(
74
+ export const Function = Resource(
75
75
  "lambda::Function",
76
- async (ctx: Context<FunctionOutput>, props: FunctionProps) => {
76
+ async function (this: Context<Function>, id: string, props: FunctionProps) {
77
77
  const client = new LambdaClient({});
78
78
  const region = await resolveRegion(client);
79
79
 
80
80
  const code = await zipCode(props.zipPath);
81
81
 
82
- if (ctx.event === "delete") {
82
+ if (this.phase === "delete") {
83
83
  await ignore(ResourceNotFoundException.name, () =>
84
84
  client.send(
85
85
  new DeleteFunctionCommand({
@@ -88,34 +88,17 @@ export class Function extends Resource(
88
88
  ),
89
89
  );
90
90
 
91
- // Return a minimal FunctionOutput for deleted state
92
- return {
93
- ...props,
94
- id: props.functionName,
95
- arn: `arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}`,
96
- lastModified: new Date().toISOString(),
97
- version: "$LATEST",
98
- qualifiedArn: `arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}:$LATEST`,
99
- invokeArn: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${region}:${process.env.AWS_ACCOUNT_ID}:function:${props.functionName}/invocations`,
100
- sourceCodeHash: "",
101
- sourceCodeSize: 0,
102
- architectures: props.architecture
103
- ? [props.architecture]
104
- : [Architecture.x86_64],
105
- revisionId: "",
106
- state: "Inactive",
107
- packageType: "Zip",
108
- };
91
+ return this.destroy();
109
92
  } else {
110
93
  try {
111
94
  // Check if function exists
112
- const func = await client.send(
95
+ await client.send(
113
96
  new GetFunctionCommand({
114
97
  FunctionName: props.functionName,
115
98
  }),
116
99
  );
117
100
 
118
- if (ctx.event === "update") {
101
+ if (this.phase === "update") {
119
102
  // Wait for function to stabilize
120
103
  await waitForFunctionStabilization(client, props.functionName);
121
104
 
@@ -228,9 +211,8 @@ export class Function extends Resource(
228
211
  ),
229
212
  ]);
230
213
 
231
- const output: FunctionOutput = {
214
+ return this({
232
215
  ...props,
233
- id: props.functionName,
234
216
  arn: config.FunctionArn!,
235
217
  lastModified: config.LastModified!,
236
218
  version: config.Version!,
@@ -251,12 +233,10 @@ export class Function extends Resource(
251
233
  packageType: config.PackageType!,
252
234
  signingProfileVersionArn: config.SigningProfileVersionArn,
253
235
  signingJobArn: config.SigningJobArn,
254
- };
255
-
256
- return output;
236
+ });
257
237
  }
258
238
  },
259
- ) {}
239
+ );
260
240
 
261
241
  // Helper to wait for function to stabilize
262
242
  async function waitForFunctionStabilization(
package/src/aws/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./account-id";
2
2
  export * from "./bucket";
3
3
  export * from "./function";
4
4
  export * from "./policy";
5
+ export * from "./policy-attachment";
5
6
  export * from "./queue";
6
7
  export * from "./role";
7
8
  export * from "./ses";
@@ -1,5 +1,3 @@
1
- import type { Input } from "../../input";
2
- import { isOutput } from "../../output";
3
1
  import { OIDCProvider, type OIDCProviderProps } from "./oidc-provider";
4
2
 
5
3
  /**
@@ -13,22 +11,19 @@ export interface GitHubOIDCProviderProps
13
11
  extends Omit<OIDCProviderProps, "thumbprint"> {
14
12
  owner: string;
15
13
  repository: string;
16
- thumbprint?: string;
17
14
  }
15
+ export type GitHubOIDCProvider = ReturnType<typeof GitHubOIDCProvider>;
18
16
 
19
- export class GitHubOIDCProvider extends OIDCProvider {
20
- constructor(id: string, props: Input<GitHubOIDCProviderProps>) {
21
- super(
22
- id,
23
- isOutput<GitHubOIDCProviderProps>(props)
24
- ? props.apply((props) => ({
25
- ...props,
26
- thumbprint: props.thumbprint ?? DEFAULT_GITHUB_THUMBPRINT,
27
- }))
28
- : ({
29
- ...props,
30
- thumbprint: props.thumbprint ?? DEFAULT_GITHUB_THUMBPRINT,
31
- } as Input<OIDCProviderProps>),
32
- );
33
- }
34
- }
17
+ export const GitHubOIDCProvider = async (
18
+ id: string,
19
+ props: GitHubOIDCProviderProps,
20
+ ) => {
21
+ return OIDCProvider(id, {
22
+ owner: props.owner,
23
+ repository: props.repository,
24
+ roleArn: props.roleArn,
25
+ branches: props.branches,
26
+ environments: props.environments,
27
+ thumbprint: DEFAULT_GITHUB_THUMBPRINT,
28
+ });
29
+ };