alchemy 0.2.3 → 0.2.5

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 (141) hide show
  1. package/lib/ai/ark.d.ts +11 -0
  2. package/lib/ai/ark.js +86 -0
  3. package/lib/ai/client.d.ts +51 -0
  4. package/lib/ai/client.js +29 -0
  5. package/lib/ai/data.d.ts +101 -0
  6. package/lib/ai/data.js +70 -0
  7. package/lib/ai/document.d.ts +101 -0
  8. package/lib/ai/document.js +82 -0
  9. package/lib/ai/index.d.ts +3 -0
  10. package/lib/ai/index.js +3 -0
  11. package/lib/alchemy.d.ts +83 -2
  12. package/lib/alchemy.js +31 -3
  13. package/lib/aws/bucket.d.ts +87 -1
  14. package/lib/aws/bucket.js +48 -1
  15. package/lib/aws/function.d.ts +186 -1
  16. package/lib/aws/function.js +62 -8
  17. package/lib/aws/oidc/github-oidc-provider.d.ts +81 -0
  18. package/lib/aws/oidc/github-oidc-provider.js +68 -0
  19. package/lib/aws/oidc/oidc-provider.d.ts +14 -6
  20. package/lib/aws/oidc/oidc-provider.js +26 -5
  21. package/lib/aws/policy-attachment.d.ts +44 -1
  22. package/lib/aws/policy-attachment.js +31 -0
  23. package/lib/aws/policy.d.ts +160 -1
  24. package/lib/aws/policy.js +78 -0
  25. package/lib/aws/queue.d.ts +83 -1
  26. package/lib/aws/queue.js +39 -0
  27. package/lib/aws/role.d.ts +162 -1
  28. package/lib/aws/role.js +114 -0
  29. package/lib/aws/ses.d.ts +57 -2
  30. package/lib/aws/ses.js +42 -1
  31. package/lib/aws/table.d.ts +96 -1
  32. package/lib/aws/table.js +36 -0
  33. package/lib/cloudflare/asset-manifest.js +0 -1
  34. package/lib/cloudflare/bound.js +0 -1
  35. package/lib/cloudflare/bucket.d.ts +37 -1
  36. package/lib/cloudflare/bucket.js +36 -0
  37. package/lib/cloudflare/durable-object-namespace.d.ts +25 -0
  38. package/lib/cloudflare/durable-object-namespace.js +22 -0
  39. package/lib/cloudflare/kv-namespace.d.ts +37 -1
  40. package/lib/cloudflare/kv-namespace.js +36 -0
  41. package/lib/cloudflare/static-site.d.ts +53 -1
  42. package/lib/cloudflare/static-site.js +53 -1
  43. package/lib/cloudflare/worker-metadata.js +0 -1
  44. package/lib/cloudflare/worker.d.ts +64 -1
  45. package/lib/cloudflare/worker.js +63 -0
  46. package/lib/cloudflare/wrangler.json.d.ts +1 -1
  47. package/lib/cloudflare/zone-settings.js +0 -1
  48. package/lib/cloudflare/zone.d.ts +56 -1
  49. package/lib/cloudflare/zone.js +55 -0
  50. package/lib/destroy.js +1 -1
  51. package/lib/esbuild/bundle.d.ts +43 -4
  52. package/lib/esbuild/bundle.js +16 -0
  53. package/lib/fs/file-collection.d.ts +16 -0
  54. package/lib/fs/file-collection.js +6 -0
  55. package/lib/fs/file-ref.d.ts +14 -0
  56. package/lib/fs/file-ref.js +6 -0
  57. package/lib/fs/file.d.ts +71 -25
  58. package/lib/fs/file.js +20 -44
  59. package/lib/fs/folder.d.ts +37 -5
  60. package/lib/fs/folder.js +26 -2
  61. package/lib/fs/index.d.ts +6 -0
  62. package/lib/fs/index.js +6 -0
  63. package/lib/fs/json-file.d.ts +16 -0
  64. package/lib/fs/json-file.js +7 -0
  65. package/lib/fs/text-file.d.ts +12 -0
  66. package/lib/fs/text-file.js +7 -0
  67. package/lib/fs/typescript-file.d.ts +19 -0
  68. package/lib/fs/typescript-file.js +14 -0
  69. package/lib/fs/yaml-file.d.ts +19 -0
  70. package/lib/fs/yaml-file.js +8 -0
  71. package/lib/github/secret.d.ts +63 -2
  72. package/lib/github/secret.js +61 -1
  73. package/lib/internal/docs.d.ts +5 -0
  74. package/lib/internal/docs.js +287 -0
  75. package/lib/resource.d.ts +1 -1
  76. package/lib/secret.d.ts +67 -0
  77. package/lib/secret.js +67 -0
  78. package/lib/shadcn/component.d.ts +1 -1
  79. package/lib/stripe/price.d.ts +51 -1
  80. package/lib/stripe/price.js +50 -0
  81. package/lib/stripe/product.d.ts +39 -1
  82. package/lib/stripe/product.js +38 -0
  83. package/lib/stripe/webhook.d.ts +46 -1
  84. package/lib/stripe/webhook.js +45 -0
  85. package/lib/test/bun.d.ts +64 -0
  86. package/lib/test/bun.js +35 -3
  87. package/lib/util/serde.js +14 -0
  88. package/lib/vite/vite.d.ts +1 -1
  89. package/lib/vitepress/dependencies.d.ts +2 -6
  90. package/lib/vitepress/vitepress.d.ts +12 -1
  91. package/lib/vitepress/vitepress.js +29 -23
  92. package/package.json +8 -3
  93. package/src/ai/ark.ts +118 -0
  94. package/src/ai/client.ts +78 -0
  95. package/src/ai/data.ts +149 -0
  96. package/src/ai/document.ts +165 -0
  97. package/src/ai/index.ts +3 -0
  98. package/src/alchemy.ts +88 -5
  99. package/src/aws/bucket.ts +111 -7
  100. package/src/aws/function.ts +231 -10
  101. package/src/aws/oidc/github-oidc-provider.ts +83 -0
  102. package/src/aws/oidc/oidc-provider.ts +39 -7
  103. package/src/aws/policy-attachment.ts +44 -1
  104. package/src/aws/policy.ts +177 -2
  105. package/src/aws/queue.ts +89 -0
  106. package/src/aws/role.ts +174 -2
  107. package/src/aws/ses.ts +56 -1
  108. package/src/aws/table.ts +103 -0
  109. package/src/cloudflare/bucket.ts +36 -0
  110. package/src/cloudflare/durable-object-namespace.ts +25 -0
  111. package/src/cloudflare/kv-namespace.ts +36 -0
  112. package/src/cloudflare/static-site.ts +53 -1
  113. package/src/cloudflare/worker.ts +63 -0
  114. package/src/cloudflare/zone.ts +55 -0
  115. package/src/destroy.ts +1 -1
  116. package/src/esbuild/bundle.ts +53 -3
  117. package/src/fs/file-collection.ts +24 -0
  118. package/src/fs/file-ref.ts +22 -0
  119. package/src/fs/file.ts +70 -77
  120. package/src/fs/folder.ts +44 -5
  121. package/src/fs/index.ts +6 -0
  122. package/src/fs/json-file.ts +23 -0
  123. package/src/fs/text-file.ts +19 -0
  124. package/src/fs/typescript-file.ts +36 -0
  125. package/src/fs/yaml-file.ts +26 -0
  126. package/src/github/secret.ts +65 -2
  127. package/src/internal/docs.ts +338 -0
  128. package/src/resource.ts +1 -1
  129. package/src/secret.ts +67 -0
  130. package/src/stripe/price.ts +50 -0
  131. package/src/stripe/product.ts +38 -0
  132. package/src/stripe/webhook.ts +45 -0
  133. package/src/test/bun.ts +83 -3
  134. package/src/util/serde.ts +17 -0
  135. package/src/vitepress/vitepress.ts +47 -26
  136. package/lib/docs/document.d.ts +0 -123
  137. package/lib/docs/document.js +0 -67
  138. package/lib/docs/index.d.ts +0 -1
  139. package/lib/docs/index.js +0 -1
  140. package/src/docs/document.ts +0 -225
  141. package/src/docs/index.ts +0 -1
