alchemy 0.36.0 → 0.37.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 (38) hide show
  1. package/lib/cloudflare/bindings.d.ts +1 -1
  2. package/lib/cloudflare/bindings.d.ts.map +1 -1
  3. package/lib/cloudflare/bound.d.ts +1 -1
  4. package/lib/cloudflare/bound.d.ts.map +1 -1
  5. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  6. package/lib/cloudflare/d1-database.js +0 -3
  7. package/lib/cloudflare/d1-database.js.map +1 -1
  8. package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
  9. package/lib/cloudflare/do-state-store/internal.js +35 -28
  10. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  11. package/lib/cloudflare/durable-object-namespace.d.ts +1 -1
  12. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  13. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  14. package/lib/cloudflare/website.d.ts.map +1 -1
  15. package/lib/cloudflare/website.js +28 -13
  16. package/lib/cloudflare/website.js.map +1 -1
  17. package/lib/cloudflare/worker-metadata.d.ts +8 -5
  18. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  19. package/lib/cloudflare/worker-metadata.js +48 -11
  20. package/lib/cloudflare/worker-metadata.js.map +1 -1
  21. package/lib/cloudflare/worker.d.ts +18 -12
  22. package/lib/cloudflare/worker.d.ts.map +1 -1
  23. package/lib/cloudflare/worker.js +74 -68
  24. package/lib/cloudflare/worker.js.map +1 -1
  25. package/lib/stripe/price.d.ts +24 -0
  26. package/lib/stripe/price.d.ts.map +1 -1
  27. package/lib/stripe/price.js +30 -0
  28. package/lib/stripe/price.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/cloudflare/bindings.ts +1 -1
  31. package/src/cloudflare/bound.ts +1 -1
  32. package/src/cloudflare/d1-database.ts +0 -4
  33. package/src/cloudflare/do-state-store/internal.ts +45 -29
  34. package/src/cloudflare/durable-object-namespace.ts +2 -3
  35. package/src/cloudflare/website.ts +40 -16
  36. package/src/cloudflare/worker-metadata.ts +101 -19
  37. package/src/cloudflare/worker.ts +104 -148
  38. package/src/stripe/price.ts +47 -1
@@ -25,7 +25,6 @@ import {
25
25
  type Bindings,
26
26
  Json,
27
27
  type WorkerBindingService,
28
- type WorkerBindingSpec,
29
28
  } from "./bindings.ts";
30
29
  import type { Bound } from "./bound.ts";
31
30
  import { isBucket } from "./bucket.ts";
@@ -56,11 +55,10 @@ import { Route } from "./route.ts";
56
55
  import { isVectorizeIndex } from "./vectorize-index.ts";
57
56
  import { type AssetUploadResult, uploadAssets } from "./worker-assets.ts";
58
57
  import {
59
- type WorkerMetadata,
60
58
  type WorkerScriptMetadata,
59
+ bumpMigrationTagVersion,
61
60
  prepareWorkerMetadata,
62
61
  } from "./worker-metadata.ts";
63
- import type { SingleStepMigration } from "./worker-migration.ts";
64
62
  import { WorkerStub, isWorkerStub } from "./worker-stub.ts";
65
63
  import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.ts";
66
64
 
@@ -98,11 +96,12 @@ export interface AssetsConfig {
98
96
 
99
97
  /**
100
98
  * When true, requests will always invoke the Worker script.
99
+ * If an array is passed, the worker will be invoked for matching requests.
101
100
  * Otherwise, attempt to serve an asset matching the request, falling back to the Worker script.
102
101
  *
103
102
  * @default false
104
103
  */
105
- run_worker_first?: boolean;
104
+ run_worker_first?: boolean | string[];
106
105
  }
107
106
 
108
107
  export interface BaseWorkerProps<
@@ -172,11 +171,6 @@ export interface BaseWorkerProps<
172
171
  enabled?: boolean;
173
172
  };
174
173
 
175
- /**
176
- * Migrations to apply to the worker
177
- */
178
- migrations?: SingleStepMigration;
179
-
180
174
  /**
181
175
  * Whether to adopt the Worker if it already exists when creating
182
176
  */
