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/resource.ts CHANGED
@@ -1,40 +1,12 @@
1
- import { alchemize } from "./alchemize";
2
- import { type ApplyOptions, apply } from "./apply";
3
- import type { DestroyOptions } from "./destroy";
4
- import { defaultStateStore, deletions, providers } from "./global";
5
- import type { Inputs, Input as input } from "./input";
6
- import { type Export, type Output, OutputChain, type Resolved } from "./output";
7
- import { type Scope as IScope, getScope, pushScope } from "./scope";
8
- import type { State, StateStore } from "./state";
1
+ import { apply } from "./apply";
2
+ import type { Context } from "./context";
3
+ import { Scope as _Scope } from "./scope";
4
+
5
+ export const PROVIDERS = new Map<ResourceKind, Provider<string, any>>();
9
6
 
10
7
  export type ResourceID = string;
11
8
  export type ResourceFQN = string;
12
- export type ResourceType = string;
13
-
14
- export const ResourceID = Symbol.for("ResourceID");
15
- export const ResourceFQN = Symbol.for("ResourceFQN");
16
- export const ResourceHandle = Symbol.for("ResourceHandle");
17
- export const Provider = Symbol.for("Provider");
18
- export const Input = Symbol.for("Input");
19
- export const Value = Symbol.for("Value");
20
- export const Apply = Symbol.for("Apply");
21
- export const Provide = Symbol.for("Provide");
22
- export const Scope = Symbol.for("Scope");
23
- export const Options = Symbol.for("Options");
24
-
25
- // properties that pierce through the Proxy
26
- const OrthogonalProperties = [
27
- ResourceID,
28
- ResourceFQN,
29
- ResourceHandle,
30
- Provider,
31
- Input,
32
- Value,
33
- Apply,
34
- Provide,
35
- Scope,
36
- Options,
37
- ] as const;
9
+ export type ResourceKind = string;
38
10
 
39
11
  export interface ProviderOptions {
40
12
  /**
@@ -43,451 +15,118 @@ export interface ProviderOptions {
43
15
  alwaysUpdate: boolean;
44
16
  }
45
17
 
46
- export type Resource<In extends any[] = any[], Out = any> = {
47
- [ResourceID]: string;
48
- [Provider]: Provider<any, any[], any>;
49
- [Input]: Inputs<In>;
50
- [Value]?: Out;
51
- [Apply]: <O>(value: Out) => O;
52
- [Provide]: (value: Out) => void;
53
- [Options]: ProviderOptions;
54
- } & Output<Out> &
55
- Export<
56
- // the resource handler may export another Output - we need to resolve it first
57
- // then, export that resolved value
58
- Resolved<Out>
59
- >;
60
-
61
- export interface BaseContext {
62
- quiet: boolean;
63
- stage: string;
64
- resourceID: ResourceID;
65
- resourceFQN: ResourceFQN;
66
- scope: IScope;
67
- get<T>(key: string): Promise<T | undefined>;
68
- set<T>(key: string, value: T): Promise<void>;
69
- delete<T>(key: string): Promise<T | undefined>;
70
- /**
71
- * Indicate that this resource is being replaced.
72
- * This will cause the resource to be deleted at the end of the stack's CREATE phase.
73
- */
74
- replace(): void;
75
- }
76
-
77
- export interface CreateContext extends BaseContext {
78
- event: "create";
79
- output?: undefined;
80
- }
81
-
82
- export interface UpdateContext<Outputs> extends BaseContext {
83
- event: "update";
84
- output: Outputs;
85
- }
86
-
87
- export interface DeleteContext<Outputs> extends BaseContext {
88
- event: "delete";
89
- output: Outputs;
90
- }
91
-
92
- export type Context<Outputs> =
93
- | CreateContext
94
- | UpdateContext<Outputs>
95
- | DeleteContext<Outputs>;
18
+ export type ResourceProps = {
19
+ [key: string]: any;
20
+ };
96
21
 
97
22
  export type Provider<
