alchemy 0.1.17 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/alchemy.d.ts +67 -0
- package/lib/alchemy.js +40 -0
- package/lib/apply.d.ts +3 -20
- package/lib/apply.js +86 -78
- package/lib/aws/account-id.d.ts +0 -1
- package/lib/aws/account-id.js +0 -1
- package/lib/aws/bucket.d.ts +4 -7
- package/lib/aws/bucket.js +8 -12
- package/lib/aws/function.d.ts +4 -7
- package/lib/aws/function.js +9 -30
- package/lib/aws/index.d.ts +1 -1
- package/lib/aws/index.js +1 -1
- package/lib/aws/oidc/github-oidc-provider.d.ts +2 -6
- package/lib/aws/oidc/github-oidc-provider.js +10 -15
- package/lib/aws/oidc/index.d.ts +0 -1
- package/lib/aws/oidc/index.js +0 -1
- package/lib/aws/oidc/oidc-provider.d.ts +4 -6
- package/lib/aws/oidc/oidc-provider.js +9 -11
- package/lib/aws/policy-attachment.d.ts +9 -0
- package/lib/aws/policy-attachment.js +20 -0
- package/lib/aws/policy.d.ts +4 -16
- package/lib/aws/policy.js +9 -40
- package/lib/aws/queue.d.ts +4 -34
- package/lib/aws/queue.js +10 -20
- package/lib/aws/role.d.ts +4 -7
- package/lib/aws/role.js +17 -26
- package/lib/aws/ses.d.ts +4 -10
- package/lib/aws/ses.js +118 -131
- package/lib/aws/table.d.ts +4 -7
- package/lib/aws/table.js +8 -11
- package/lib/cloudflare/api.d.ts +0 -1
- package/lib/cloudflare/api.js +0 -1
- package/lib/cloudflare/asset-manifest.d.ts +0 -1
- package/lib/cloudflare/asset-manifest.js +0 -1
- package/lib/cloudflare/auth.d.ts +0 -1
- package/lib/cloudflare/auth.js +0 -1
- package/lib/cloudflare/bindings.d.ts +4 -3
- package/lib/cloudflare/bindings.js +3 -2
- package/lib/cloudflare/bound.d.ts +7 -0
- package/lib/cloudflare/bound.js +1 -0
- package/lib/cloudflare/bucket.d.ts +7 -15
- package/lib/cloudflare/bucket.js +12 -19
- package/lib/cloudflare/durable-object-namespace.d.ts +0 -3
- package/lib/cloudflare/durable-object-namespace.js +0 -4
- package/lib/cloudflare/generate-asset-manifest.d.ts +0 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -2
- package/lib/cloudflare/index.d.ts +0 -1
- package/lib/cloudflare/index.js +0 -1
- package/lib/cloudflare/kv-namespace.d.ts +6 -9
- package/lib/cloudflare/kv-namespace.js +16 -20
- package/lib/cloudflare/state.d.ts +0 -1
- package/lib/cloudflare/state.js +0 -1
- package/lib/cloudflare/static-site-router.d.ts +0 -1
- package/lib/cloudflare/static-site-router.js +0 -4
- package/lib/cloudflare/static-site.d.ts +5 -11
- package/lib/cloudflare/static-site.js +16 -28
- package/lib/cloudflare/types.d.ts +0 -1
- package/lib/cloudflare/types.js +0 -1
- package/lib/cloudflare/upload-asset-manifest.d.ts +0 -1
- package/lib/cloudflare/upload-asset-manifest.js +0 -1
- package/lib/cloudflare/worker-metadata.d.ts +0 -1
- package/lib/cloudflare/worker-metadata.js +0 -1
- package/lib/cloudflare/worker-migration.d.ts +0 -1
- package/lib/cloudflare/worker-migration.js +0 -1
- package/lib/cloudflare/worker.d.ts +13 -13
- package/lib/cloudflare/worker.js +43 -43
- package/lib/context.d.ts +58 -0
- package/lib/context.js +36 -0
- package/lib/destroy.d.ts +10 -13
- package/lib/destroy.js +104 -43
- package/lib/{esbuild.d.ts → esbuild/bundle.d.ts} +4 -18
- package/lib/{esbuild.js → esbuild/bundle.js} +10 -24
- package/lib/esbuild/index.d.ts +1 -0
- package/lib/esbuild/index.js +1 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +20 -0
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +16 -0
- package/lib/fs/index.d.ts +2 -0
- package/lib/fs/index.js +2 -0
- package/lib/github/client.d.ts +0 -1
- package/lib/github/client.js +0 -1
- package/lib/github/index.d.ts +0 -1
- package/lib/github/index.js +0 -1
- package/lib/github/secret.d.ts +6 -7
- package/lib/github/secret.js +30 -36
- package/lib/index.d.ts +7 -10
- package/lib/index.js +6 -9
- package/lib/resource.d.ts +34 -62
- package/lib/resource.js +29 -270
- package/lib/scope.d.ts +35 -14
- package/lib/scope.js +78 -23
- package/lib/secret.d.ts +7 -0
- package/lib/secret.js +17 -0
- package/lib/state.d.ts +14 -10
- package/lib/state.js +15 -13
- package/lib/stripe/index.d.ts +0 -1
- package/lib/stripe/index.js +0 -1
- package/lib/stripe/price.d.ts +4 -5
- package/lib/stripe/price.js +10 -20
- package/lib/stripe/product.d.ts +4 -5
- package/lib/stripe/product.js +10 -20
- package/lib/stripe/webhook.d.ts +4 -6
- package/lib/stripe/webhook.js +12 -25
- package/lib/test/bun.d.ts +19 -0
- package/lib/test/bun.js +55 -0
- package/lib/{utils → util}/content-type.d.ts +0 -1
- package/lib/{utils → util}/content-type.js +0 -1
- package/lib/util/encrypt.d.ts +16 -0
- package/lib/util/encrypt.js +44 -0
- package/lib/{error.d.ts → util/ignore.d.ts} +0 -1
- package/lib/{error.js → util/ignore.js} +0 -1
- package/lib/{utils → util}/retry.d.ts +0 -1
- package/lib/{utils → util}/retry.js +0 -1
- package/lib/util/serde.d.ts +3 -0
- package/lib/util/serde.js +46 -0
- package/lib/util/slugify.d.ts +1 -0
- package/lib/util/slugify.js +3 -0
- package/package.json +5 -7
- package/src/alchemy.ts +120 -0
- package/src/apply.ts +109 -111
- package/src/aws/bucket.ts +11 -14
- package/src/aws/function.ts +13 -33
- package/src/aws/index.ts +1 -0
- package/src/aws/oidc/github-oidc-provider.ts +14 -19
- package/src/aws/oidc/oidc-provider.ts +19 -12
- package/src/aws/policy-attachment.ts +51 -0
- package/src/aws/policy.ts +15 -60
- package/src/aws/queue.ts +17 -21
- package/src/aws/role.ts +25 -28
- package/src/aws/ses.ts +150 -164
- package/src/aws/table.ts +15 -15
- package/src/cloudflare/bindings.ts +12 -2
- package/src/cloudflare/bound.ts +18 -0
- package/src/cloudflare/bucket.ts +40 -48
- package/src/cloudflare/durable-object-namespace.ts +0 -10
- package/src/cloudflare/generate-asset-manifest.ts +1 -1
- package/src/cloudflare/kv-namespace.ts +35 -33
- package/src/cloudflare/static-site-router.ts +0 -3
- package/src/cloudflare/static-site.ts +43 -59
- package/src/cloudflare/worker.ts +76 -64
- package/src/context.ts +122 -0
- package/src/destroy.ts +124 -73
- package/src/{esbuild.ts → esbuild/bundle.ts} +16 -28
- package/src/esbuild/index.ts +1 -0
- package/src/fs/file.ts +39 -0
- package/src/fs/folder.ts +32 -0
- package/src/fs/index.ts +2 -0
- package/src/github/secret.ts +53 -49
- package/src/index.ts +8 -15
- package/src/resource.ts +101 -462
- package/src/scope.ts +101 -36
- package/src/secret.ts +18 -0
- package/src/state.ts +30 -20
- package/src/stripe/price.ts +17 -22
- package/src/stripe/product.ts +17 -22
- package/src/stripe/webhook.ts +21 -27
- package/src/test/bun.ts +105 -0
- package/src/util/encrypt.ts +79 -0
- package/src/util/serde.ts +53 -0
- package/src/util/slugify.ts +3 -0
- package/lib/$.d.ts +0 -12
- package/lib/$.d.ts.map +0 -1
- package/lib/$.js +0 -42
- package/lib/$.js.map +0 -1
- package/lib/alchemize.d.ts +0 -43
- package/lib/alchemize.d.ts.map +0 -1
- package/lib/alchemize.js +0 -112
- package/lib/alchemize.js.map +0 -1
- package/lib/apply.d.ts.map +0 -1
- package/lib/apply.js.map +0 -1
- package/lib/aws/account-id.d.ts.map +0 -1
- package/lib/aws/account-id.js.map +0 -1
- package/lib/aws/bucket.d.ts.map +0 -1
- package/lib/aws/bucket.js.map +0 -1
- package/lib/aws/function.d.ts.map +0 -1
- package/lib/aws/function.js.map +0 -1
- package/lib/aws/index.d.ts.map +0 -1
- package/lib/aws/index.js.map +0 -1
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +0 -1
- package/lib/aws/oidc/github-oidc-provider.js.map +0 -1
- package/lib/aws/oidc/index.d.ts.map +0 -1
- package/lib/aws/oidc/index.js.map +0 -1
- package/lib/aws/oidc/oidc-provider.d.ts.map +0 -1
- package/lib/aws/oidc/oidc-provider.js.map +0 -1
- package/lib/aws/policy.d.ts.map +0 -1
- package/lib/aws/policy.js.map +0 -1
- package/lib/aws/queue.d.ts.map +0 -1
- package/lib/aws/queue.js.map +0 -1
- package/lib/aws/role.d.ts.map +0 -1
- package/lib/aws/role.js.map +0 -1
- package/lib/aws/ses.d.ts.map +0 -1
- package/lib/aws/ses.js.map +0 -1
- package/lib/aws/table.d.ts.map +0 -1
- package/lib/aws/table.js.map +0 -1
- package/lib/cloudflare/api.d.ts.map +0 -1
- package/lib/cloudflare/api.js.map +0 -1
- package/lib/cloudflare/asset-manifest.d.ts.map +0 -1
- package/lib/cloudflare/asset-manifest.js.map +0 -1
- package/lib/cloudflare/auth.d.ts.map +0 -1
- package/lib/cloudflare/auth.js.map +0 -1
- package/lib/cloudflare/bindings.d.ts.map +0 -1
- package/lib/cloudflare/bindings.js.map +0 -1
- package/lib/cloudflare/bucket.d.ts.map +0 -1
- package/lib/cloudflare/bucket.js.map +0 -1
- package/lib/cloudflare/durable-object-namespace.d.ts.map +0 -1
- package/lib/cloudflare/durable-object-namespace.js.map +0 -1
- package/lib/cloudflare/generate-asset-manifest.d.ts.map +0 -1
- package/lib/cloudflare/generate-asset-manifest.js.map +0 -1
- package/lib/cloudflare/index.d.ts.map +0 -1
- package/lib/cloudflare/index.js.map +0 -1
- package/lib/cloudflare/kv-namespace.d.ts.map +0 -1
- package/lib/cloudflare/kv-namespace.js.map +0 -1
- package/lib/cloudflare/state.d.ts.map +0 -1
- package/lib/cloudflare/state.js.map +0 -1
- package/lib/cloudflare/static-site-router.d.ts.map +0 -1
- package/lib/cloudflare/static-site-router.js.map +0 -1
- package/lib/cloudflare/static-site.d.ts.map +0 -1
- package/lib/cloudflare/static-site.js.map +0 -1
- package/lib/cloudflare/types.d.ts.map +0 -1
- package/lib/cloudflare/types.js.map +0 -1
- package/lib/cloudflare/upload-asset-manifest.d.ts.map +0 -1
- package/lib/cloudflare/upload-asset-manifest.js.map +0 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +0 -1
- package/lib/cloudflare/worker-metadata.js.map +0 -1
- package/lib/cloudflare/worker-migration.d.ts.map +0 -1
- package/lib/cloudflare/worker-migration.js.map +0 -1
- package/lib/cloudflare/worker.d.ts.map +0 -1
- package/lib/cloudflare/worker.js.map +0 -1
- package/lib/destroy.d.ts.map +0 -1
- package/lib/destroy.js.map +0 -1
- package/lib/error.d.ts.map +0 -1
- package/lib/error.js.map +0 -1
- package/lib/esbuild.d.ts.map +0 -1
- package/lib/esbuild.js.map +0 -1
- package/lib/fs.d.ts +0 -12
- package/lib/fs.d.ts.map +0 -1
- package/lib/fs.js +0 -41
- package/lib/fs.js.map +0 -1
- package/lib/github/client.d.ts.map +0 -1
- package/lib/github/client.js.map +0 -1
- package/lib/github/index.d.ts.map +0 -1
- package/lib/github/index.js.map +0 -1
- package/lib/github/secret.d.ts.map +0 -1
- package/lib/github/secret.js.map +0 -1
- package/lib/global.d.ts +0 -16
- package/lib/global.d.ts.map +0 -1
- package/lib/global.js +0 -42
- package/lib/global.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/input.d.ts +0 -6
- package/lib/input.d.ts.map +0 -1
- package/lib/input.js +0 -2
- package/lib/input.js.map +0 -1
- package/lib/main.d.ts +0 -3
- package/lib/main.d.ts.map +0 -1
- package/lib/main.js +0 -32
- package/lib/main.js.map +0 -1
- package/lib/output.d.ts +0 -24
- package/lib/output.d.ts.map +0 -1
- package/lib/output.js +0 -23
- package/lib/output.js.map +0 -1
- package/lib/print.d.ts +0 -7
- package/lib/print.d.ts.map +0 -1
- package/lib/print.js +0 -10
- package/lib/print.js.map +0 -1
- package/lib/resource.d.ts.map +0 -1
- package/lib/resource.js.map +0 -1
- package/lib/scope.d.ts.map +0 -1
- package/lib/scope.js.map +0 -1
- package/lib/slug.d.ts +0 -2
- package/lib/slug.d.ts.map +0 -1
- package/lib/slug.js +0 -4
- package/lib/slug.js.map +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js.map +0 -1
- package/lib/stripe/index.d.ts.map +0 -1
- package/lib/stripe/index.js.map +0 -1
- package/lib/stripe/price.d.ts.map +0 -1
- package/lib/stripe/price.js.map +0 -1
- package/lib/stripe/product.d.ts.map +0 -1
- package/lib/stripe/product.js.map +0 -1
- package/lib/stripe/webhook.d.ts.map +0 -1
- package/lib/stripe/webhook.js.map +0 -1
- package/lib/utils/content-type.d.ts.map +0 -1
- package/lib/utils/content-type.js.map +0 -1
- package/lib/utils/retry.d.ts.map +0 -1
- package/lib/utils/retry.js.map +0 -1
- package/src/$.ts +0 -69
- package/src/alchemize.ts +0 -186
- package/src/fs.ts +0 -47
- package/src/global.ts +0 -63
- package/src/input.ts +0 -16
- package/src/main.ts +0 -34
- package/src/output.ts +0 -71
- package/src/print.ts +0 -18
- package/src/slug.ts +0 -3
- /package/src/{utils → util}/content-type.ts +0 -0
- /package/src/{error.ts → util/ignore.ts} +0 -0
- /package/src/{utils → util}/retry.ts +0 -0
package/src/scope.ts
CHANGED
|
@@ -1,51 +1,116 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import type { PendingResource, ResourceID } from "./resource";
|
|
3
|
+
import {
|
|
4
|
+
FileSystemStateStore,
|
|
5
|
+
type StateStore,
|
|
6
|
+
type StateStoreType,
|
|
7
|
+
} from "./state";
|
|
5
8
|
|
|
6
9
|
const scopeStorage = new AsyncLocalStorage<Scope>();
|
|
7
10
|
|
|
11
|
+
export type ScopeOptions = {
|
|
12
|
+
appName?: string;
|
|
13
|
+
stage: string;
|
|
14
|
+
parent?: Scope;
|
|
15
|
+
scopeName?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
stateStore?: StateStoreType;
|
|
18
|
+
quiet?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
8
21
|
export class Scope {
|
|
9
|
-
public
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
public static get(): Scope | undefined {
|
|
23
|
+
return scopeStorage.getStore();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static get current(): Scope {
|
|
27
|
+
const scope = Scope.get();
|
|
28
|
+
if (!scope) {
|
|
29
|
+
throw new Error("Not running within an Alchemy Scope");
|
|
14
30
|
}
|
|
15
|
-
|
|
31
|
+
return scope;
|
|
32
|
+
}
|
|
16
33
|
|
|
17
|
-
public readonly
|
|
34
|
+
public readonly resources = new Map<ResourceID, PendingResource>();
|
|
35
|
+
public readonly appName: string | undefined;
|
|
36
|
+
public readonly stage: string;
|
|
37
|
+
public readonly scopeName: string | null;
|
|
38
|
+
public readonly parent: Scope | undefined;
|
|
39
|
+
public readonly password: string | undefined;
|
|
40
|
+
public readonly state: StateStore;
|
|
41
|
+
public readonly quiet: boolean;
|
|
18
42
|
|
|
19
|
-
constructor(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
constructor(options: ScopeOptions) {
|
|
44
|
+
this.appName = options.appName;
|
|
45
|
+
this.stage = options.stage;
|
|
46
|
+
this.scopeName = options.scopeName ?? null;
|
|
47
|
+
this.parent = options.parent ?? Scope.get();
|
|
48
|
+
this.quiet = options.quiet ?? this.parent?.quiet ?? false;
|
|
49
|
+
if (this.parent && !this.scopeName) {
|
|
50
|
+
throw new Error("Scope name is required when creating a child scope");
|
|
51
|
+
}
|
|
52
|
+
this.password = options.password;
|
|
53
|
+
this.state = new (options.stateStore ?? FileSystemStateStore)(this);
|
|
54
|
+
}
|
|
23
55
|
|
|
24
|
-
public
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Then join the current scope name (if any) onto the parent's path
|
|
28
|
-
return this.scopeName ? path.join(parentPath, this.scopeName) : parentPath;
|
|
56
|
+
public async delete(resourceID: ResourceID) {
|
|
57
|
+
await this.state.delete(resourceID);
|
|
58
|
+
this.resources.delete(resourceID);
|
|
29
59
|
}
|
|
30
|
-
}
|
|
31
60
|
|
|
32
|
-
|
|
61
|
+
private _seq = 0;
|
|
33
62
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
63
|
+
public seq() {
|
|
64
|
+
return this._seq++;
|
|
65
|
+
}
|
|
37
66
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
}
|
|
67
|
+
public get chain(): string[] {
|
|
68
|
+
const thisScope = this.scopeName ? [this.scopeName] : [];
|
|
69
|
+
const app = this.appName ? [this.appName] : [];
|
|
70
|
+
if (this.parent) {
|
|
71
|
+
return [...this.parent.chain, ...thisScope];
|
|
72
|
+
} else {
|
|
73
|
+
return [...app, this.stage, ...thisScope];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public enter() {
|
|
78
|
+
scopeStorage.enterWith(this);
|
|
79
|
+
}
|
|
44
80
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
81
|
+
public async init() {
|
|
82
|
+
await this.state.init?.();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async deinit() {
|
|
86
|
+
await this.state.deinit?.();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public fqn(resourceID: ResourceID): string {
|
|
90
|
+
return [...this.chain, resourceID].join("/");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async finalize() {
|
|
94
|
+
// TODO
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async run<T>(fn: (scope: Scope) => Promise<T>): Promise<T> {
|
|
98
|
+
return scopeStorage.run(this, () => fn(this));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[Symbol.asyncDispose]() {
|
|
102
|
+
return this.finalize();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns a string representation of the scope.
|
|
107
|
+
*/
|
|
108
|
+
toString() {
|
|
109
|
+
return `Scope(
|
|
110
|
+
chain=${this.chain.join("/")},
|
|
111
|
+
resources=[${Array.from(this.resources.values())
|
|
112
|
+
.map((r) => r.ID)
|
|
113
|
+
.join(",\n ")}]
|
|
114
|
+
)`;
|
|
115
|
+
}
|
|
51
116
|
}
|
package/src/secret.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class Secret {
|
|
2
|
+
public readonly type = "secret";
|
|
3
|
+
constructor(readonly unencrypted: string) {}
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function isSecret(binding: any): binding is Secret {
|
|
7
|
+
return (
|
|
8
|
+
binding instanceof Secret ||
|
|
9
|
+
(typeof binding === "object" && binding.type === "secret_text")
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function secret<S extends string | undefined>(unencrypted: S): Secret {
|
|
14
|
+
if (unencrypted === undefined) {
|
|
15
|
+
throw new Error("Secret cannot be undefined");
|
|
16
|
+
}
|
|
17
|
+
return new Secret(unencrypted);
|
|
18
|
+
}
|
package/src/state.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import type { Resource, ResourceProps } from "./resource";
|
|
4
|
+
import type { Scope } from "./scope";
|
|
5
|
+
import { ignore } from "./util/ignore";
|
|
6
|
+
import { deserialize, serialize } from "./util/serde";
|
|
4
7
|
|
|
5
|
-
export interface State
|
|
8
|
+
export interface State<
|
|
9
|
+
Kind extends string = string,
|
|
10
|
+
Props extends ResourceProps = ResourceProps,
|
|
11
|
+
Out extends Resource = Resource,
|
|
12
|
+
> {
|
|
6
13
|
status:
|
|
7
14
|
| `creating`
|
|
8
15
|
| `created`
|
|
@@ -10,14 +17,19 @@ export interface State {
|
|
|
10
17
|
| `updated`
|
|
11
18
|
| `deleting`
|
|
12
19
|
| `deleted`;
|
|
13
|
-
|
|
20
|
+
kind: Kind;
|
|
21
|
+
id: string;
|
|
22
|
+
fqn: string;
|
|
23
|
+
seq: number;
|
|
14
24
|
data: Record<string, any>;
|
|
15
|
-
deps: string[];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
output:
|
|
25
|
+
// deps: string[];
|
|
26
|
+
props: Props;
|
|
27
|
+
oldProps?: Props;
|
|
28
|
+
output: Out;
|
|
19
29
|
}
|
|
20
30
|
|
|
31
|
+
export type StateStoreType = new (scope: Scope) => StateStore;
|
|
32
|
+
|
|
21
33
|
export interface StateStore {
|
|
22
34
|
/** Initialize the state container if one is required */
|
|
23
35
|
init?(): Promise<void>;
|
|
@@ -34,23 +46,21 @@ export interface StateStore {
|
|
|
34
46
|
delete(key: string): Promise<void>;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
export type StateStoreType = new (dir: string) => StateStore;
|
|
49
|
+
const stateRootDir = path.join(process.cwd(), ".alchemy");
|
|
40
50
|
|
|
41
51
|
export class FileSystemStateStore implements StateStore {
|
|
42
|
-
public readonly
|
|
43
|
-
constructor(
|
|
44
|
-
this.
|
|
52
|
+
public readonly dir: string;
|
|
53
|
+
constructor(public readonly scope: Scope) {
|
|
54
|
+
this.dir = path.join(stateRootDir, ...scope.chain);
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
async init(): Promise<void> {
|
|
48
|
-
await fs.promises.mkdir(
|
|
49
|
-
await fs.promises.mkdir(this.
|
|
58
|
+
await fs.promises.mkdir(stateRootDir, { recursive: true });
|
|
59
|
+
await fs.promises.mkdir(this.dir, { recursive: true });
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
async deinit(): Promise<void> {
|
|
53
|
-
await ignore("ENOENT", () => fs.promises.rmdir(this.
|
|
63
|
+
await ignore("ENOENT", () => fs.promises.rmdir(this.dir));
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
async count(): Promise<number> {
|
|
@@ -59,7 +69,7 @@ export class FileSystemStateStore implements StateStore {
|
|
|
59
69
|
|
|
60
70
|
async list(): Promise<string[]> {
|
|
61
71
|
try {
|
|
62
|
-
const files = await fs.promises.readdir(this.
|
|
72
|
+
const files = await fs.promises.readdir(this.dir, {
|
|
63
73
|
withFileTypes: true,
|
|
64
74
|
});
|
|
65
75
|
return files
|
|
@@ -79,7 +89,7 @@ export class FileSystemStateStore implements StateStore {
|
|
|
79
89
|
await this.getPath(key),
|
|
80
90
|
"utf8",
|
|
81
91
|
);
|
|
82
|
-
return JSON.parse(content) as State;
|
|
92
|
+
return (await deserialize(this.scope, JSON.parse(content))) as State;
|
|
83
93
|
} catch (error: any) {
|
|
84
94
|
if (error.code === "ENOENT") {
|
|
85
95
|
return undefined;
|
|
@@ -91,7 +101,7 @@ export class FileSystemStateStore implements StateStore {
|
|
|
91
101
|
async set(key: string, value: State): Promise<void> {
|
|
92
102
|
return fs.promises.writeFile(
|
|
93
103
|
await this.getPath(key),
|
|
94
|
-
JSON.stringify(value, null, 2),
|
|
104
|
+
JSON.stringify(await serialize(this.scope, value), null, 2),
|
|
95
105
|
);
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -120,7 +130,7 @@ export class FileSystemStateStore implements StateStore {
|
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
private async getPath(key: string): Promise<string> {
|
|
123
|
-
const file = path.join(this.
|
|
133
|
+
const file = path.join(this.dir, `${key}.json`);
|
|
124
134
|
const dir = path.dirname(file);
|
|
125
135
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
126
136
|
return file;
|
package/src/stripe/price.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stripe from "stripe";
|
|
2
|
-
import type { Context } from "../
|
|
2
|
+
import type { Context } from "../context";
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -98,7 +98,7 @@ export interface PriceProps {
|
|
|
98
98
|
/**
|
|
99
99
|
* Output from the Stripe price
|
|
100
100
|
*/
|
|
101
|
-
export interface
|
|
101
|
+
export interface Price extends Resource<"stripe::Price">, PriceProps {
|
|
102
102
|
/**
|
|
103
103
|
* The ID of the price
|
|
104
104
|
*/
|
|
@@ -125,9 +125,13 @@ export interface PriceOutput extends PriceProps {
|
|
|
125
125
|
lookupKey?: string;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export
|
|
128
|
+
export const Price = Resource(
|
|
129
129
|
"stripe::Price",
|
|
130
|
-
async (
|
|
130
|
+
async function (
|
|
131
|
+
this: Context<Price>,
|
|
132
|
+
id: string,
|
|
133
|
+
props: PriceProps,
|
|
134
|
+
): Promise<Price> {
|
|
131
135
|
// Get Stripe API key from context or environment
|
|
132
136
|
const apiKey = process.env.STRIPE_API_KEY;
|
|
133
137
|
if (!apiKey) {
|
|
@@ -137,32 +141,25 @@ export class Price extends Resource(
|
|
|
137
141
|
// Initialize Stripe client
|
|
138
142
|
const stripe = new Stripe(apiKey);
|
|
139
143
|
|
|
140
|
-
if (
|
|
144
|
+
if (this.phase === "delete") {
|
|
141
145
|
try {
|
|
142
|
-
if (
|
|
146
|
+
if (this.phase === "delete" && this.output?.id) {
|
|
143
147
|
// Prices can't be deleted, only deactivated
|
|
144
|
-
await stripe.prices.update(
|
|
148
|
+
await stripe.prices.update(this.output.id, { active: false });
|
|
145
149
|
}
|
|
146
150
|
} catch (error) {
|
|
147
151
|
// Ignore if the price doesn't exist
|
|
148
152
|
console.error("Error deactivating price:", error);
|
|
149
153
|
}
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
...props,
|
|
154
|
-
id: "",
|
|
155
|
-
createdAt: 0,
|
|
156
|
-
livemode: false,
|
|
157
|
-
type: (props.recurring ? "recurring" : "one_time") as Stripe.Price.Type,
|
|
158
|
-
};
|
|
155
|
+
return this.destroy();
|
|
159
156
|
} else {
|
|
160
157
|
try {
|
|
161
158
|
let price: Stripe.Price;
|
|
162
159
|
|
|
163
|
-
if (
|
|
160
|
+
if (this.phase === "update" && this.output?.id) {
|
|
164
161
|
// Update existing price (limited properties can be updated)
|
|
165
|
-
price = await stripe.prices.update(
|
|
162
|
+
price = await stripe.prices.update(this.output.id, {
|
|
166
163
|
active: props.active,
|
|
167
164
|
metadata: props.metadata,
|
|
168
165
|
nickname: props.nickname,
|
|
@@ -213,7 +210,7 @@ export class Price extends Resource(
|
|
|
213
210
|
: undefined;
|
|
214
211
|
|
|
215
212
|
// Map Stripe API response to our output format
|
|
216
|
-
|
|
213
|
+
return this({
|
|
217
214
|
id: price.id,
|
|
218
215
|
product:
|
|
219
216
|
typeof price.product === "string"
|
|
@@ -232,13 +229,11 @@ export class Price extends Resource(
|
|
|
232
229
|
livemode: price.livemode,
|
|
233
230
|
type: price.type as Stripe.Price.Type,
|
|
234
231
|
lookupKey: price.lookup_key || undefined,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
return output;
|
|
232
|
+
});
|
|
238
233
|
} catch (error) {
|
|
239
234
|
console.error("Error creating/updating price:", error);
|
|
240
235
|
throw error;
|
|
241
236
|
}
|
|
242
237
|
}
|
|
243
238
|
},
|
|
244
|
-
)
|
|
239
|
+
);
|
package/src/stripe/product.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stripe from "stripe";
|
|
2
|
-
import type { Context } from "../
|
|
2
|
+
import type { Context } from "../context";
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
4
|
|
|
5
5
|
type ProductType = Stripe.Product.Type;
|
|
@@ -67,7 +67,7 @@ export interface ProductProps {
|
|
|
67
67
|
/**
|
|
68
68
|
* Output from the Stripe product
|
|
69
69
|
*/
|
|
70
|
-
export interface
|
|
70
|
+
export interface Product extends Resource<"stripe::Product">, ProductProps {
|
|
71
71
|
/**
|
|
72
72
|
* The ID of the product
|
|
73
73
|
*/
|
|
@@ -99,9 +99,13 @@ export interface ProductOutput extends ProductProps {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export
|
|
102
|
+
export const Product = Resource(
|
|
103
103
|
"stripe::Product",
|
|
104
|
-
async (
|
|
104
|
+
async function (
|
|
105
|
+
this: Context<Product>,
|
|
106
|
+
id: string,
|
|
107
|
+
props: ProductProps,
|
|
108
|
+
): Promise<Product> {
|
|
105
109
|
// Get Stripe API key from context or environment
|
|
106
110
|
const apiKey = process.env.STRIPE_API_KEY;
|
|
107
111
|
if (!apiKey) {
|
|
@@ -111,10 +115,10 @@ export class Product extends Resource(
|
|
|
111
115
|
// Initialize Stripe client
|
|
112
116
|
const stripe = new Stripe(apiKey);
|
|
113
117
|
|
|
114
|
-
if (
|
|
118
|
+
if (this.phase === "delete") {
|
|
115
119
|
try {
|
|
116
|
-
if (
|
|
117
|
-
await stripe.products.update(
|
|
120
|
+
if (this.phase === "delete" && this.output?.id) {
|
|
121
|
+
await stripe.products.update(this.output.id, { active: false });
|
|
118
122
|
}
|
|
119
123
|
} catch (error) {
|
|
120
124
|
// Ignore if the product doesn't exist
|
|
@@ -122,20 +126,14 @@ export class Product extends Resource(
|
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
// Return a minimal output for deleted state
|
|
125
|
-
return
|
|
126
|
-
...props,
|
|
127
|
-
id: "",
|
|
128
|
-
createdAt: 0,
|
|
129
|
-
updatedAt: 0,
|
|
130
|
-
livemode: false,
|
|
131
|
-
};
|
|
129
|
+
return this.destroy();
|
|
132
130
|
} else {
|
|
133
131
|
try {
|
|
134
132
|
let product: Stripe.Product;
|
|
135
133
|
|
|
136
|
-
if (
|
|
134
|
+
if (this.phase === "update" && this.output?.id) {
|
|
137
135
|
// Update existing product
|
|
138
|
-
product = await stripe.products.update(
|
|
136
|
+
product = await stripe.products.update(this.output.id, {
|
|
139
137
|
name: props.name,
|
|
140
138
|
description: props.description,
|
|
141
139
|
active: props.active,
|
|
@@ -162,8 +160,7 @@ export class Product extends Resource(
|
|
|
162
160
|
});
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
const output: ProductOutput = {
|
|
163
|
+
return this({
|
|
167
164
|
id: product.id,
|
|
168
165
|
name: product.name,
|
|
169
166
|
description: product.description || undefined,
|
|
@@ -181,13 +178,11 @@ export class Product extends Resource(
|
|
|
181
178
|
livemode: product.livemode,
|
|
182
179
|
updatedAt: product.updated,
|
|
183
180
|
packageDimensions: product.package_dimensions || undefined,
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
return output;
|
|
181
|
+
});
|
|
187
182
|
} catch (error) {
|
|
188
183
|
console.error("Error creating/updating product:", error);
|
|
189
184
|
throw error;
|
|
190
185
|
}
|
|
191
186
|
}
|
|
192
187
|
},
|
|
193
|
-
)
|
|
188
|
+
);
|
package/src/stripe/webhook.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stripe from "stripe";
|
|
2
|
-
import type { Context } from "../
|
|
2
|
+
import type { Context } from "../context";
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
4
|
|
|
5
5
|
export type EnabledEvent = Stripe.WebhookEndpointUpdateParams.EnabledEvent;
|
|
@@ -47,7 +47,9 @@ export interface WebhookEndpointProps {
|
|
|
47
47
|
/**
|
|
48
48
|
* Output from the Stripe webhook endpoint
|
|
49
49
|
*/
|
|
50
|
-
export interface
|
|
50
|
+
export interface WebhookEndpoint
|
|
51
|
+
extends Resource<"stripe::WebhookEndpoint">,
|
|
52
|
+
WebhookEndpointProps {
|
|
51
53
|
/**
|
|
52
54
|
* The ID of the webhook
|
|
53
55
|
*/
|
|
@@ -84,9 +86,13 @@ export interface WebhookEndpointOutput extends WebhookEndpointProps {
|
|
|
84
86
|
status: string;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
export
|
|
89
|
+
export const WebhookEndpoint = Resource(
|
|
88
90
|
"stripe::WebhookEndpoint",
|
|
89
|
-
async (
|
|
91
|
+
async function (
|
|
92
|
+
this: Context<WebhookEndpoint>,
|
|
93
|
+
id: string,
|
|
94
|
+
props: WebhookEndpointProps,
|
|
95
|
+
) {
|
|
90
96
|
// Get Stripe API key from context or environment
|
|
91
97
|
const apiKey = process.env.STRIPE_API_KEY;
|
|
92
98
|
if (!apiKey) {
|
|
@@ -96,34 +102,25 @@ export class WebhookEndpoint extends Resource(
|
|
|
96
102
|
// Initialize Stripe client
|
|
97
103
|
const stripe = new Stripe(apiKey);
|
|
98
104
|
|
|
99
|
-
if (
|
|
105
|
+
if (this.phase === "delete") {
|
|
100
106
|
try {
|
|
101
107
|
// Get the webhook ID from the stored output
|
|
102
|
-
if (
|
|
103
|
-
await stripe.webhookEndpoints.del(
|
|
108
|
+
if (this.phase === "delete" && this.output?.id) {
|
|
109
|
+
await stripe.webhookEndpoints.del(this.output.id);
|
|
104
110
|
}
|
|
105
111
|
} catch (error) {
|
|
106
112
|
// Ignore if the webhook doesn't exist
|
|
107
113
|
console.error("Error deleting webhook:", error);
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
...props,
|
|
113
|
-
id: "",
|
|
114
|
-
secret: "",
|
|
115
|
-
createdAt: 0,
|
|
116
|
-
updatedAt: 0,
|
|
117
|
-
livemode: false,
|
|
118
|
-
status: "deleted",
|
|
119
|
-
};
|
|
116
|
+
return this.destroy();
|
|
120
117
|
} else {
|
|
121
118
|
try {
|
|
122
119
|
let webhook: Stripe.WebhookEndpoint;
|
|
123
120
|
|
|
124
|
-
if (
|
|
121
|
+
if (this.phase === "update" && this.output?.id) {
|
|
125
122
|
// Update existing webhook
|
|
126
|
-
webhook = await stripe.webhookEndpoints.update(
|
|
123
|
+
webhook = await stripe.webhookEndpoints.update(this.output.id, {
|
|
127
124
|
url: props.url,
|
|
128
125
|
enabled_events: props.enabledEvents,
|
|
129
126
|
description: props.description,
|
|
@@ -152,12 +149,11 @@ export class WebhookEndpoint extends Resource(
|
|
|
152
149
|
let secret = "";
|
|
153
150
|
if (webhook.secret) {
|
|
154
151
|
secret = webhook.secret;
|
|
155
|
-
} else if (
|
|
156
|
-
secret =
|
|
152
|
+
} else if (this.phase === "update" && this.output?.secret) {
|
|
153
|
+
secret = this.output.secret;
|
|
157
154
|
}
|
|
158
155
|
|
|
159
|
-
|
|
160
|
-
const output: WebhookEndpointOutput = {
|
|
156
|
+
return this({
|
|
161
157
|
id: webhook.id,
|
|
162
158
|
url: webhook.url,
|
|
163
159
|
enabledEvents: webhook.enabled_events as EnabledEvent[],
|
|
@@ -172,13 +168,11 @@ export class WebhookEndpoint extends Resource(
|
|
|
172
168
|
livemode: webhook.livemode,
|
|
173
169
|
updatedAt: webhook.created, // Using created timestamp as updated
|
|
174
170
|
status: webhook.status,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
return output;
|
|
171
|
+
});
|
|
178
172
|
} catch (error) {
|
|
179
173
|
console.error("Error creating/updating webhook:", error);
|
|
180
174
|
throw error;
|
|
181
175
|
}
|
|
182
176
|
}
|
|
183
177
|
},
|
|
184
|
-
)
|
|
178
|
+
);
|
package/src/test/bun.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/// <reference types="bun" />
|
|
2
|
+
|
|
3
|
+
import { afterAll, it } from "bun:test";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { alchemy } from "../alchemy";
|
|
6
|
+
import { destroy } from "../destroy";
|
|
7
|
+
import type { Scope } from "../scope";
|
|
8
|
+
|
|
9
|
+
declare module "../alchemy" {
|
|
10
|
+
interface Alchemy {
|
|
11
|
+
test: typeof test;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
alchemy.test = test;
|
|
16
|
+
|
|
17
|
+
export interface TestOptions {
|
|
18
|
+
destroy?: boolean;
|
|
19
|
+
quiet?: boolean;
|
|
20
|
+
password?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const globalTestScope = alchemy.scope("test");
|
|
24
|
+
|
|
25
|
+
type test = {
|
|
26
|
+
(name: string, fn: (scope: Scope) => Promise<any>, timeout?: number): void;
|
|
27
|
+
(
|
|
28
|
+
name: string,
|
|
29
|
+
options: TestOptions,
|
|
30
|
+
fn: (scope: Scope) => Promise<any>,
|
|
31
|
+
timeout?: number,
|
|
32
|
+
): void;
|
|
33
|
+
skipIf(condition: boolean): test;
|
|
34
|
+
scope: Scope;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
38
|
+
test.skipIf = (condition: boolean) => {
|
|
39
|
+
if (condition) {
|
|
40
|
+
// TODO: proxy through to bun:test.skipIf
|
|
41
|
+
return (...args: any[]) => {};
|
|
42
|
+
}
|
|
43
|
+
return test;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const localTestScope = alchemy.scope(path.basename(meta.filename), {
|
|
47
|
+
parent: globalTestScope,
|
|
48
|
+
});
|
|
49
|
+
test.scope = localTestScope;
|
|
50
|
+
|
|
51
|
+
afterAll(() => alchemy.destroy(test.scope));
|
|
52
|
+
|
|
53
|
+
return test as any;
|
|
54
|
+
|
|
55
|
+
function test(
|
|
56
|
+
...args:
|
|
57
|
+
| [
|
|
58
|
+
name: string,
|
|
59
|
+
options: TestOptions,
|
|
60
|
+
fn: (scope: Scope) => Promise<void>,
|
|
61
|
+
]
|
|
62
|
+
| [name: string, fn: (scope: Scope) => Promise<void>]
|
|
63
|
+
) {
|
|
64
|
+
const testName = args[0];
|
|
65
|
+
const _options = typeof args[1] === "object" ? args[1] : undefined;
|
|
66
|
+
const spread = (obj: any) =>
|
|
67
|
+
obj && typeof obj === "object"
|
|
68
|
+
? Object.fromEntries(
|
|
69
|
+
Object.entries(obj).flatMap(([k, v]) =>
|
|
70
|
+
v !== undefined ? [[k, v]] : [],
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
: {};
|
|
74
|
+
const options = {
|
|
75
|
+
destroy: false,
|
|
76
|
+
quiet: false,
|
|
77
|
+
password: "test-password",
|
|
78
|
+
...spread(defaultOptions),
|
|
79
|
+
...spread(_options),
|
|
80
|
+
};
|
|
81
|
+
const fn = typeof args[1] === "function" ? args[1] : args[2]!;
|
|
82
|
+
|
|
83
|
+
return alchemy.run(
|
|
84
|
+
testName,
|
|
85
|
+
{
|
|
86
|
+
...options,
|
|
87
|
+
parent: localTestScope,
|
|
88
|
+
},
|
|
89
|
+
async (scope) => {
|
|
90
|
+
return it(testName, async () => {
|
|
91
|
+
// bun will call from a different scope, so we need to enter the test's scope manually
|
|
92
|
+
scope.enter();
|
|
93
|
+
try {
|
|
94
|
+
await fn(scope);
|
|
95
|
+
} finally {
|
|
96
|
+
if (options.destroy !== false) {
|
|
97
|
+
// TODO: auto-destroy resources
|
|
98
|
+
await destroy(scope);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|