@@ -894,12 +888,6 @@ export const _Worker = Resource(
894
888
  const compatibilityFlags = props.compatibilityFlags ?? [];
895
889
 
896
890
  const uploadWorkerScript = async (props: WorkerProps<B>) => {
897
- const [oldBindings, oldMetadata] = await Promise.all([
898
- getWorkerBindings(api, workerName),
899
- getWorkerScriptMetadata(api, workerName),
900
- ]);
901
- const oldTags = oldMetadata?.default_environment?.script?.tags;
902
-
903
891
  // Get the script content - either from props.script, or by bundling
904
892
  const scriptBundle =
905
893
  props.script ??
@@ -949,34 +937,17 @@ export const _Worker = Resource(
949
937
  });
950
938
  }
951
939
 
952
- // Prepare metadata with bindings
953
- const scriptMetadata = await prepareWorkerMetadata(
954
- this,
955
- oldBindings,
956
- oldTags,
957
- {
958
- ...props,
959
- compatibilityDate,
960
- compatibilityFlags,
961
- workerName,
962
- },
963
- assetUploadResult,
964
- );
965
-
966
940
  // Deploy worker (either as version or live worker)
967
- const versionResult = await putWorker(
968
- api,
941
+ const versionResult = await putWorker(api, {
942
+ ...props,
969
943
  workerName,
970
944
  scriptBundle,
971
- scriptMetadata,
972
945
  dispatchNamespace,
973
- props.version
974
- ? {
975
- versionLabel: props.version,
976
- message: `Version ${props.version}`,
977
- }
978
- : undefined,
979
- );
946
+ version: props.version,
947
+ compatibilityDate,
948
+ compatibilityFlags,
949
+ assetUploadResult,
950
+ });
980
951
 
981
952
  for (const workflow of workflowsBindings) {
982
953
  if (
@@ -1050,7 +1021,7 @@ export const _Worker = Resource(
1050
1021
  }
1051
1022
  }
1052
1023
 
1053
- return { scriptBundle, scriptMetadata, workerUrl, now, versionResult };
1024
+ return { scriptBundle, workerUrl, now, versionResult };
1054
1025
  };
1055
1026
 