@@ -3,11 +3,71 @@ import { Resource } from "../resource";
3
3
  import { createGitHubClient, verifyGitHubAuth } from "./client";
4
4
  /**
5
5
  * Resource for managing GitHub repository secrets
6
+ *
7
+ * Authentication is handled in the following order:
8
+ * 1. `token` parameter in the resource props (if provided)
9
+ * 2. `GITHUB_TOKEN` environment variable
10
+ *
11
+ * The token must have the following permissions:
12
+ * - 'repo' scope for private repositories
13
+ * - 'public_repo' scope for public repositories
14
+ *
15
+ * @example
16
+ * // Create a secret using GITHUB_TOKEN environment variable:
17
+ * const secret = await GitHubSecret("my-secret", {
18
+ * owner: "my-github-username",
19
+ * repository: "my-repo",
20
+ * name: "API_KEY",
21
+ * value: alchemy.secret("my-secret-value")
22
+ * });
23
+ *
24
+ * @example
25
+ * // Create a secret with a custom GitHub token:
26
+ * const secret = await GitHubSecret("my-secret", {
27
+ * owner: "my-github-username",
28
+ * repository: "my-repo",
29
+ * name: "API_KEY",
30
+ * value: alchemy.secret("my-secret-value"),
31
+ * token: alchemy.secret(process.env.CUSTOM_GITHUB_TOKEN)
32
+ * });
33
+ *
34
+ * @example
35
+ * // Create multiple secrets with environment variables:
36
+ * const secrets = await Promise.all([
37
+ * GitHubSecret("aws-secret", {
38
+ * owner: "my-github-username",
39
+ * repository: "cloud-app",
40
+ * name: "AWS_ROLE_ARN",
41
+ * value: alchemy.secret(process.env.AWS_ROLE_ARN)
42
+ * }),
43
+ * GitHubSecret("cf-secret", {
44
+ * owner: "my-github-username",
45
+ * repository: "cloud-app",
46
+ * name: "CLOUDFLARE_API_KEY",
47
+ * value: alchemy.secret(process.env.CLOUDFLARE_API_KEY)
48
+ * })
49
+ * ]);
50
+ *
51
+ * @example
52
+ * // Create a secret in a secure scope with a password:
53
+ * await alchemy.run("secure-scope", {
54
+ * password: process.env.SECRET_PASSPHRASE
55
+ * }, async () => {
56
+ * const secret = await GitHubSecret("deploy-secret", {
57
+ * owner: "my-github-username",
58
+ * repository: "my-app",
59
+ * name: "DEPLOY_TOKEN",
60
+ * value: alchemy.secret(process.env.DEPLOY_TOKEN),
61
+ * token: alchemy.secret(process.env.GITHUB_TOKEN)
62
+ * });
63
+ * });
6
64
  */
