alchemy 0.1.4 → 0.1.6

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.
@@ -0,0 +1,244 @@
1
+ import Stripe from "stripe";
2
+ import type { Context } from "../resource";
3
+ import { Resource } from "../resource";
4
+
5
+ /**
6
+ * Properties for price recurring configuration
7
+ */
8
+ export interface PriceRecurring {
9
+ /**
10
+ * Specifies billing frequency. Either 'day', 'week', 'month' or 'year'.
11
+ */
12
+ interval: Stripe.PriceCreateParams.Recurring.Interval;
13
+
14
+ /**
15
+ * The number of intervals between subscription billings. For example, interval=month and interval_count=3 bills every 3 months.
16
+ */
17
+ intervalCount?: number;
18
+
19
+ /**
20
+ * Configures how the quantity per period should be determined, can be either 'metered' or 'licensed'.
21
+ * 'licensed' will automatically bill the quantity set for a plan when adding it to a subscription,
22
+ * 'metered' will aggregate the total usage based on usage records.
23
+ */
24
+ usageType?: Stripe.PriceCreateParams.Recurring.UsageType;
25
+
26
+ /**
27
+ * Specifies a usage aggregation strategy for prices of `usage_type=metered`. Allowed values are `sum` for summing up all usage during a period,
28
+ * `last_during_period` for picking the last usage record reported within a period,
29
+ * `last_ever` for picking the last usage record ever (across period bounds) or `max` which picks the usage record with the maximum reported usage during a period.
30
+ */
31
+ aggregateUsage?: Stripe.PriceCreateParams.Recurring.AggregateUsage;
32
+ }
33
+
34
+ type TaxBehavior = Stripe.PriceCreateParams.TaxBehavior;
35
+ type BillingScheme = Stripe.PriceCreateParams.BillingScheme;
36
+
37
+ /**
38
+ * Properties for creating a Stripe price
39
+ */
40
+ export interface PriceProps {
41
+ /**
42
+ * The ID of the product that this price will belong to
43
+ */
44
+ product: string;
45
+
46
+ /**
47
+ * Three-letter ISO currency code, in lowercase. Must be a supported currency.
48
+ */
49
+ currency: string;
50
+
51
+ /**
52
+ * A positive integer in cents (or local equivalent) representing how much to charge (e.g., 999 for $9.99).
53
+ * Alternatively, use unit_amount_decimal to specify a precise amount.
54
+ */
55
+ unitAmount?: number;
56
+
57
+ /**
58
+ * Same as unit_amount, but accepts a decimal string (e.g., '9.99') with at most 12 decimal places.
59
+ * Only one of unit_amount and unit_amount_decimal can be set.
60
+ */
61
+ unitAmountDecimal?: string;
62
+
63
+ /**
64
+ * Whether the price can be used for new purchases
65
+ */
66
+ active?: boolean;
67
+
68
+ /**
69
+ * Describes how to compute the price per period. Either `per_unit` or `tiered`.
70
+ * `per_unit` indicates that the fixed amount (specified in `unit_amount` or `unit_amount_decimal`)
71
+ * will be charged per unit in `quantity`.
72
+ * `tiered` indicates that the unit pricing will be computed using a tiering strategy.
73
+ */
74
+ billingScheme?: BillingScheme;
75
+
76
+ /**
77
+ * A brief description of the price, hidden from customers.
78
+ */
79
+ nickname?: string;
80
+
81
+ /**
82
+ * The recurring components of a price such as `interval` and `interval_count`.
83
+ */
84
+ recurring?: PriceRecurring;
85
+
86
+ /**
87
+ * Set of key-value pairs that you can attach to an object.
88
+ */
89
+ metadata?: Record<string, string>;
90
+
91
+ /**
92
+ * Specifies whether the price is considered inclusive of taxes or exclusive of taxes.
93
+ * One of inclusive, exclusive, or unspecified.
94
+ */
95
+ taxBehavior?: TaxBehavior;
96
+ }
97
+
98
+ /**
99
+ * Output from the Stripe price
100
+ */
101
+ export interface PriceOutput extends PriceProps {
102
+ /**
103
+ * The ID of the price
104
+ */
105
+ id: string;
106
+
107
+ /**
108
+ * Time at which the object was created
109
+ */
110
+ createdAt: number;
111
+
112
+ /**
113
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
114
+ */
115
+ livemode: boolean;
116
+
117
+ /**
118
+ * The type of the price - either 'one_time' or 'recurring'
119
+ */
120
+ type: Stripe.Price.Type;
121
+
122
+ /**
123
+ * The lookup key (if any) used by the customer to identify this price object
124
+ */
125
+ lookupKey?: string;
126
+ }
127
+
128
+ export class Price extends Resource(
129
+ "stripe::Price",
130
+ async (ctx: Context<PriceOutput>, props: PriceProps) => {
131
+ // Get Stripe API key from context or environment
132
+ const apiKey = process.env.STRIPE_API_KEY;
133
+ if (!apiKey) {
134
+ throw new Error("STRIPE_API_KEY environment variable is required");
135
+ }
136
+
137
+ // Initialize Stripe client
138
+ const stripe = new Stripe(apiKey);
139
+
140
+ if (ctx.event === "delete") {
141
+ try {
142
+ if (ctx.event === "delete" && ctx.output?.id) {
143
+ // Prices can't be deleted, only deactivated
144
+ await stripe.prices.update(ctx.output.id, { active: false });
145
+ }
146
+ } catch (error) {
147
+ // Ignore if the price doesn't exist
148
+ console.error("Error deactivating price:", error);
149
+ }
150
+
151
+ // Return a minimal output for deleted state
152
+ return {
153
+ ...props,
154
+ id: "",
155
+ createdAt: 0,
156
+ livemode: false,
157
+ type: (props.recurring ? "recurring" : "one_time") as Stripe.Price.Type,
158
+ };
159
+ } else {
160
+ try {
161
+ let price: Stripe.Price;
162
+
163
+ if (ctx.event === "update" && ctx.output?.id) {
164
+ // Update existing price (limited properties can be updated)
165
+ price = await stripe.prices.update(ctx.output.id, {
166
+ active: props.active,
167
+ metadata: props.metadata,
168
+ nickname: props.nickname,
169
+ });
170
+ } else {
171
+ // Create new price
172
+ const createParams: Stripe.PriceCreateParams = {
173
+ currency: props.currency,
174
+ product: props.product,
175
+ active: props.active,
176
+ billing_scheme: props.billingScheme,
177
+ nickname: props.nickname,
178
+ metadata: props.metadata,
179
+ tax_behavior: props.taxBehavior,
180
+ };
181
+
182
+ // Add unit amount fields
183
+ if (props.unitAmount !== undefined) {
184
+ createParams.unit_amount = props.unitAmount;
185
+ } else if (props.unitAmountDecimal !== undefined) {
186
+ createParams.unit_amount_decimal = props.unitAmountDecimal;
187
+ }
188
+
189
+ // Add recurring parameters if present
190
+ if (props.recurring) {
191
+ createParams.recurring = {
192
+ interval: props.recurring.interval,
193
+ interval_count: props.recurring.intervalCount,
194
+ usage_type: props.recurring.usageType,
195
+ aggregate_usage: props.recurring.aggregateUsage,
196
+ };
197
+ }
198
+
199
+ price = await stripe.prices.create(createParams);
200
+ }
201
+
202
+ // Transform Stripe recurring object to our format
203
+ const recurring = price.recurring
204
+ ? {
205
+ interval: price.recurring
206
+ .interval as Stripe.PriceCreateParams.Recurring.Interval,
207
+ intervalCount: price.recurring.interval_count,
208
+ usageType: price.recurring
209
+ .usage_type as Stripe.PriceCreateParams.Recurring.UsageType,
210
+ aggregateUsage: price.recurring
211
+ .aggregate_usage as Stripe.PriceCreateParams.Recurring.AggregateUsage,
212
+ }
213
+ : undefined;
214
+
215
+ // Map Stripe API response to our output format
216
+ const output: PriceOutput = {
217
+ id: price.id,
218
+ product:
219
+ typeof price.product === "string"
220
+ ? price.product
221
+ : price.product.id,
222
+ currency: price.currency,
223
+ unitAmount: price.unit_amount || undefined,
224
+ unitAmountDecimal: price.unit_amount_decimal || undefined,
225
+ active: price.active,
226
+ billingScheme: price.billing_scheme as BillingScheme,
227
+ nickname: price.nickname || undefined,
228
+ recurring: recurring,
229
+ metadata: price.metadata || undefined,
230
+ taxBehavior: price.tax_behavior as TaxBehavior,
231
+ createdAt: price.created,
232
+ livemode: price.livemode,
233
+ type: price.type as Stripe.Price.Type,
234
+ lookupKey: price.lookup_key || undefined,
235
+ };
236
+
237
+ return output;
238
+ } catch (error) {
239
+ console.error("Error creating/updating price:", error);
240
+ throw error;
241
+ }
242
+ }
243
+ },
244
+ ) {}
@@ -0,0 +1,193 @@
1
+ import Stripe from "stripe";
2
+ import type { Context } from "../resource";
3
+ import { Resource } from "../resource";
4
+
5
+ type ProductType = Stripe.Product.Type;
6
+
7
+ /**
8
+ * Properties for creating a Stripe product
9
+ */
10
+ export interface ProductProps {
11
+ /**
12
+ * The product's name
13
+ */
14
+ name: string;
15
+
16
+ /**
17
+ * The product's description
18
+ */
19
+ description?: string;
20
+
21
+ /**
22
+ * Whether the product is active
23
+ */
24
+ active?: boolean;
25
+
26
+ /**
27
+ * A list of up to 8 URLs of images for this product
28
+ */
29
+ images?: string[];
30
+
31
+ /**
32
+ * A URL of the product's thumbnail
33
+ */
34
+ url?: string;
35
+
36
+ /**
37
+ * Whether this product is shipped (physical goods)
38
+ */
39
+ shippable?: boolean;
40
+
41
+ /**
42
+ * Product type: good, service
43
+ */
44
+ type?: ProductType;
45
+
46
+ /**
47
+ * Unit label to use on invoices
48
+ */
49
+ unitLabel?: string;
50
+
51
+ /**
52
+ * A label for the product to be displayed on Checkout
53
+ */
54
+ statementDescriptor?: string;
55
+
56
+ /**
57
+ * Set of key-value pairs for this product
58
+ */
59
+ metadata?: Record<string, string>;
60
+
61
+ /**
62
+ * Default tax code for the product
63
+ */
64
+ taxCode?: string;
65
+ }
66
+
67
+ /**
68
+ * Output from the Stripe product
69
+ */
70
+ export interface ProductOutput extends ProductProps {
71
+ /**
72
+ * The ID of the product
73
+ */
74
+ id: string;
75
+
76
+ /**
77
+ * Time at which the object was created
78
+ */
79
+ createdAt: number;
80
+
81
+ /**
82
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
83
+ */
84
+ livemode: boolean;
85
+
86
+ /**
87
+ * Time at which the object was last updated
88
+ */
89
+ updatedAt: number;
90
+
91
+ /**
92
+ * Whether this product can be used for Checkout/Payment Links
93
+ */
94
+ packageDimensions?: {
95
+ height: number;
96
+ length: number;
97
+ weight: number;
98
+ width: number;
99
+ };
100
+ }
101
+
102
+ export class Product extends Resource(
103
+ "stripe::Product",
104
+ async (ctx: Context<ProductOutput>, props: ProductProps) => {
105
+ // Get Stripe API key from context or environment
106
+ const apiKey = process.env.STRIPE_API_KEY;
107
+ if (!apiKey) {
108
+ throw new Error("STRIPE_API_KEY environment variable is required");
109
+ }
110
+
111
+ // Initialize Stripe client
112
+ const stripe = new Stripe(apiKey);
113
+
114
+ if (ctx.event === "delete") {
115
+ try {
116
+ if (ctx.event === "delete" && ctx.output?.id) {
117
+ await stripe.products.update(ctx.output.id, { active: false });
118
+ }
119
+ } catch (error) {
120
+ // Ignore if the product doesn't exist
121
+ console.error("Error deactivating product:", error);
122
+ }
123
+
124
+ // Return a minimal output for deleted state
125
+ return {
126
+ ...props,
127
+ id: "",
128
+ createdAt: 0,
129
+ updatedAt: 0,
130
+ livemode: false,
131
+ };
132
+ } else {
133
+ try {
134
+ let product: Stripe.Product;
135
+
136
+ if (ctx.event === "update" && ctx.output?.id) {
137
+ // Update existing product
138
+ product = await stripe.products.update(ctx.output.id, {
139
+ name: props.name,
140
+ description: props.description,
141
+ active: props.active,
142
+ images: props.images,
143
+ url: props.url,
144
+ statement_descriptor: props.statementDescriptor,
145
+ metadata: props.metadata,
146
+ tax_code: props.taxCode,
147
+ });
148
+ } else {
149
+ // Create new product
150
+ product = await stripe.products.create({
151
+ name: props.name,
152
+ description: props.description,
153
+ active: props.active,
154
+ images: props.images,
155
+ url: props.url,
156
+ shippable: props.shippable,
157
+ type: props.type as Stripe.ProductCreateParams.Type,
158
+ unit_label: props.unitLabel,
159
+ statement_descriptor: props.statementDescriptor,
160
+ metadata: props.metadata,
161
+ tax_code: props.taxCode,
162
+ });
163
+ }
164
+
165
+ // Map Stripe API response to our output format
166
+ const output: ProductOutput = {
167
+ id: product.id,
168
+ name: product.name,
169
+ description: product.description || undefined,
170
+ active: product.active,
171
+ images: product.images || undefined,
172
+ url: product.url || undefined,
173
+ shippable: product.shippable || undefined,
174
+ type: product.type as ProductType,
175
+ unitLabel: product.unit_label || undefined,
176
+ statementDescriptor: product.statement_descriptor || undefined,
177
+ metadata: product.metadata || undefined,
178
+ taxCode:
179
+ typeof product.tax_code === "string" ? product.tax_code : undefined,
180
+ createdAt: product.created,
181
+ livemode: product.livemode,
182
+ updatedAt: product.updated,
183
+ packageDimensions: product.package_dimensions || undefined,
184
+ };
185
+
186
+ return output;
187
+ } catch (error) {
188
+ console.error("Error creating/updating product:", error);
189
+ throw error;
190
+ }
191
+ }
192
+ },
193
+ ) {}
@@ -0,0 +1,184 @@
1
+ import Stripe from "stripe";
2
+ import type { Context } from "../resource";
3
+ import { Resource } from "../resource";
4
+
5
+ export type EnabledEvent = Stripe.WebhookEndpointUpdateParams.EnabledEvent;
6
+
7
+ /**
8
+ * Properties for creating a Stripe webhook endpoint
9
+ */
10
+ export interface WebhookEndpointProps {
11
+ /**
12
+ * The URL of the webhook endpoint
13
+ */
14
+ url: string;
15
+
16
+ /**
17
+ * The list of events to enable for this endpoint
18
+ */
19
+ enabledEvents: EnabledEvent[];
20
+
21
+ /**
22
+ * Description of the webhook
23
+ */
24
+ description?: string;
25
+
26
+ /**
27
+ * Whether the webhook is active
28
+ */
29
+ active?: boolean;
30
+
31
+ /**
32
+ * The API version events are rendered as for this webhook
33
+ */
34
+ apiVersion?: string;
35
+
36
+ /**
37
+ * Whether to include mounted endpoint events automatically
38
+ */
39
+ connect?: boolean;
40
+
41
+ /**
42
+ * Webhook endpoint metadata
43
+ */
44
+ metadata?: Record<string, string>;
45
+ }
46
+
47
+ /**
48
+ * Output from the Stripe webhook endpoint
49
+ */
50
+ export interface WebhookEndpointOutput extends WebhookEndpointProps {
51
+ /**
52
+ * The ID of the webhook
53
+ */
54
+ id: string;
55
+
56
+ /**
57
+ * The webhook endpoint's secret key, used to verify signatures on received events
58
+ */
59
+ secret: string;
60
+
61
+ /**
62
+ * The webhook endpoint's application
63
+ */
64
+ application?: string;
65
+
66
+ /**
67
+ * Time at which the object was created
68
+ */
69
+ createdAt: number;
70
+
71
+ /**
72
+ * Has the value true if the object exists in live mode or the value false if the object exists in test mode
73
+ */
74
+ livemode: boolean;
75
+
76
+ /**
77
+ * Time at which the object was last updated
78
+ */
79
+ updatedAt: number;
80
+
81
+ /**
82
+ * The status of the webhook
83
+ */
84
+ status: string;
85
+ }
86
+
87
+ export class WebhookEndpoint extends Resource(
88
+ "stripe::WebhookEndpoint",
89
+ async (ctx: Context<WebhookEndpointOutput>, props: WebhookEndpointProps) => {
90
+ // Get Stripe API key from context or environment
91
+ const apiKey = process.env.STRIPE_API_KEY;
92
+ if (!apiKey) {
93
+ throw new Error("STRIPE_API_KEY environment variable is required");
94
+ }
95
+
96
+ // Initialize Stripe client
97
+ const stripe = new Stripe(apiKey);
98
+
99
+ if (ctx.event === "delete") {
100
+ try {
101
+ // Get the webhook ID from the stored output
102
+ if (ctx.event === "delete" && ctx.output?.id) {
103
+ await stripe.webhookEndpoints.del(ctx.output.id);
104
+ }
105
+ } catch (error) {
106
+ // Ignore if the webhook doesn't exist
107
+ console.error("Error deleting webhook:", error);
108
+ }
109
+
110
+ // Return a minimal output for deleted state
111
+ return {
112
+ ...props,
113
+ id: "",
114
+ secret: "",
115
+ createdAt: 0,
116
+ updatedAt: 0,
117
+ livemode: false,
118
+ status: "deleted",
119
+ };
120
+ } else {
121
+ try {
122
+ let webhook: Stripe.WebhookEndpoint;
123
+
124
+ if (ctx.event === "update" && ctx.output?.id) {
125
+ // Update existing webhook
126
+ webhook = await stripe.webhookEndpoints.update(ctx.output.id, {
127
+ url: props.url,
128
+ enabled_events: props.enabledEvents,
129
+ description: props.description,
130
+ disabled: props.active === false,
131
+ metadata: props.metadata,
132
+ });
133
+ } else {
134
+ // Create new webhook
135
+ webhook = await stripe.webhookEndpoints.create({
136
+ url: props.url,
137
+ enabled_events: props.enabledEvents,
138
+ description: props.description,
139
+ metadata: props.metadata,
140
+ });
141
+
142
+ // For connect parameter, need to handle it separately if it exists
143
+ if (props.connect !== undefined) {
144
+ // Note: connect is specified at creation time and cannot be updated
145
+ console.log(
146
+ "Note: 'connect' parameter will be applied at creation time only",
147
+ );
148
+ }
149
+ }
150
+
151
+ // Store the secret if available
152
+ let secret = "";
153
+ if (webhook.secret) {
154
+ secret = webhook.secret;
155
+ } else if (ctx.event === "update" && ctx.output?.secret) {
156
+ secret = ctx.output.secret;
157
+ }
158
+
159
+ // Map Stripe API response to our output format
160
+ const output: WebhookEndpointOutput = {
161
+ id: webhook.id,
162
+ url: webhook.url,
163
+ enabledEvents: webhook.enabled_events as EnabledEvent[],
164
+ description: webhook.description || undefined,
165
+ active: webhook.status === "enabled",
166
+ apiVersion: webhook.api_version || undefined,
167
+ // connect: !!webhook.connect,
168
+ metadata: webhook.metadata || undefined,
169
+ secret: secret,
170
+ application: webhook.application || undefined,
171
+ createdAt: webhook.created,
172
+ livemode: webhook.livemode,
173
+ updatedAt: webhook.created, // Using created timestamp as updated
174
+ status: webhook.status,
175
+ };
176
+
177
+ return output;
178
+ } catch (error) {
179
+ console.error("Error creating/updating webhook:", error);
180
+ throw error;
181
+ }
182
+ }
183
+ },
184
+ ) {}
@@ -0,0 +1,61 @@
1
+ import { tool } from "ai";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import { z } from "zod";
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ export const installDependencies = tool({
9
+ description:
10
+ "Installs dependencies using bun with support for different dependency types (runtime, dev, or peer)",
11
+ parameters: z.object({
12
+ packages: z.array(z.string()).describe("Array of package names to install"),
13
+ type: z
14
+ .enum(["runtime", "dev", "peer"])
15
+ .optional()
16
+ .describe(
17
+ "Type of dependency to install: runtime, dev, or peer. Default 'runtime'.",
18
+ ),
19
+ }),
20
+ execute: async ({ packages, type }) => {
21
+ if (packages.length === 0) {
22
+ throw new Error("No packages specified for installation");
23
+ }
24
+
25
+ let command = "bun add";
26
+
27
+ // Add appropriate flags based on dependency type
28
+ switch (type) {
29
+ case "dev":
30
+ command += " -D";
31
+ break;
32
+ case "peer":
33
+ command += " --peer";
34
+ break;
35
+ // runtime dependencies don't need a special flag
36
+ }
37
+
38
+ // Add packages to the command
39
+ command += " " + packages.join(" ");
40
+
41
+ try {
42
+ console.log(`Executing: ${command}`);
43
+ const { stdout, stderr } = await execAsync(command);
44
+
45
+ if (stderr) {
46
+ console.warn("Installation warnings:", stderr);
47
+ }
48
+
49
+ return {
50
+ success: true,
51
+ command,
52
+ output: stdout,
53
+ packages,
54
+ type,
55
+ };
56
+ } catch (error: any) {
57
+ console.error("Installation failed:", error);
58
+ throw new Error(`Failed to install packages: ${error.message}`);
59
+ }
60
+ },
61
+ });
@@ -7,11 +7,11 @@ import {
7
7
  import { describe, expect, test } from "bun:test";
8
8
  import path from "node:path";
9
9
  import { apply } from "../../src/apply";
10
- import { Function } from "../../src/components/aws/function";
11
- import type { PolicyDocument } from "../../src/components/aws/policy";
12
- import { Role } from "../../src/components/aws/role";
13
- import { Bundle } from "../../src/components/esbuild";
10
+ import { Function } from "../../src/aws/function";
11
+ import type { PolicyDocument } from "../../src/aws/policy";
12
+ import { Role } from "../../src/aws/role";
14
13
  import { destroy } from "../../src/destroy";
14
+ import { Bundle } from "../../src/esbuild";
15
15
 
16
16
  const __dirname = path.dirname(new URL(import.meta.url).pathname);
17
17
 
@@ -5,7 +5,7 @@ import {
5
5
  } from "@aws-sdk/client-sqs";
6
6
  import { describe, expect, test } from "bun:test";
7
7
  import { apply } from "../../src/apply";
8
- import { Queue } from "../../src/components/aws/queue";
8
+ import { Queue } from "../../src/aws/queue";
9
9
  import { destroy } from "../../src/destroy";
10
10
 
11
11
  const sqs = new SQSClient({});