98
- Type extends ResourceType = ResourceType,
99
- In extends any[] = any[],
100
- Out = any,
101
- > = {
102
- type: Type;
103
- update(
104
- stage: string,
105
- resource: Resource,
106
- deps: Set<ResourceID>,
107
- inputs: Inputs<In>,
108
- stateStore: StateStore,
109
- options: ApplyOptions,
110
- ): Promise<Awaited<Out>>;
111
- delete(
112
- stage: string,
113
- scope: IScope | undefined,
114
- resourceID: ResourceID,
115
- state: State,
116
- inputs: Inputs<In>,
117
- options: DestroyOptions,
118
- ): Promise<void>;
119
- } & (new (
120
- id: string,
121
- ...inputs: [...Inputs<In>, ...any[]]
122
- ) => Resource<In, Out>);
123
-
124
- export function isResource(value: any): value is Resource {
125
- return value?.[ResourceID] !== undefined;
23
+ Type extends string = string,
24
+ F extends ResourceLifecycleHandler = ResourceLifecycleHandler,
25
+ > = F &
26
+ IsClass & {
27
+ type: Type;
28
+ options: Partial<ProviderOptions> | undefined;
29
+ handler: F;
30
+ };
31
+
32
+ export type PendingResource<
33
+ Out = unknown,
34
+ Kind extends ResourceKind = ResourceKind,
35
+ ID extends ResourceID = ResourceID,
36
+ FQN extends ResourceFQN = ResourceFQN,
37
+ Scope extends _Scope = _Scope,
38
+ Seq extends number = number,
39
+ > = Promise<Out> & {
40
+ Kind: Kind;
41
+ ID: ID;
42
+ FQN: FQN;
43
+ Seq: Seq;
44
+ Scope: Scope;
45
+ signal: () => void;
46
+ };
47
+
48
+ export interface Resource<
49
+ // give each name types for syntax highlighting (differentiation)
50
+ Kind extends ResourceKind = ResourceKind,
51
+ ID extends ResourceID = ResourceID,
52
+ FQN extends ResourceFQN = ResourceFQN,
53
+ Scope extends _Scope = _Scope,
54
+ Seq extends number = number,
55
+ > {
56
+ // use capital letters to avoid collision with conventional camelCase typescript properties
57
+ Kind: Kind;
58
+ ID: ID;
59
+ FQN: FQN;
60
+ Scope: Scope;
61
+ Seq: Seq;
126
62
  }
127
63
 
128
- const applied = new Map<Object, any>();
64
+ // helper for semantic syntax highlighting (color as a type/class instead of function/value)
65
+ type IsClass = {
66
+ new (_: never): never;
67
+ };
129
68
 
130
- const destroyed = new WeakMap<Resource<any, any>, Promise<void>>();
69
+ type ResourceLifecycleHandler = (
70
+ this: Context<any>,
71
+ id: string,
72
+ props: any,
73
+ ) => Promise<Resource<string>>;
131
74
 
132
- const i = 0;
75
+ // see: https://x.com/samgoodwin89/status/1904640134097887653
76
+ type Handler<F extends (...args: any[]) => any> =
77
+ | F
78
+ | (((this: any, id: string, props: Parameters<F>[1]) => never) & IsClass);
133
79
 
134
80
  export function Resource<
135
- // const Type extends ResourceType,
136
- Args extends any[],
137
- Out,
138
- >(
139
- type: string,
140
- options: Partial<ProviderOptions>,
141
- func: (
142
- ctx: Context<Out>,
143
- ...args: Args
144
- ) => Promise<input<Out> | void> | input<Out> | void,
145
- ): Provider<string, Args, Awaited<Out>>;
81
+ const Type extends string,
82
+ F extends ResourceLifecycleHandler,
83
+ >(type: Type, fn: F): Handler<F>;
146
84
 
147
85
  export function Resource<
148
- const Type extends ResourceType,
149
- Args extends any[],
150
- Out,
151
- >(
152
- type: Type,
153
- func: (
154
- ctx: Context<Out>,
155
- ...args: Args
156
- ) => Promise<input<Out> | void> | input<Out> | void,
157
- ): Provider<Type, Args, Awaited<Out>>;
86
+ const Type extends string,
87
+ F extends ResourceLifecycleHandler,
88
+ >(type: Type, options: Partial<ProviderOptions>, fn: F): Handler<F>;
158
89
 
159
90
  export function Resource<
160
- const Type extends ResourceType,
161
- Args extends any[],
162
- Out,
163
- >(
164
- type: Type,
165
- ...args:
166
- | [
167
- Partial<ProviderOptions>,
168
- (
169
- ctx: Context<Out>,
170
- ...args: Args
171
- ) => Promise<input<Out> | void> | input<Out> | void,
172
- ]
173
- | [
174
- (
175
- ctx: Context<Out>,
176
- ...args: Args
177
- ) => Promise<input<Out> | void> | input<Out> | void,
178
- ]
179
- ): Provider<Type, Args, Awaited<Out>> {
180
- if (providers.has(type)) {
91
+ const Type extends ResourceKind,
92
+ F extends ResourceLifecycleHandler,
93
+ >(type: Type, ...args: [Partial<ProviderOptions>, F] | [F]): Handler<F> {
94
+ if (PROVIDERS.has(type)) {
181
95
  throw new Error(`Resource ${type} already exists`);
182
96
  }
183
- const [options, func] = args.length === 2 ? args : [undefined, args[0]];
184
-
185
- interface Resource {
186
- [ResourceID]: ResourceID;
187
- [ResourceFQN]: string;
188
- [ResourceHandle]: Object;
189
- [Provider]: Provider<Type, Args, Out>;
190
- [Input]: Inputs<Args>;
191
- [Value]?: Out;
192
- [Scope]: IScope;
193
- [Options]: ProviderOptions;
194
- }
195
-
196
- class Resource {
197
- static readonly type = type;
198
-
199
- constructor(id: ResourceID, ...input: Inputs<Args>) {
200
- const scope = getScope();
201
- const node = {
202
- provider: Resource,
203
- resource: this,
204
- } as const;
205
-
206
- if (scope.nodes.has(id)) {
207
- // TODO(sam): do we want to throw?
208
- // it's kind of awesome that you can re-create a resource and call apply
209
- // console.warn(`Resource ${id} already exists in the stack: ${stack.id}`);
210
- }
211
- scope.nodes.set(id, node as any);
212
-
213
- // const resourceFQN = scope.getScopePath(id) + "/" + id;
214
-
215
- this[ResourceID] = id;
216
- this[ResourceHandle] = new Object();
217
- // this[ResourceFQN] = resourceFQN;
218
- // this[ResourceFQN] = i++;
219
- this[Provider] = Resource as any;
220
- this[Input] = input;
221
- this[Value] = undefined;
222
- this[Scope] = scope;
223
- this[Provide] = (value: Out) => {
224
- this[Value] = value;
225
- };
226
- this[Options] = {
227
- alwaysUpdate: options?.alwaysUpdate ?? false,
228
- };
229
-
230
- return new Proxy(this, {
231
- // TODO(sam): this won't work for the sub-class (class Table extends Resource)
232
- getPrototypeOf() {
233
- return Resource.prototype;
234
- },
235
- get(target: any, prop) {
236
- if (OrthogonalProperties.includes(prop as any)) {
237
- return target[prop];
238
- } else if (prop === "apply") {
239
- return target[Apply].bind(target);
240
- } else {
241
- return target[Apply]((value: Out) => value[prop as keyof Out]);
242
- }
243
- },
244
- });
245
- }
97
+ const [options, handler] = args.length === 2 ? args : [undefined, args[0]];
246
98
 
247
- public apply<U>(fn: (value: Out) => U): Output<U> {
248
- return this[Apply](fn);
249
- }
250
-
251
- public [Apply]<U>(fn: (value: Out) => U): Output<U> {
252
- return new OutputChain<Out, U>(this as any, fn);
253
- }
254
-
255
- private box?: {
256
- value: Out;
257
- } = undefined;
258
-
259
- public [Provide](value: Out) {
260
- if (this.box) {
261
- throw new Error(`Output ${this[ResourceID]} already has a value`);
262
- }
263
- this.box = { value };
264
- const subscribers = this.subscribers;
265
- this.subscribers = [];
266
- subscribers.forEach((fn) => fn(value));
267
- }
268
-
269
- private subscribers: ((value: Out) => void)[] = [];
270
-
271
- /**
272
- * @internal
273
- */
274
- public subscribe(fn: (value: Out) => Promise<void>) {
275
- if (this.box) {
276
- fn(this.box.value);
277
- } else {
278
- this.subscribers.push(fn);
279
- }
280
- }
281
-
282
- static async update(
283
- stage: string,
284
- resource: Resource,
285
- deps: Set<ResourceID>,
286
- inputs: Args,
287
- stateStore: StateStore,
288
- options: ApplyOptions,
289
- ): Promise<Awaited<Out | void>> {
290
- // const stack = resource[ResourceStack];
291
- const resourceID = resource[ResourceID];
292
- const resourceFQN = `${resource[Scope].getScopePath(stage)}/${resourceID}`;
293
- const cacheKey = resource[ResourceHandle];
294
- if (!applied.has(cacheKey)) {
295
- let res, rej;
296
- const promise = new Promise<Awaited<Out | void>>((resolve, reject) => {
297
- res = resolve;
298
- rej = reject;
299
- });
300
- applied.set(cacheKey, promise);
301
- update().then(res).catch(rej);
302
- return promise;
303
- }
304
- return await applied.get(cacheKey);
305
-
306
- async function update(): Promise<Awaited<Out | void>> {
307
- let resourceState: State | undefined = await stateStore.get(resourceID);
308
- if (resourceState === undefined) {
309
- resourceState = {
310
- provider: type,
311
- status: "creating",
312
- data: {},
313
- output: undefined,
314
- deps: [...deps],
315
- inputs,
316
- };
317
- await stateStore.set(resourceID, resourceState);
318
- }
319
-
320
- // Skip update if inputs haven't changed and resource is in a stable state
321
- if (
322
- resourceState.status === "created" ||
323
- resourceState.status === "updated"
324
- ) {
325
- if (
326
- JSON.stringify(resourceState.inputs) === JSON.stringify(inputs) &&
327
- !resource[Options].alwaysUpdate
328
- ) {
329
- if (!options?.quiet) {
330
- console.log(`Skip: ${resourceFQN} (no changes)`);
331
- }
332
- if (resourceState.output !== undefined) {
333
- resource[Provide](resourceState.output);
334
- }
335
- return resourceState.output;
336
- }
337
- }
338
-
339
- const event = resourceState.status === "creating" ? "create" : "update";
340
- resourceState.status = event === "create" ? "creating" : "updating";
341
- resourceState.oldInputs = resourceState.inputs;
342
- resourceState.inputs = inputs;
99
+ type Out = Awaited<ReturnType<F>>;
343
100
 
344
- if (!options?.quiet) {
345
- console.log(
346
- `${event === "create" ? "Create" : "Update"}: ${resourceFQN}`,
347
- );
348
- }
101
+ const provider = ((
102
+ resourceID: string,
103
+ props: ResourceProps,
104
+ ): Promise<Resource<string>> => {
105
+ const scope = _Scope.current;
349
106
 
350
- await stateStore.set(resourceID, resourceState);
351
-
352
- let isReplaced = false;
353
-
354
- const quiet = options.quiet ?? false;
355
-
356
- const evaluated = await pushScope(
357
- resource[Scope],
358
- resourceID,
359
- async () => {
360
- const result = await func(
361
- {
362
- stage,
363
- resourceID,
364
- resourceFQN,
365
- event,
366
- scope: getScope(),
367
- output: resourceState.output,
368
- replace: () => {
369
- if (isReplaced) {
370
- console.warn(
371
- `Resource ${type} ${resourceFQN} is already marked as REPLACE`,
372
- );
373
- return;
374
- }
375
- isReplaced = true;
376
- deletions.push({
377
- id: resourceID,
378
- data: {
379
- ...resourceState!.data,
380
- },
381
- inputs: inputs,
382
- });
383
- },
384
- get: (key) => resourceState!.data[key],
385
- set: async (key, value) => {
386
- resourceState!.data[key] = value;
387
- await stateStore.set(resourceID, resourceState!);
388
- },
389
- delete: async (key) => {
390
- const value = resourceState!.data[key];
391
- delete resourceState!.data[key];
392
- await stateStore.set(resourceID, resourceState!);
393
- return value;
394
- },
395
- quiet,
396
- },
397
- ...inputs,
398
- );
399
-
400
- if (result === undefined) {
401
- return undefined;
402
- }
403
-
404
- const evaluated = await apply(result as Out, {
405
- stage,
406
- scope: getScope(),
407
- quiet,
408
- });
409
-
410
- return evaluated;
411
- },
412
- );
413
-
414
- if (!options?.quiet) {
415
- console.log(
416
- `${event === "create" ? "Created" : "Updated"}: ${resourceFQN}`,
417
- );
418
- }
419
- await stateStore.set(resourceID, {
420
- provider: type,
421
- data: resourceState.data,
422
- status: event === "create" ? "created" : "updated",
423
- output: evaluated,
424
- inputs,
425
- deps: [...deps],
426
- });
427
- if (evaluated !== undefined) {
428
- resource[Provide](evaluated as Out);
429
- }
430
- return evaluated as Awaited<Out>;
431
- }
107
+ if (scope.resources.has(resourceID)) {
108
+ // TODO(sam): do we want to throw?
109
+ // it's kind of awesome that you can re-create a resource and call apply
110
+ // console.warn(`Resource ${id} already exists in the stack: ${stack.id}`);
432
111
  }
433
112
 
434
- static async delete(
435
- stage: string,
436
- scope: IScope,
437
- resourceID: ResourceID,
438
- state: State,
439
- inputs: Args,
440
- options: DestroyOptions,
441
- ) {
442
- const { Scope } = await import("./scope");
443
- const resourceFQN = `${scope.getScopePath(stage)}/${resourceID}`;
444
- const nestedScope = new Scope(resourceID, scope);
445
-
446
- await alchemize({
447
- mode: "destroy",
448
- stage,
449
- scope: nestedScope,
450
- // TODO(sam): should use the appropriate state store
451
- stateStore: defaultStateStore,
452
- quiet: options.quiet,
453
- });
454
-
455
- if (!options?.quiet) {
456
- console.log(`Delete: ${resourceFQN}`);
457
- }
458
-
459
- await func(
460
- {
461
- stage,
462
- scope,
463
- resourceID: resourceID,
464
- resourceFQN: resourceFQN,
465
- event: "delete",
466
- output: state.output,
467
- replace() {
468
- throw new Error("Cannot replace a resource that is being deleted");
469
- },
470
- get: (key) => {
471
- return state.data[key];
472
- },
473
- set: async (key, value) => {
474
- state.data[key] = value;
475
- },
476
- delete: async (key) => {
477
- const value = state.data[key];
478
- delete state.data[key];
479
- return value;
480
- },
481
- quiet: options.quiet ?? false,
482
- },
483
- ...inputs,
484
- );
485
-
486
- if (!options?.quiet) {
487
- console.log(`Deleted: ${resourceFQN}`);
488
- }
489
- }
490
- }
491
- providers.set(type, Resource as any);
492
- return Resource as any;
113
+ // get a sequence number (unique within the scope) for the resource
114
+ const seq = scope.seq();
115
+ const meta = {
116
+ Kind: type,
117
+ ID: resourceID,
118
+ FQN: scope.fqn(resourceID),
119
+ Seq: seq,
120
+ Scope: scope,
121
+ } as any as PendingResource<Out>;
122
+ const promise = apply(meta, props, options);
123
+ const resource = Object.assign(promise, meta);
124
+ scope.resources.set(resourceID, resource);
125
+ return resource;
126
+ }) as Provider<Type, F>;
127
+ provider.type = type;
128
+ provider.handler = handler;
129
+ provider.options = options;
130
+ PROVIDERS.set(type, provider);
131
+ return provider;
493
132
  }