7
65
  export const GitHubSecret = Resource("github::Secret", async function (id, props) {
8
66
  // Create authenticated Octokit client - will automatically handle token resolution
9
67
  /// TODO: use fetch
10
- const octokit = await createGitHubClient({ token: props.token });
68
+ const octokit = await createGitHubClient({
69
+ token: props.token?.unencrypted,
70
+ });
11
71
  // Verify authentication and permissions
12
72
  if (!this.quiet) {
13
73
  await verifyGitHubAuth(octokit, props.owner, props.repository);
@@ -0,0 +1,5 @@
1
+ export interface DocsProps {
2
+ docs?: boolean | number;
3
+ }
4
+ export type AlchemyDocs = Awaited<ReturnType<typeof AlchemyDocs>>;
5
+ export declare function AlchemyDocs({ docs: isDocsEnabled }: DocsProps): Promise<void>;
@@ -0,0 +1,287 @@
1
+ import { type } from "arktype";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { Data } from "../ai/data";
5
+ import { Document } from "../ai/document";
6
+ import { alchemy } from "../alchemy";
7
+ import { Folder } from "../fs/folder";
8
+ import { VitePressProject } from "../vitepress/vitepress";
9
+ export async function AlchemyDocs({ docs: isDocsEnabled }) {
10
+ const root = (await Folder("alchemy-web")).path;
11
+ const docs = (await Folder(path.join(root, "docs"))).path;
12
+ const providersDir = (await Folder(path.join(docs, "providers"))).path;
13
+ const exclude = ["util", "test", "vitepress", "vite", "shadcn", "internal"];
14
+ // Get all folders in the alchemy/src directory
15
+ let providers = (await fs.readdir(path.resolve("alchemy", "src"), {
16
+ withFileTypes: true,
17
+ }))
18
+ .filter((dirent) => dirent.isDirectory() && !exclude.includes(dirent.name))
19
+ .map((dirent) => path.join(dirent.parentPath, dirent.name));
20
+ // For each provider, list all files
21
+ if (isDocsEnabled === false) {
22
+ return;
23
+ }
24
+ else if (typeof isDocsEnabled === "number") {
25
+ providers = providers.slice(0, isDocsEnabled);
26
+ }
27
+ const providerDocs = await Promise.all(providers.map(async (provider) => {
28
+ const providerName = path.basename(provider);
29
+ const files = (await fs.readdir(path.resolve(provider), {
30
+ withFileTypes: true,
31
+ }))
32
+ .filter((dirent) => dirent.isFile())
33
+ .map((dirent) => path.relative(process.cwd(), path.resolve(provider, dirent.name)))
34
+ .filter((file) => file.endsWith(".ts") && !file.endsWith("index.ts"));
35
+ const { object: { groups }, } = await Data(`docs/${providerName}`, {
36
+ schema: type({
37
+ groups: type({
38
+ title: type("string").describe("The title of the group, should be the Resource Name exactly without spaces, e.g. Bucket or Static Site."),
39
+ filename: type("string").describe("The filename of the Resource's Document, e.g. bucket.md or static-site.md"),
40
+ category: type("'Resource'|'Client'|'Utility'|'Types'").describe("The classification of the Resource's Document, one of: Resource, Client, Utility, or Types."),
41
+ }).array(),
42
+ }),
43
+ system: await alchemy `
44
+ You are a technical writer tasked with identifying the distinct documents that need to be written for a document group (folder) in a documentation site.
45
+ You will be provided with a list of documents and instructions on how to classify them.
46
+ Each document has a title, file name, and category.
47
+ `,
48
+ prompt: await alchemy `
49
+ Identify and classify the documents that need to be written for the '${provider}' Service's Alchemy Resources.
50
+ For background knowledge on Alchemy, see ${alchemy.file("./README.md")}.
51
+ For background knowledge on the structure of an Alchemy Resource, see ${alchemy.file("./.cursorrules")}.
52
+
53
+ The ${provider} Service has the following resources:
54
+ ${alchemy.files(files)}
55
+
56
+ A file is considered a "Resource" if it contains a const <ResourceName> = Resource(...) call or if it is a function that calls a Resource function, e.g. const TypeScriptFile = () => File(...).
57
+ A file is considered a "Client" if it exposes a wrapper around creating a SDK client or fetch.
58
+ A file is considered a "Utility" if it contains utility functions that are not resources or clients.
59
+ A file is considered a "Types" if it contains just type definitions and maybe helpers around working with those types.
60
+
61
+ The title should be simply the name of the resource, e.g. "Bucket" or "Function", except with spaces, e.g. "Static Site" instead of "StaticSite". Maintain all other casing.
62
+ `,
63
+ });
64
+ const providerDocsDir = (await Folder(path.join(providersDir, providerName))).path;
65
+ const documents = await Promise.all(groups
66
+ .filter((g) => g.category === "Resource")
67
+ .map(async (g) => Document(`docs/${providerName}/${g.title}`, {
68
+ title: g.title,
69
+ path: path.join(providerDocsDir, `${g.filename.replace(".ts", "").replace(".md", "")}.md`),
70
+ prompt: await alchemy `
71
+ You are a technical writer writing API documentation for an Alchemy IaC Resource.
72
+ See ${alchemy.file("./README.md")} to understand the overview of Alchemy.
73
+ See ${alchemy.file("./.cursorrules")} to better understand the structure and convention of an Alchemy Resource.
74
+
75
+ Relevant files for the ${providerName} Service:
76
+ ${alchemy.files(files)}
77
+
78
+ Write concise documentation for the "${g.title}" Resource.
79
+
80
+ > [!CAUTION]
81
+ > Avoid the temptation to over explain or over describe. Focus on concise, simple, high value snippets. One heading and 0-1 descriptions per snippet.
82
+
83
+ > [!TIP]
84
+ > Make sure the examples follow a natural progression from the minimal example to logical next steps of how the Resource might be used.
85
+
86
+ Each document must follow the following format:
87
+
88
+ # ${g.title}
89
+
90
+ (simple description with an external link to the provider's website)
91
+ e.g.
92
+ The Efs component lets you add [Amazon Elastic File System (EFS)](https://docs.aws.amazon.com/efs/latest/ug/whatisefs.html) to your app.
93
+
94
+ # Minimal Example
95
+
96
+ \`\`\`ts
97
+ import { ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
98
+
99
+ (example)
100
+ \`\`\`
101
+
102
+
103
+ # Create the ${g.title}
104
+
105
+ \`\`\`ts
106
+ import { ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
107
+
108
+ (example)
109
+ \`\`\`
110
+
111
+ ${providerName === "cloudflare"
112
+ ? await alchemy `# Bind to a Worker
113
+ (if it is a Cloudflare Resource)
114
+
115
+ \`\`\`ts
116
+ import { Worker, ${g.title.replaceAll(" ", "")} } from "alchemy/${providerName}";
117
+
118
+ const myResource = await ${g.title.replaceAll(" ", "")}("my-resource", {
119
+ // ...
120
+ });
121
+
122
+ await Worker("my-worker", {
123
+ name: "my-worker",
124
+ script: "console.log('Hello, world!')",
125
+ bindings: {
126
+ myResource,
127
+ },
128
+ });
129
+ \`\`\``
130
+ : ""}
131
+ `,
132
+ })));
133
+ return {
134
+ dir: providerDocsDir,
135
+ provider: providerName,
136
+ documents,
137
+ };
138
+ }));
139
+ await VitePressProject("docs", {
140
+ name: "alchemy-web",
141
+ title: "Alchemy",
142
+ description: "Alchemy is an TypeScript-native, embeddable IaC library",
143
+ overwrite: true,
144
+ delete: false,
145
+ tsconfig: {
146
+ extends: "../tsconfig.base.json",
147
+ references: ["../alchemy/tsconfig.json"],
148
+ },
149
+ devDependencies: {
150
+ alchemy: "workspace:*",
151
+ },
152
+ theme: {
153
+ light: "light-plus",
154
+ dark: "dark-plus",
155
+ },
156
+ home: {
157
+ layout: "home",
158
+ hero: {
159
+ name: "Alchemy",
160
+ text: "Materialize all the things! 🪄",
161
+ tagline: "TypeScript-native Infrastructure-as-Code (IaC)",
162
+ image: {
163
+ src: "./public/alchemist.png",
164
+ alt: "The Alchemist",
165
+ },
166
+ actions: [
167
+ {
168
+ text: "Get Started",
169
+ link: "/docs",
170
+ theme: "brand",
171
+ },
172
+ ],
173
+ },
174
+ features: [
175
+ {
176
+ title: "JS-native",
177
+ details: "No second language, toolchains, dependencies, processes, services, etc. to lug around.",
178
+ },
179
+ {
180
+ title: "Async",
181
+ details: "Resources are just async functions - no complex abstraction to learn.",
182
+ },
183
+ {
184
+ title: "ESM",
185
+ details: "Built exclusively on ESM, with a slight preference for modern JS runtimes like Bun.",
186
+ },
187
+ {
188
+ title: "Embeddable",
189
+ details: "Runs in any JavaScript/TypeScript environment, including the browser!",
190
+ },
191
+ {
192
+ title: "Extensible",
193
+ details: "Implement your own resources with a simple function.",
194
+ },
195
+ {
196
+ title: "AI-first",
197
+ details: "Create, copy, fork, and modify resources using LLMs to fit your needs.",
198
+ },
199
+ {
200
+ title: "No dependencies",
201
+ details: "The alchemy core package has 0 required dependencies.",
202
+ },
203
+ {
204
+ title: "No service",
205
+ details: "State files are stored locally in your project for easy inspection and version control.",
206
+ },
207
+ {
208
+ title: "No strong opinions",
209
+ details: "Structure your codebase however you want, store state anywhere - we don't care!",
210
+ },
211
+ ],
212
+ },
213
+ themeConfig: {
214
+ sidebar: {
215
+ "/blog/": [{ text: "Blog", items: [{ text: "Blog", link: "/blog/" }] }],
216
+ "/docs/": [
217
+ {
218
+ text: "Getting Started",
219
+ items: [{ text: "Install", link: "/docs/getting-started/install" }],
220
+ },
221
+ {
222
+ text: "Guides",
223
+ items: [
224
+ {
225
+ text: "Custom Resource",
226
+ link: "/docs/guides/custom-resource",
227
+ },
228
+ {
229
+ text: "Automating with LLMs",
230
+ link: "/docs/guides/llms",
231
+ },
232
+ ],
233
+ },
234
+ {
235
+ text: "Core",
236
+ collapsed: true,
237
+ items: [
238
+ { text: "App", link: "/docs/core/app" },
239
+ { text: "Resource", link: "/docs/core/resource" },
240
+ { text: "Scope", link: "/docs/core/scope" },
241
+ { text: "Phase", link: "/docs/core/phase" },
242
+ { text: "Finalize", link: "/docs/core/finalize" },
243
+ { text: "State", link: "/docs/core/state" },
244
+ { text: "Secret", link: "/docs/core/secret" },
245
+ { text: "Context", link: "/docs/core/context" },
246
+ ],
247
+ },
248
+ {
249
+ text: "Providers",
250
+ items: providerDocs
251
+ .map((provider) => ({
252
+ text: provider.provider,
253
+ link: `/docs/providers/${provider.provider}`,
254
+ collapsed: true,
255
+ items: provider.documents.map((document) => ({
256
+ text: document.title,
257
+ link: `/docs/providers/${provider.provider}/${path.basename(document.path, ".md")}`,
258
+ })),
259
+ }))
260
+ .sort((a, b) => a.text.localeCompare(b.text)),
261
+ },
262
+ ],
263
+ "/examples/": [
264
+ {
265
+ text: "Examples",
266
+ items: [{ text: "Foo", link: "/examples/foo" }],
267
+ },
268
+ ],
269
+ "/": [
270
+ {
271
+ text: "Home",
272
+ items: [
273
+ { text: "Markdown Examples", link: "/markdown-examples" },
274
+ { text: "Runtime API Examples", link: "/api-examples" },
275
+ ],
276
+ },
277
+ ],
278
+ },
279
+ socialLinks: [
280
+ {
281
+ icon: "github",
282
+ link: "https://github.com/sam-goodwin/alchemy",
283
+ },
284
+ ],
285
+ },
286
+ });
287
+ }
package/lib/resource.d.ts CHANGED
@@ -37,7 +37,7 @@ type IsClass = {
37
37
  new (_: never): never;
38
38
  };
39
39
  type ResourceLifecycleHandler = (this: Context<any, any>, id: string, props: any) => Promise<Resource<string>>;
40
- type Handler<F extends (...args: any[]) => any> = F | (((this: any, id: string, props?: Parameters<F>[1]) => never) & IsClass);
40
+ type Handler<F extends (...args: any[]) => any> = F | (((this: any, id: string, props?: {}) => never) & IsClass);
41
41
  export declare function Resource<const Type extends string, F extends ResourceLifecycleHandler>(type: Type, fn: F): Handler<F>;
42
42
  export declare function Resource<const Type extends string, F extends ResourceLifecycleHandler>(type: Type, options: Partial<ProviderOptions>, fn: F): Handler<F>;
43
43
  export {};
package/lib/secret.d.ts CHANGED
@@ -1,7 +1,74 @@
1
+ /**
2
+ * Internal wrapper for sensitive values like API keys and credentials.
3
+ * When stored in alchemy state files, the value is automatically encrypted
4
+ * using the application's password. The password can be provided either:
5
+ *
6
+ * 1. Globally when initializing the alchemy application:
7
+ * ```ts
8
+ * const app = alchemy("my-app", {
9
+ * password: process.env.SECRET_PASSPHRASE
10
+ * });
11
+ * ```
12
+ *
13
+ * 2. For a specific scope using alchemy.run:
14
+ * ```ts
15
+ * await alchemy.run("scope-name", {
16
+ * password: process.env.SECRET_PASSPHRASE
17
+ * }, async () => {
18
+ * // Secrets in this scope will use this password
19
+ * alchemy.secret(process.env.MY_SECRET)
20
+ * });
21
+ * ```
22
+ *
23
+ * Without a password, secrets cannot be encrypted or decrypted, and operations
24
+ * involving sensitive values will fail.
25
+ *
26
+ * @example
27
+ * // In state file (.alchemy/app/prod/resource.json):
28
+ * {
29
+ * "props": {
30
+ * "apiKey": {
31
+ * "@secret": "encrypted-value-here..." // encrypted using app password
32
+ * }
33
+ * }
34
+ * }
35
+ */
1
36
  export declare class Secret {
2
37
  readonly unencrypted: string;
3
38
  readonly type = "secret";
4
39
  constructor(unencrypted: string);
5
40
  }
41
+ /**
42
+ * Type guard to check if a value is a Secret wrapper
43
+ */
6
44
  export declare function isSecret(binding: any): binding is Secret;
45
+ /**
46
+ * Wraps a sensitive value so it will be encrypted when stored in state files.
47
+ * Requires a password to be set either globally in the alchemy application options
48
+ * or locally in an alchemy.run scope.
49
+ *
50
+ * @example
51
+ * // Global password for all secrets
52
+ * const app = alchemy("my-app", {
53
+ * password: process.env.SECRET_PASSPHRASE
54
+ * });
55
+ *
56
+ * const resource = await Resource("my-resource", {
57
+ * apiKey: alchemy.secret(process.env.API_KEY)
58
+ * });
59
+ *
60
+ * @example
61
+ * // Scoped password for specific secrets
62
+ * await alchemy.run("secure-scope", {
63
+ * password: process.env.SCOPE_SECRET_PASSPHRASE
64
+ * }, async () => {
65
+ * const resource = await Resource("my-resource", {
66
+ * apiKey: alchemy.secret(process.env.API_KEY)
67
+ * });
68
+ * });
69
+ *
70
+ * @param unencrypted The sensitive value to encrypt in state files
71
+ * @throws {Error} If the value is undefined
72
+ * @throws {Error} If no password is set in the alchemy application options or current scope
73
+ */
7
74
  export declare function secret<S extends string | undefined>(unencrypted: S): Secret;
package/lib/secret.js CHANGED
@@ -1,3 +1,38 @@
1
+ /**
2
+ * Internal wrapper for sensitive values like API keys and credentials.
3
+ * When stored in alchemy state files, the value is automatically encrypted
4
+ * using the application's password. The password can be provided either:
5
+ *
6
+ * 1. Globally when initializing the alchemy application:
7
+ * ```ts
8
+ * const app = alchemy("my-app", {
9
+ * password: process.env.SECRET_PASSPHRASE
10
+ * });
11
+ * ```
12
+ *
13
+ * 2. For a specific scope using alchemy.run:
14
+ * ```ts
15
+ * await alchemy.run("scope-name", {
16
+ * password: process.env.SECRET_PASSPHRASE
17
+ * }, async () => {
18
+ * // Secrets in this scope will use this password
19
+ * alchemy.secret(process.env.MY_SECRET)
20
+ * });
21
+ * ```
22
+ *
23
+ * Without a password, secrets cannot be encrypted or decrypted, and operations
24
+ * involving sensitive values will fail.
25
+ *
26
+ * @example
27
+ * // In state file (.alchemy/app/prod/resource.json):
28
+ * {
29
+ * "props": {
30
+ * "apiKey": {
31
+ * "@secret": "encrypted-value-here..." // encrypted using app password
32
+ * }
33
+ * }
34
+ * }
35
+ */
1
36
  export class Secret {
2
37
  unencrypted;
3
38
  type = "secret";
@@ -5,10 +40,42 @@ export class Secret {
5
40
  this.unencrypted = unencrypted;
6
41
  }
7
42
  }
43
+ /**
44
+ * Type guard to check if a value is a Secret wrapper
45
+ */
8
46
  export function isSecret(binding) {
9
47
  return (binding instanceof Secret ||
10
48
  (typeof binding === "object" && binding.type === "secret_text"));
11
49
  }
50
+ /**
51
+ * Wraps a sensitive value so it will be encrypted when stored in state files.
52
+ * Requires a password to be set either globally in the alchemy application options
53
+ * or locally in an alchemy.run scope.
54
+ *
55
+ * @example
56
+ * // Global password for all secrets
57
+ * const app = alchemy("my-app", {
58
+ * password: process.env.SECRET_PASSPHRASE
59
+ * });
60
+ *
61
+ * const resource = await Resource("my-resource", {
62
+ * apiKey: alchemy.secret(process.env.API_KEY)
63
+ * });
64
+ *
65
+ * @example
66
+ * // Scoped password for specific secrets
67
+ * await alchemy.run("secure-scope", {
68
+ * password: process.env.SCOPE_SECRET_PASSPHRASE
69
+ * }, async () => {
70
+ * const resource = await Resource("my-resource", {
71
+ * apiKey: alchemy.secret(process.env.API_KEY)
72
+ * });
73
+ * });
74
+ *
75
+ * @param unencrypted The sensitive value to encrypt in state files
76
+ * @throws {Error} If the value is undefined
77
+ * @throws {Error} If no password is set in the alchemy application options or current scope
78
+ */
12
79
  export function secret(unencrypted) {
13
80
  if (unencrypted === undefined) {
14
81
  throw new Error("Secret cannot be undefined");
@@ -26,4 +26,4 @@ export interface ShadcnComponent extends ShadcnComponentProps, Resource {
26
26
  */
27
27
  name: string;
28
28
  }
29
- export declare const ShadcnComponent: ((this: Context<ShadcnComponent>, id: string, props: ShadcnComponentProps) => Promise<ShadcnComponent>) | (((this: any, id: string, props?: ShadcnComponentProps | undefined) => never) & (new (_: never) => never));
29
+ export declare const ShadcnComponent: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<ShadcnComponent>, id: string, props: ShadcnComponentProps) => Promise<ShadcnComponent>);
@@ -104,5 +104,55 @@ export interface Price extends Resource<"stripe::Price">, PriceProps {
104
104
  */
105
105
  lookupKey?: string;
106
106
  }
107
- export declare const Price: ((this: Context<Price>, id: string, props: PriceProps) => Promise<Price>) | (((this: any, id: string, props?: PriceProps | undefined) => never) & (new (_: never) => never));
107
+ /**
108
+ * Create and manage Stripe prices for products
109
+ *
110
+ * @example
111
+ * // Create a one-time fixed price for a product
112
+ * const oneTimePrice = await Price("basic-license", {
113
+ * currency: "usd",
114
+ * unitAmount: 2999, // $29.99
115
+ * product: "prod_xyz"
116
+ * });
117
+ *
118
+ * @example
119
+ * // Create a recurring subscription price with fixed monthly billing
120
+ * const subscriptionPrice = await Price("pro-monthly", {
121
+ * currency: "usd",
122
+ * unitAmount: 1499, // $14.99/month
123
+ * product: "prod_xyz",
124
+ * recurring: {
125
+ * interval: "month",
126
+ * usageType: "licensed"
127
+ * }
128
+ * });
129
+ *
130
+ * @example
131
+ * // Create a metered price for usage-based billing
132
+ * const meteredPrice = await Price("storage", {
133
+ * currency: "usd",
134
+ * unitAmount: 25, // $0.25 per GB
135
+ * product: "prod_xyz",
136
+ * recurring: {
137
+ * interval: "month",
138
+ * usageType: "metered",
139
+ * aggregateUsage: "sum"
140
+ * }
141
+ * });
142
+ *
143
+ * @example
144
+ * // Create a tiered price with tax behavior
145
+ * const tieredPrice = await Price("enterprise", {
146
+ * currency: "usd",
147
+ * unitAmount: 10000, // $100.00
148
+ * product: "prod_xyz",
149
+ * billingScheme: "tiered",
150
+ * taxBehavior: "exclusive",
151
+ * metadata: {
152
+ * tier: "enterprise",
153
+ * features: "all"
154
+ * }
155
+ * });
156
+ */
157
+ export declare const Price: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Price>, id: string, props: PriceProps) => Promise<Price>);
108
158
  export {};
@@ -1,5 +1,55 @@
1
1
  import Stripe from "stripe";
2
2
  import { Resource } from "../resource";
3
+ /**
4
+ * Create and manage Stripe prices for products
5
+ *
6
+ * @example
7
+ * // Create a one-time fixed price for a product
8
+ * const oneTimePrice = await Price("basic-license", {
9
+ * currency: "usd",
10
+ * unitAmount: 2999, // $29.99
11
+ * product: "prod_xyz"
12
+ * });
13
+ *
14
+ * @example
15
+ * // Create a recurring subscription price with fixed monthly billing
16
+ * const subscriptionPrice = await Price("pro-monthly", {
17
+ * currency: "usd",
18
+ * unitAmount: 1499, // $14.99/month
19
+ * product: "prod_xyz",
20
+ * recurring: {
21
+ * interval: "month",
22
+ * usageType: "licensed"
23
+ * }
24
+ * });
25
+ *
26
+ * @example
27
+ * // Create a metered price for usage-based billing
28
+ * const meteredPrice = await Price("storage", {
29
+ * currency: "usd",
30
+ * unitAmount: 25, // $0.25 per GB
31
+ * product: "prod_xyz",
32
+ * recurring: {
33
+ * interval: "month",
34
+ * usageType: "metered",
35
+ * aggregateUsage: "sum"
36
+ * }
37
+ * });
38
+ *
39
+ * @example
40
+ * // Create a tiered price with tax behavior
41
+ * const tieredPrice = await Price("enterprise", {
42
+ * currency: "usd",
43
+ * unitAmount: 10000, // $100.00
44
+ * product: "prod_xyz",
45
+ * billingScheme: "tiered",
46
+ * taxBehavior: "exclusive",
47
+ * metadata: {
48
+ * tier: "enterprise",
49
+ * features: "all"
50
+ * }
51
+ * });
52
+ */
3
53
  export const Price = Resource("stripe::Price", async function (id, props) {
4
54
  // Get Stripe API key from context or environment
5
55
  const apiKey = process.env.STRIPE_API_KEY;