1056
1027
  if (this.phase === "delete") {
@@ -1059,9 +1030,13 @@ export const _Worker = Resource(
1059
1030
 
1060
1031
  await withExponentialBackoff(
1061
1032
  () =>
1062
- deleteWorker(this, api, {
1063
- ...props,
1033
+ deleteWorker(api, {
1064
1034
  workerName,
1035
+ namespace:
1036
+ typeof props.namespace === "string"
1037
+ ? props.namespace
1038
+ : props.namespace?.namespaceId,
1039
+ url: this.output.url,
1065
1040
  }),
1066
1041
  (err) =>
1067
1042
  (err.status === 400 &&
@@ -1076,10 +1051,6 @@ export const _Worker = Resource(
1076
1051
 
1077
1052
  return this.destroy();
1078
1053
  }
1079
- // Validate input - we need either script, entryPoint, or bundle
1080
- if (!props.script && !props.entrypoint) {
1081
- throw new Error("One of script or entrypoint must be provided");
1082
- }
1083
1054
 
1084
1055
  if (this.phase === "create") {
1085
1056
  if (props.version) {
@@ -1096,7 +1067,7 @@ export const _Worker = Resource(
1096
1067
  }
1097
1068
  }
1098
1069
 
1099
- const { scriptMetadata, workerUrl, now } = await uploadWorkerScript(props);
1070
+ const { workerUrl, now } = await uploadWorkerScript(props);
1100
1071
 
1101
1072
  // Create routes if provided and capture their outputs
1102
1073
  let createdRoutes: Route[] = [];
@@ -1166,7 +1137,7 @@ export const _Worker = Resource(
1166
1137
  format: props.format || "esm", // Include format in the output
1167
1138
  bindings: exportBindings(),
1168
1139
  env: props.env,
1169
- observability: scriptMetadata.observability,
1140
+ observability: props.observability,
1170
1141
  createdAt: now,
1171
1142
  updatedAt: now,
1172
1143
  eventSources: props.eventSources,
@@ -1187,10 +1158,13 @@ export const _Worker = Resource(
1187
1158
  },
1188
1159
  );
1189
1160
 
1190
- export async function deleteWorker<B extends Bindings>(
1191
- ctx: Context<Worker<B>>,
1161
+ export async function deleteWorker(
1192
1162
  api: CloudflareApi,
1193
- props: WorkerProps<B> & { workerName: string },
1163
+ props: {
1164
+ workerName: string;
1165
+ namespace?: string | DispatchNamespaceResource;
1166
+ url?: string;
1167
+ },
1194
1168
  ) {
1195
1169
  const workerName = props.workerName;
1196
1170
 
@@ -1207,7 +1181,7 @@ export async function deleteWorker<B extends Bindings>(
1207
1181
  }
1208
1182
 
1209
1183
  // Disable the URL if it was enabled
1210
- if (ctx.output?.url) {
1184
+ if (props.url) {
1211
1185
  try {
1212
1186
  await api.post(
1213
1187
  `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
@@ -1225,22 +1199,33 @@ export async function deleteWorker<B extends Bindings>(
1225
1199
  return;
1226
1200
  }
1227
1201
 
1228
- interface PutWorkerOptions {
1229
- versionLabel?: string;
1230
- message?: string;
1202
+ type PutWorkerOptions = WorkerProps & {
1231
1203
  dispatchNamespace?: string;
1232
- }
1204
+ migrationTag?: string;
1205
+ workerName: string;
1206
+ scriptBundle: string | NoBundleResult;
1207
+ version: string | undefined;
1208
+ compatibilityDate: string;
1209
+ compatibilityFlags: string[];
1210
+ assetUploadResult: AssetUploadResult | undefined;
1211
+ };
1233
1212
 
1234
- async function putWorkerInternal(
1213
+ export async function putWorker(
1235
1214
  api: CloudflareApi,
1236
- workerName: string,
1237
- scriptBundle: string | NoBundleResult,
1238
- scriptMetadata: WorkerMetadata,
1239
- options: PutWorkerOptions = {},
1215
+ props: PutWorkerOptions,
1240
1216
  ): Promise<{ versionId?: string; previewUrl?: string }> {
1217
+ const {
1218
+ //
1219
+ dispatchNamespace,
1220
+ migrationTag,
1221
+ workerName,
1222
+ scriptBundle,
1223
+ version,
1224
+ } = props;
1225
+ const scriptMetadata = await prepareWorkerMetadata(api, props);
1226
+
1241
1227
  return withExponentialBackoff(
1242
1228
  async () => {
1243
- const { versionLabel, message, dispatchNamespace } = options;
1244
1229
  const scriptName =
1245
1230
  scriptMetadata.main_module ?? scriptMetadata.body_part!;
1246
1231
 
@@ -1271,17 +1256,30 @@ async function putWorkerInternal(
1271
1256
  }
1272
1257
 
1273
1258
  // Prepare metadata - add version annotations if this is a version
1274
- const finalMetadata = versionLabel
1259
+ const finalMetadata = version
1275
1260
  ? {
1276
1261
  ...scriptMetadata,
1277
1262
  // Exclude migrations for worker versions - they're not allowed
1278
1263
  migrations: undefined,
1279
1264
  annotations: {
1280
- "workers/tag": versionLabel,
1281
- ...(message && { "workers/message": message.substring(0, 100) }),
1265
+ "workers/tag": version,
1266
+ "workers/message": `Version ${version}`,
1282
1267
  },
1283
1268
  }
1284
- : scriptMetadata;
1269
+ : {
1270
+ ...scriptMetadata,
1271
+ migrations: scriptMetadata.migrations
1272
+ ? {
1273
+ ...scriptMetadata.migrations,
1274
+ old_tag: migrationTag,
1275
+ new_tag: bumpMigrationTagVersion(migrationTag),
1276
+ }
1277
+ : undefined,
1278
+ };
1279
+
1280
+ if (process.env.DEBUG) {
1281
+ console.log(`metadata(${scriptName}):`, finalMetadata);
1282
+ }
1285
1283
 
1286
1284
  // Add metadata as JSON
1287
1285
  formData.append(
@@ -1294,8 +1292,12 @@ async function putWorkerInternal(
1294
1292
  // Determine endpoint and HTTP method
1295
1293
  let endpoint: string;
1296
1294
  let method: "PUT" | "POST";
1297
-
1298
- if (versionLabel) {
1295
+ if (version) {
1296
+ if (dispatchNamespace) {
1297
+ throw new Error(
1298
+ "Worker Preview Versions are not supported in Workers for Platforms",
1299
+ );
1300
+ }
1299
1301
  // Upload worker version using the versions API
1300
1302
  endpoint = `/accounts/${api.accountId}/workers/scripts/${workerName}/versions`;
1301
1303
  method = "POST";
@@ -1322,16 +1324,43 @@ async function putWorkerInternal(
1322
1324
 
1323
1325
  // Check if the upload was successful
1324
1326
  if (!uploadResponse.ok) {
1325
- await handleApiError(
1326
- uploadResponse,
1327
- versionLabel ? "uploading worker version" : "uploading worker script",
1328
- "worker",
1329
- workerName,
1330
- );
1327
+ try {
1328
+ return await handleApiError(
1329
+ uploadResponse,
1330
+ version ? "uploading worker version" : "uploading worker script",
1331
+ "worker",
1332
+ workerName,
1333
+ );
1334
+ } catch (error) {
1335
+ if (error instanceof CloudflareApiError && error.status === 412) {
1336
+ // this happens when adopting a Worker managed with Wrangler
1337
+ // because wrangler includes a migration tag and we do not
1338
+ // currently, the only way to discover the old_tag is through the error message
1339
+ // Get Worker Script Settings is meant to return it (according to the docs)
1340
+ // but it doesn't work at runtime
1341
+ //
1342
+ // so, we catch the error and parse out the tag and then retry
1343
+ if (error.message.includes("when expected tag is")) {
1344
+ const newTag = error.message.match(
1345
+ /when expected tag is ['"]?(v\d+)['"]?/,
1346
+ )?.[1];
1347
+ if (newTag) {
1348
+ return await putWorker(api, {
1349
+ ...props,
1350
+ migrationTag: newTag,
1351
+ });
1352
+ }
1353
+ } else {
1354
+ throw error;
1355
+ }
1356
+ } else {
1357
+ throw error;
1358
+ }
1359
+ }
1331
1360
  }
1332
1361
 
1333
1362
  // Handle version response
1334
- if (versionLabel) {
1363
+ if (props.version) {
1335
1364
  const responseData = (await uploadResponse.json()) as {
1336
1365
  result: {
1337
1366
  id: string;
@@ -1390,27 +1419,6 @@ async function putWorkerInternal(
1390
1419
  );
1391
1420
  }
1392
1421
 
1393
- export async function putWorker(
1394
- api: CloudflareApi,
1395
- workerName: string,
1396
- scriptBundle: string | NoBundleResult,
1397
- scriptMetadata: WorkerMetadata,
1398
- dispatchNamespace?: string,
1399
- version?: { versionLabel: string; message?: string },
1400
- ): Promise<{ versionId?: string; previewUrl?: string }> {
1401
- return await putWorkerInternal(
1402
- api,
1403
- workerName,
1404
- scriptBundle,
1405
- scriptMetadata,
1406
- {
1407
- dispatchNamespace,
1408
- versionLabel: version?.versionLabel,
1409
- message: version?.message,
1410
- },
1411
- );
1412
- }
1413
-
1414
1422
  export async function checkWorkerExists(
1415
1423
  api: CloudflareApi,
1416
1424
  workerName: string,
@@ -1494,13 +1502,6 @@ export async function configureURL<B extends Bindings>(
1494
1502
 
1495
1503
  if (subdomain) {
1496
1504
  workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
1497
-
1498
- // Add a delay when the subdomain is first created.
1499
- // This is to prevent an issue where a negative cache-hit
1500
- // causes the subdomain to be unavailable for 30 seconds.
1501
- if (ctx.phase === "create" || !ctx.output?.url) {
1502
- await new Promise((resolve) => setTimeout(resolve, 3000));
1503
- }
1504
1505
  }
1505
1506
  } else if (url === false && ctx.output?.url) {
1506
1507
  // Explicitly disable URL if it was previously enabled
@@ -1538,51 +1539,6 @@ export async function getWorkerScriptMetadata(
1538
1539
  return ((await response.json()) as any).result as WorkerScriptMetadata;
1539
1540
  }
1540
1541
 
1541
- async function getWorkerBindings(api: CloudflareApi, workerName: string) {
1542
- // Fetch the bindings for a worker by calling the Cloudflare API endpoint:
1543
- // GET /accounts/:account_id/workers/scripts/:script_name/bindings
1544
- // See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
1545
- const response = await api.get(
1546
- `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
1547
- );
1548
- if (response.status === 404) {
1549
- return undefined;
1550
- }
1551
- if (!response.ok) {
1552
- throw new Error(
1553
- `Error getting worker bindings: ${response.status} ${response.statusText}`,
1554
- );
1555
- }
1556
- // The result is an object with a "result" property containing the bindings array
1557
- const { result, success, errors } = (await response.json()) as {
1558
- result: {
1559
- bindings: WorkerBindingSpec[];
1560
- compatibility_date: string;
1561
- compatibility_flags: string[];
1562
- [key: string]: any;
1563
- };
1564
- success: boolean;
1565
- errors: Array<{
1566
- code: number;
1567
- message: string;
1568
- documentation_url: string;
1569
- [key: string]: any;
1570
- }>;
1571
- messages: Array<{
1572
- code: number;
1573
- message: string;
1574
- documentation_url: string;
1575
- [key: string]: any;
1576
- }>;
1577
- };
1578
- if (!success) {
1579
- throw new Error(
1580
- `Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
1581
- );
1582
- }
1583
- return result.bindings;
1584
- }
1585
-
1586
1542
  /**
1587
1543
  * Lists and deletes all queue consumers for a specific worker
1588
1544
  * @param ctx Worker context containing eventSources
@@ -32,6 +32,12 @@ export interface PriceRecurring {
32
32
  * `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.
33
33
  */
34
34
  aggregateUsage?: Stripe.PriceCreateParams.Recurring.AggregateUsage;
35
+
36
+ /**
37
+ * The ID of the billing meter this price is associated with.
38
+ * Only applicable when usageType = 'metered'.
39
+ */
40
+ meter?: string;
35
41
  }
36
42
 
37
43
  type TaxBehavior = Stripe.PriceCreateParams.TaxBehavior;
@@ -320,6 +326,25 @@ export interface Price extends Resource<"stripe::Price">, PriceProps {
320
326
  * }
321
327
  * ]
322
328
  * });
329
+ *
330
+ * @example
331
+ * // Create a metered price with billing meter for API usage tracking
332
+ * const meteredPrice = await Price("api-usage-price", {
333
+ * product: "prod_xyz",
334
+ * currency: "usd",
335
+ * billingScheme: "tiered",
336
+ * tiersMode: "graduated",
337
+ * recurring: {
338
+ * interval: "month",
339
+ * usageType: "metered",
340
+ * meter: "meter_123abc" // Associate with billing meter
341
+ * },
342
+ * tiers: [
343
+ * { upTo: 10000, unitAmountDecimal: "0" },
344
+ * { upTo: 25000, unitAmountDecimal: "0.002" },
345
+ * { upTo: "inf", flatAmountDecimal: "3000" }
346
+ * ]
347
+ * });
323
348
  */
324
349
  export const Price = Resource(
325
350
  "stripe::Price",
@@ -370,6 +395,9 @@ export const Price = Resource(
370
395
  expand: ["tiers"],
371
396
  };
372
397
 
398
+ // Note: Stripe doesn't allow updating recurring fields (including meter) after price creation
399
+ // If meter needs to be changed, a new price must be created
400
+
373
401
  price = await stripe.prices.update(this.output.id, updateParams);
374
402
  } else {
375
403
  // Create new price
@@ -401,6 +429,21 @@ export const Price = Resource(
401
429
  usage_type: props.recurring.usageType,
402
430
  aggregate_usage: props.recurring.aggregateUsage,
403
431
  };
432
+
433
+ // Add meter to recurring if present (only for metered usage type)
434
+ if (props.recurring.meter) {
435
+ if (props.recurring.usageType !== "metered") {
436
+ throw new Error(
437
+ "Meter can only be set for prices with recurring.usageType = 'metered'",
438
+ );
439
+ }
440
+ // Extend the recurring params with meter
441
+ (
442
+ createParams.recurring as Stripe.PriceCreateParams.Recurring & {
443
+ meter: string;
444
+ }
445
+ ).meter = props.recurring.meter;
446
+ }
404
447
  }
405
448
 
406
449
  // Add tier configuration if present
@@ -452,7 +495,7 @@ export const Price = Resource(
452
495
  // Need to retrieve with expanded tiers
453
496
  price = await stripe.prices.retrieve(existingPrices.data[0].id, {
454
497
  expand: ["tiers"],
455
- } as any);
498
+ });
456
499
  }
457
500
  } else {
458
501
  price = await stripe.prices.create(createParams);
@@ -472,6 +515,9 @@ export const Price = Resource(
472
515
  .usage_type as Stripe.PriceCreateParams.Recurring.UsageType,
473
516
  aggregateUsage: price.recurring
474
517
  .aggregate_usage as Stripe.PriceCreateParams.Recurring.AggregateUsage,
518
+ meter: (
519
+ price.recurring as Stripe.Price.Recurring & { meter?: string }
520
+ ).meter,
475
521
  }
476
522
  : undefined;
477
523