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/lib/resource.js CHANGED
@@ -1,276 +1,35 @@
1
- import { alchemize } from "./alchemize";
2
1
  import { apply } from "./apply";
3
- import { defaultStateStore, deletions, providers } from "./global";
4
- import { OutputChain } from "./output";
5
- import { getScope, pushScope } from "./scope";
6
- export const ResourceID = Symbol.for("ResourceID");
7
- export const ResourceFQN = Symbol.for("ResourceFQN");
8
- export const ResourceHandle = Symbol.for("ResourceHandle");
9
- export const Provider = Symbol.for("Provider");
10
- export const Input = Symbol.for("Input");
11
- export const Value = Symbol.for("Value");
12
- export const Apply = Symbol.for("Apply");
13
- export const Provide = Symbol.for("Provide");
14
- export const Scope = Symbol.for("Scope");
15
- export const Options = Symbol.for("Options");
16
- // properties that pierce through the Proxy
17
- const OrthogonalProperties = [
18
- ResourceID,
19
- ResourceFQN,
20
- ResourceHandle,
21
- Provider,
22
- Input,
23
- Value,
24
- Apply,
25
- Provide,
26
- Scope,
27
- Options,
28
- ];
29
- export function isResource(value) {
30
- return value?.[ResourceID] !== undefined;
31
- }
32
- const applied = new Map();
33
- const destroyed = new WeakMap();
34
- const i = 0;
2
+ import { Scope as _Scope } from "./scope";
3
+ export const PROVIDERS = new Map();
35
4
  export function Resource(type, ...args) {
36
- if (providers.has(type)) {
5
+ if (PROVIDERS.has(type)) {
37
6
  throw new Error(`Resource ${type} already exists`);
38
7
  }
39
- const [options, func] = args.length === 2 ? args : [undefined, args[0]];
40
- class Resource {
41
- static type = type;
42
- constructor(id, ...input) {
43
- const scope = getScope();
44
- const node = {
45
- provider: Resource,
46
- resource: this,
47
- };
48
- if (scope.nodes.has(id)) {
49
- // TODO(sam): do we want to throw?
50
- // it's kind of awesome that you can re-create a resource and call apply
51
- // console.warn(`Resource ${id} already exists in the stack: ${stack.id}`);
52
- }
53
- scope.nodes.set(id, node);
54
- // const resourceFQN = scope.getScopePath(id) + "/" + id;
55
- this[ResourceID] = id;
56
- this[ResourceHandle] = new Object();
57
- // this[ResourceFQN] = resourceFQN;
58
- // this[ResourceFQN] = i++;
59
- this[Provider] = Resource;
60
- this[Input] = input;
61
- this[Value] = undefined;
62
- this[Scope] = scope;
63
- this[Provide] = (value) => {
64
- this[Value] = value;
65
- };
66
- this[Options] = {
67
- alwaysUpdate: options?.alwaysUpdate ?? false,
68
- };
69
- return new Proxy(this, {
70
- // TODO(sam): this won't work for the sub-class (class Table extends Resource)
71
- getPrototypeOf() {
72
- return Resource.prototype;
73
- },
74
- get(target, prop) {
75
- if (OrthogonalProperties.includes(prop)) {
76
- return target[prop];
77
- }
78
- else if (prop === "apply") {
79
- return target[Apply].bind(target);
80
- }
81
- else {
82
- return target[Apply]((value) => value[prop]);
83
- }
84
- },
85
- });
86
- }
87
- apply(fn) {
88
- return this[Apply](fn);
89
- }
90
- [Apply](fn) {
91
- return new OutputChain(this, fn);
92
- }
93
- box = undefined;
94
- [Provide](value) {
95
- if (this.box) {
96
- throw new Error(`Output ${this[ResourceID]} already has a value`);
97
- }
98
- this.box = { value };
99
- const subscribers = this.subscribers;
100
- this.subscribers = [];
101
- subscribers.forEach((fn) => fn(value));
8
+ const [options, handler] = args.length === 2 ? args : [undefined, args[0]];
9
+ const provider = ((resourceID, props) => {
10
+ const scope = _Scope.current;
11
+ if (scope.resources.has(resourceID)) {
12
+ // TODO(sam): do we want to throw?
13
+ // it's kind of awesome that you can re-create a resource and call apply
14
+ // console.warn(`Resource ${id} already exists in the stack: ${stack.id}`);
102
15
  }
103
- subscribers = [];
104
- /**
105
- * @internal
106
- */
107
- subscribe(fn) {
108
- if (this.box) {
109
- fn(this.box.value);
110
- }
111
- else {
112
- this.subscribers.push(fn);
113
- }
114
- }
115
- static async update(stage, resource, deps, inputs, stateStore, options) {
116
- // const stack = resource[ResourceStack];
117
- const resourceID = resource[ResourceID];
118
- const resourceFQN = `${resource[Scope].getScopePath(stage)}/${resourceID}`;
119
- const cacheKey = resource[ResourceHandle];
120
- if (!applied.has(cacheKey)) {
121
- let res, rej;
122
- const promise = new Promise((resolve, reject) => {
123
- res = resolve;
124
- rej = reject;
125
- });
126
- applied.set(cacheKey, promise);
127
- update().then(res).catch(rej);
128
- return promise;
129
- }
130
- return await applied.get(cacheKey);
131
- async function update() {
132
- let resourceState = await stateStore.get(resourceID);
133
- if (resourceState === undefined) {
134
- resourceState = {
135
- provider: type,
136
- status: "creating",
137
- data: {},
138
- output: undefined,
139
- deps: [...deps],
140
- inputs,
141
- };
142
- await stateStore.set(resourceID, resourceState);
143
- }
144
- // Skip update if inputs haven't changed and resource is in a stable state
145
- if (resourceState.status === "created" ||
146
- resourceState.status === "updated") {
147
- if (JSON.stringify(resourceState.inputs) === JSON.stringify(inputs) &&
148
- !resource[Options].alwaysUpdate) {
149
- if (!options?.quiet) {
150
- console.log(`Skip: ${resourceFQN} (no changes)`);
151
- }
152
- if (resourceState.output !== undefined) {
153
- resource[Provide](resourceState.output);
154
- }
155
- return resourceState.output;
156
- }
157
- }
158
- const event = resourceState.status === "creating" ? "create" : "update";
159
- resourceState.status = event === "create" ? "creating" : "updating";
160
- resourceState.oldInputs = resourceState.inputs;
161
- resourceState.inputs = inputs;
162
- if (!options?.quiet) {
163
- console.log(`${event === "create" ? "Create" : "Update"}: ${resourceFQN}`);
164
- }
165
- await stateStore.set(resourceID, resourceState);
166
- let isReplaced = false;
167
- const quiet = options.quiet ?? false;
168
- const evaluated = await pushScope(resource[Scope], resourceID, async () => {
169
- const result = await func({
170
- stage,
171
- resourceID,
172
- resourceFQN,
173
- event,
174
- scope: getScope(),
175
- output: resourceState.output,
176
- replace: () => {
177
- if (isReplaced) {
178
- console.warn(`Resource ${type} ${resourceFQN} is already marked as REPLACE`);
179
- return;
180
- }
181
- isReplaced = true;
182
- deletions.push({
183
- id: resourceID,
184
- data: {
185
- ...resourceState.data,
186
- },
187
- inputs: inputs,
188
- });
189
- },
190
- get: (key) => resourceState.data[key],
191
- set: async (key, value) => {
192
- resourceState.data[key] = value;
193
- await stateStore.set(resourceID, resourceState);
194
- },
195
- delete: async (key) => {
196
- const value = resourceState.data[key];
197
- delete resourceState.data[key];
198
- await stateStore.set(resourceID, resourceState);
199
- return value;
200
- },
201
- quiet,
202
- }, ...inputs);
203
- if (result === undefined) {
204
- return undefined;
205
- }
206
- const evaluated = await apply(result, {
207
- stage,
208
- scope: getScope(),
209
- quiet,
210
- });
211
- return evaluated;
212
- });
213
- if (!options?.quiet) {
214
- console.log(`${event === "create" ? "Created" : "Updated"}: ${resourceFQN}`);
215
- }
216
- await stateStore.set(resourceID, {
217
- provider: type,
218
- data: resourceState.data,
219
- status: event === "create" ? "created" : "updated",
220
- output: evaluated,
221
- inputs,
222
- deps: [...deps],
223
- });
224
- if (evaluated !== undefined) {
225
- resource[Provide](evaluated);
226
- }
227
- return evaluated;
228
- }
229
- }
230
- static async delete(stage, scope, resourceID, state, inputs, options) {
231
- const { Scope } = await import("./scope");
232
- const resourceFQN = `${scope.getScopePath(stage)}/${resourceID}`;
233
- const nestedScope = new Scope(resourceID, scope);
234
- await alchemize({
235
- mode: "destroy",
236
- stage,
237
- scope: nestedScope,
238
- // TODO(sam): should use the appropriate state store
239
- stateStore: defaultStateStore,
240
- quiet: options.quiet,
241
- });
242
- if (!options?.quiet) {
243
- console.log(`Delete: ${resourceFQN}`);
244
- }
245
- await func({
246
- stage,
247
- scope,
248
- resourceID: resourceID,
249
- resourceFQN: resourceFQN,
250
- event: "delete",
251
- output: state.output,
252
- replace() {
253
- throw new Error("Cannot replace a resource that is being deleted");
254
- },
255
- get: (key) => {
256
- return state.data[key];
257
- },
258
- set: async (key, value) => {
259
- state.data[key] = value;
260
- },
261
- delete: async (key) => {
262
- const value = state.data[key];
263
- delete state.data[key];
264
- return value;
265
- },
266
- quiet: options.quiet ?? false,
267
- }, ...inputs);
268
- if (!options?.quiet) {
269
- console.log(`Deleted: ${resourceFQN}`);
270
- }
271
- }
272
- }
273
- providers.set(type, Resource);
274
- return Resource;
16
+ // get a sequence number (unique within the scope) for the resource
17
+ const seq = scope.seq();
18
+ const meta = {
19
+ Kind: type,
20
+ ID: resourceID,
21
+ FQN: scope.fqn(resourceID),
22
+ Seq: seq,
23
+ Scope: scope,
24
+ };
25
+ const promise = apply(meta, props, options);
26
+ const resource = Object.assign(promise, meta);
27
+ scope.resources.set(resourceID, resource);
28
+ return resource;
29
+ });
30
+ provider.type = type;
31
+ provider.handler = handler;
32
+ provider.options = options;
33
+ PROVIDERS.set(type, provider);
34
+ return provider;
275
35
  }
276
- //# sourceMappingURL=resource.js.map
package/lib/scope.d.ts CHANGED
@@ -1,18 +1,41 @@
1
- import type { Output } from "./output";
2
- import type { Provider, Resource } from "./resource";
1
+ import type { PendingResource, ResourceID } from "./resource";
2
+ import { type StateStore, type StateStoreType } from "./state";
3
+ export type ScopeOptions = {
4
+ appName?: string;
5
+ stage: string;
6
+ parent?: Scope;
7
+ scopeName?: string;
8
+ password?: string;
9
+ stateStore?: StateStoreType;
10
+ quiet?: boolean;
11
+ };
3
12
  export declare class Scope {
13
+ static get(): Scope | undefined;
14
+ static get current(): Scope;
15
+ readonly resources: Map<string, PendingResource>;
16
+ readonly appName: string | undefined;
17
+ readonly stage: string;
4
18
  readonly scopeName: string | null;
5
19
  readonly parent: Scope | undefined;
6
- readonly nodes: Map<string, {
7
- provider: Provider<any, any[], any>;
8
- resource: Resource<any, any>;
9
- }>;
10
- readonly callbacks: Output<void>[];
11
- constructor(scopeName?: string | null, parent?: Scope | undefined);
12
- getScopePath(root: string): string;
20
+ readonly password: string | undefined;
21
+ readonly state: StateStore;
22
+ readonly quiet: boolean;
23
+ private isErrored;
24
+ constructor(options: ScopeOptions);
25
+ delete(resourceID: ResourceID): Promise<void>;
26
+ private _seq;
27
+ seq(): number;
28
+ get chain(): string[];
29
+ fail(): void;
30
+ enter(): void;
31
+ init(): Promise<void>;
32
+ deinit(): Promise<void>;
33
+ fqn(resourceID: ResourceID): string;
34
+ finalize(): Promise<void>;
35
+ run<T>(fn: (scope: Scope) => Promise<T>): Promise<T>;
36
+ [Symbol.asyncDispose](): Promise<void>;
37
+ /**
38
+ * Returns a string representation of the scope.
39
+ */
40
+ toString(): string;
13
41
  }
14
- export declare const rootScope: Scope;
15
- export declare function getScope(): Scope;
16
- export declare function withScope<T>(scope: Scope, fn: () => T | Promise<T>): Promise<T>;
17
- export declare function pushScope<T>(parent: Scope, scopeName: string, fn: () => T | Promise<T>): Promise<T>;
18
- //# sourceMappingURL=scope.d.ts.map
package/lib/scope.js CHANGED
@@ -1,30 +1,104 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import path from "node:path";
2
+ import { destroy } from "./destroy";
3
+ import { FileSystemStateStore, } from "./state";
3
4
  const scopeStorage = new AsyncLocalStorage();
4
5
  export class Scope {
6
+ static get() {
7
+ return scopeStorage.getStore();
8
+ }
9
+ static get current() {
10
+ const scope = Scope.get();
11
+ if (!scope) {
12
+ throw new Error("Not running within an Alchemy Scope");
13
+ }
14
+ return scope;
15
+ }
16
+ resources = new Map();
17
+ appName;
18
+ stage;
5
19
  scopeName;
6
20
  parent;
7
- nodes = new Map();
8
- callbacks = [];
9
- constructor(scopeName = null, parent = undefined) {
10
- this.scopeName = scopeName;
11
- this.parent = parent;
12
- }
13
- getScopePath(root) {
14
- // First, compute the parent's scope path
15
- const parentPath = this.parent ? this.parent.getScopePath(root) : root;
16
- // Then join the current scope name (if any) onto the parent's path
17
- return this.scopeName ? path.join(parentPath, this.scopeName) : parentPath;
21
+ password;
22
+ state;
23
+ quiet;
24
+ isErrored = false;
25
+ constructor(options) {
26
+ this.appName = options.appName;
27
+ this.stage = options.stage;
28
+ this.scopeName = options.scopeName ?? null;
29
+ this.parent = options.parent ?? Scope.get();
30
+ this.quiet = options.quiet ?? this.parent?.quiet ?? false;
31
+ if (this.parent && !this.scopeName) {
32
+ throw new Error("Scope name is required when creating a child scope");
33
+ }
34
+ this.password = options.password;
35
+ this.state = new (options.stateStore ?? FileSystemStateStore)(this);
36
+ }
37
+ async delete(resourceID) {
38
+ await this.state.delete(resourceID);
39
+ this.resources.delete(resourceID);
40
+ }
41
+ _seq = 0;
42
+ seq() {
43
+ return this._seq++;
44
+ }
45
+ get chain() {
46
+ const thisScope = this.scopeName ? [this.scopeName] : [];
47
+ const app = this.appName ? [this.appName] : [];
48
+ if (this.parent) {
49
+ return [...this.parent.chain, ...thisScope];
50
+ }
51
+ else {
52
+ return [...app, this.stage, ...thisScope];
53
+ }
54
+ }
55
+ fail() {
56
+ console.error("Scope failed", this.chain.join("/"));
57
+ this.isErrored = true;
58
+ }
59
+ enter() {
60
+ scopeStorage.enterWith(this);
61
+ }
62
+ async init() {
63
+ await this.state.init?.();
64
+ }
65
+ async deinit() {
66
+ await this.state.deinit?.();
67
+ }
68
+ fqn(resourceID) {
69
+ return [...this.chain, resourceID].join("/");
70
+ }
71
+ async finalize() {
72
+ if (!this.isErrored) {
73
+ // TODO: need to detect if it is in error
74
+ const resourceIds = await this.state.list();
75
+ const aliveIds = new Set(this.resources.keys());
76
+ const orphanIds = Array.from(resourceIds.filter((id) => !aliveIds.has(id)));
77
+ const orphans = await Promise.all(orphanIds.map(async (id) => (await this.state.get(id)).output));
78
+ await destroy.all(orphans, {
79
+ quiet: this.quiet,
80
+ strategy: "sequential",
81
+ });
82
+ }
83
+ else {
84
+ console.warn("Scope is in error, skipping finalize");
85
+ }
86
+ }
87
+ async run(fn) {
88
+ return scopeStorage.run(this, () => fn(this));
89
+ }
90
+ [Symbol.asyncDispose]() {
91
+ return this.finalize();
92
+ }
93
+ /**
94
+ * Returns a string representation of the scope.
95
+ */
96
+ toString() {
97
+ return `Scope(
98
+ chain=${this.chain.join("/")},
99
+ resources=[${Array.from(this.resources.values())
100
+ .map((r) => r.ID)
101
+ .join(",\n ")}]
102
+ )`;
18
103
  }
19
104
  }
20
- export const rootScope = new Scope(null);
21
- export function getScope() {
22
- return scopeStorage.getStore() ?? rootScope;
23
- }
24
- export function withScope(scope, fn) {
25
- return scopeStorage.run(scope, async () => fn());
26
- }
27
- export function pushScope(parent, scopeName, fn) {
28
- return scopeStorage.run(new Scope(scopeName, parent), async () => fn());
29
- }
30
- //# sourceMappingURL=scope.js.map
package/lib/secret.d.ts CHANGED
@@ -1,12 +1,7 @@
1
- import type { Input } from "./input";
2
- import { type Output } from "./output";
3
- export declare function secret<S extends Input<string> | undefined>(unencrypted: S): S extends undefined ? never : S extends Output<string> ? Output<Secret> : Secret;
4
1
  export declare class Secret {
5
2
  readonly unencrypted: string;
6
3
  readonly type = "secret";
7
4
  constructor(unencrypted: string);
8
5
  }
9
- export declare function setSecretPassphrase(passphrase: string): void;
10
- export declare function getSecretPassphrase(): string;
11
- export declare function tryGetSecretPassphrase(): string | undefined;
12
- //# sourceMappingURL=secret.d.ts.map
6
+ export declare function isSecret(binding: any): binding is Secret;
7
+ export declare function secret<S extends string | undefined>(unencrypted: S): Secret;
package/lib/secret.js CHANGED
@@ -1,12 +1,3 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
- import { isOutput } from "./output";
3
- const passphraseContext = new AsyncLocalStorage();
4
- export function secret(unencrypted) {
5
- if (unencrypted === undefined) {
6
- throw new Error("Secret cannot be undefined");
7
- }
8
- return (isOutput(unencrypted) ? unencrypted.apply(secret) : new Secret(unencrypted));
9
- }
10
1
  export class Secret {
11
2
  unencrypted;
12
3
  type = "secret";
@@ -14,17 +5,13 @@ export class Secret {
14
5
  this.unencrypted = unencrypted;
15
6
  }
16
7
  }
17
- export function setSecretPassphrase(passphrase) {
18
- passphraseContext.enterWith(passphrase);
8
+ export function isSecret(binding) {
9
+ return (binding instanceof Secret ||
10
+ (typeof binding === "object" && binding.type === "secret_text"));
19
11
  }
20
- export function getSecretPassphrase() {
21
- const passphrase = passphraseContext.getStore();
22
- if (!passphrase) {
23
- throw new Error("No passphrase set");
12
+ export function secret(unencrypted) {
13
+ if (unencrypted === undefined) {
14
+ throw new Error("Secret cannot be undefined");
24
15
  }
25
- return passphrase;
26
- }
27
- export function tryGetSecretPassphrase() {
28
- return passphraseContext.getStore();
16
+ return new Secret(unencrypted);
29
17
  }
30
- //# sourceMappingURL=secret.js.map
package/lib/state.d.ts CHANGED
@@ -1,12 +1,17 @@
1
- export interface State {
1
+ import type { Resource, ResourceProps } from "./resource";
2
+ import type { Scope } from "./scope";
3
+ export interface State<Kind extends string = string, Props extends ResourceProps = ResourceProps, Out extends Resource = Resource> {
2
4
  status: `creating` | `created` | `updating` | `updated` | `deleting` | `deleted`;
3
- provider: string;
5
+ kind: Kind;
6
+ id: string;
7
+ fqn: string;
8
+ seq: number;
4
9
  data: Record<string, any>;
5
- deps: string[];
6
- inputs: any[];
7
- oldInputs?: any[];
8
- output: any;
10
+ props: Props;
11
+ oldProps?: Props;
12
+ output: Out;
9
13
  }
14
+ export type StateStoreType = new (scope: Scope) => StateStore;
10
15
  export interface StateStore {
11
16
  /** Initialize the state container if one is required */
12
17
  init?(): Promise<void>;
@@ -22,10 +27,10 @@ export interface StateStore {
22
27
  set(key: string, value: State): Promise<void>;
23
28
  delete(key: string): Promise<void>;
24
29
  }
25
- export type StateStoreType = new (dir: string) => StateStore;
26
30
  export declare class FileSystemStateStore implements StateStore {
27
- readonly path: string;
28
- constructor(dir: string);
31
+ readonly scope: Scope;
32
+ readonly dir: string;
33
+ constructor(scope: Scope);
29
34
  init(): Promise<void>;
30
35
  deinit(): Promise<void>;
31
36
  count(): Promise<number>;
@@ -37,4 +42,3 @@ export declare class FileSystemStateStore implements StateStore {
37
42
  getBatch(ids: string[]): Promise<Record<string, State>>;
38
43
  private getPath;
39
44
  }
40
- //# sourceMappingURL=state.d.ts.map
package/lib/state.js CHANGED
@@ -1,26 +1,28 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { ignore } from "./error";
4
- import { deserialize, serialize } from "./serde";
5
- const stateFile = path.join(process.cwd(), ".alchemy");
3
+ import { ignore } from "./util/ignore";
4
+ import { deserialize, serialize } from "./util/serde";
5
+ const stateRootDir = path.join(process.cwd(), ".alchemy");
6
6
  export class FileSystemStateStore {
7
- path;
8
- constructor(dir) {
9
- this.path = path.join(stateFile, dir);
7
+ scope;
8
+ dir;
9
+ constructor(scope) {
10
+ this.scope = scope;
11
+ this.dir = path.join(stateRootDir, ...scope.chain);
10
12
  }
11
13
  async init() {
12
- await fs.promises.mkdir(stateFile, { recursive: true });
13
- await fs.promises.mkdir(this.path, { recursive: true });
14
+ await fs.promises.mkdir(stateRootDir, { recursive: true });
15
+ await fs.promises.mkdir(this.dir, { recursive: true });
14
16
  }
15
17
  async deinit() {
16
- await ignore("ENOENT", () => fs.promises.rmdir(this.path));
18
+ await ignore("ENOENT", () => fs.promises.rmdir(this.dir));
17
19
  }
18
20
  async count() {
19
21
  return Object.keys(await this.list()).length;
20
22
  }
21
23
  async list() {
22
24
  try {
23
- const files = await fs.promises.readdir(this.path, {
25
+ const files = await fs.promises.readdir(this.dir, {
24
26
  withFileTypes: true,
25
27
  });
26
28
  return files
@@ -37,7 +39,12 @@ export class FileSystemStateStore {
37
39
  async get(key) {
38
40
  try {
39
41
  const content = await fs.promises.readFile(await this.getPath(key), "utf8");
40
- return (await deserialize(JSON.parse(content)));
42
+ const state = (await deserialize(this.scope, JSON.parse(content)));
43
+ if (state.output === undefined) {
44
+ state.output = {};
45
+ }
46
+ state.output.Scope = this.scope;
47
+ return state;
41
48
  }
42
49
  catch (error) {
43
50
  if (error.code === "ENOENT") {
@@ -47,7 +54,7 @@ export class FileSystemStateStore {
47
54
  }
48
55
  }
49
56
  async set(key, value) {
50
- return fs.promises.writeFile(await this.getPath(key), JSON.stringify(await serialize(value), null, 2));
57
+ return fs.promises.writeFile(await this.getPath(key), JSON.stringify(await serialize(this.scope, value), null, 2));
51
58
  }
52
59
  async delete(key) {
53
60
  return fs.promises.unlink(await this.getPath(key));
@@ -65,10 +72,9 @@ export class FileSystemStateStore {
65
72
  }))).flat());
66
73
  }
67
74
  async getPath(key) {
68
- const file = path.join(this.path, `${key}.json`);
75
+ const file = path.join(this.dir, `${key}.json`);
69
76
  const dir = path.dirname(file);
70
77
  await fs.promises.mkdir(dir, { recursive: true });
71
78
  return file;
72
79
  }
73
80
  }
74
- //# sourceMappingURL=state.js.map
@@ -1,4 +1,3 @@
1
1
  export * from "./price";
2
2
  export * from "./product";
3
3
  export * from "./webhook";
4
- //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,3 @@
1
1
  export * from "./price";
2
2
  export * from "./product";
3
3
  export * from "./webhook";
4
- //# sourceMappingURL=index.js.map