alchemy 0.24.0 → 0.25.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.
Files changed (184) hide show
  1. package/lib/aws/control/client.js +1 -1
  2. package/lib/aws/control/client.js.map +1 -1
  3. package/lib/aws/control/resource.d.ts.map +1 -1
  4. package/lib/aws/control/resource.js +2 -1
  5. package/lib/aws/control/resource.js.map +1 -1
  6. package/lib/aws/function.d.ts.map +1 -1
  7. package/lib/aws/function.js +1 -4
  8. package/lib/aws/function.js.map +1 -1
  9. package/lib/cloudflare/ai.d.ts +21 -0
  10. package/lib/cloudflare/ai.d.ts.map +1 -1
  11. package/lib/cloudflare/ai.js +21 -0
  12. package/lib/cloudflare/ai.js.map +1 -1
  13. package/lib/cloudflare/bindings.d.ts +3 -3
  14. package/lib/cloudflare/bindings.d.ts.map +1 -1
  15. package/lib/cloudflare/bindings.js.map +1 -1
  16. package/lib/cloudflare/bound.d.ts +3 -3
  17. package/lib/cloudflare/bound.d.ts.map +1 -1
  18. package/lib/cloudflare/do-state-store/index.d.ts +2 -0
  19. package/lib/cloudflare/do-state-store/index.d.ts.map +1 -0
  20. package/lib/cloudflare/do-state-store/index.js +2 -0
  21. package/lib/cloudflare/do-state-store/index.js.map +1 -0
  22. package/lib/cloudflare/do-state-store/internal.d.ts +19 -0
  23. package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -0
  24. package/lib/cloudflare/do-state-store/internal.js +163 -0
  25. package/lib/cloudflare/do-state-store/internal.js.map +1 -0
  26. package/lib/cloudflare/do-state-store/store.d.ts +59 -0
  27. package/lib/cloudflare/do-state-store/store.d.ts.map +1 -0
  28. package/lib/cloudflare/do-state-store/store.js +120 -0
  29. package/lib/cloudflare/do-state-store/store.js.map +1 -0
  30. package/lib/cloudflare/do-state-store/types.d.ts +82 -0
  31. package/lib/cloudflare/do-state-store/types.d.ts.map +1 -0
  32. package/lib/cloudflare/do-state-store/types.js +1 -0
  33. package/lib/cloudflare/do-state-store/types.js.map +1 -0
  34. package/lib/cloudflare/do-state-store/worker.d.ts +27 -0
  35. package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -0
  36. package/lib/cloudflare/do-state-store/worker.js +170 -0
  37. package/lib/cloudflare/do-state-store/worker.js.map +1 -0
  38. package/lib/cloudflare/index.d.ts +1 -0
  39. package/lib/cloudflare/index.d.ts.map +1 -1
  40. package/lib/cloudflare/index.js +1 -0
  41. package/lib/cloudflare/index.js.map +1 -1
  42. package/lib/cloudflare/pipeline.js +1 -1
  43. package/lib/cloudflare/pipeline.js.map +1 -1
  44. package/lib/cloudflare/queue-consumer.d.ts +14 -1
  45. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  46. package/lib/cloudflare/queue-consumer.js +17 -0
  47. package/lib/cloudflare/queue-consumer.js.map +1 -1
  48. package/lib/cloudflare/website.d.ts +30 -1
  49. package/lib/cloudflare/website.d.ts.map +1 -1
  50. package/lib/cloudflare/website.js +8 -5
  51. package/lib/cloudflare/website.js.map +1 -1
  52. package/lib/cloudflare/worker-assets.d.ts.map +1 -1
  53. package/lib/cloudflare/worker-assets.js +13 -3
  54. package/lib/cloudflare/worker-assets.js.map +1 -1
  55. package/lib/cloudflare/worker-metadata.js +1 -1
  56. package/lib/cloudflare/worker-metadata.js.map +1 -1
  57. package/lib/cloudflare/worker.d.ts +40 -1
  58. package/lib/cloudflare/worker.d.ts.map +1 -1
  59. package/lib/cloudflare/worker.js +40 -38
  60. package/lib/cloudflare/worker.js.map +1 -1
  61. package/lib/cloudflare/wrangler.json.js +1 -1
  62. package/lib/cloudflare/wrangler.json.js.map +1 -1
  63. package/lib/cloudflare/zone.d.ts +30 -2
  64. package/lib/cloudflare/zone.d.ts.map +1 -1
  65. package/lib/cloudflare/zone.js +53 -0
  66. package/lib/cloudflare/zone.js.map +1 -1
  67. package/lib/fs/file-system-state-store.d.ts +3 -1
  68. package/lib/fs/file-system-state-store.d.ts.map +1 -1
  69. package/lib/fs/file-system-state-store.js +2 -2
  70. package/lib/fs/file-system-state-store.js.map +1 -1
  71. package/lib/stripe/card.d.ts +171 -0
  72. package/lib/stripe/card.d.ts.map +1 -0
  73. package/lib/stripe/card.js +148 -0
  74. package/lib/stripe/card.js.map +1 -0
  75. package/lib/stripe/client.d.ts +9 -0
  76. package/lib/stripe/client.d.ts.map +1 -0
  77. package/lib/stripe/client.js +94 -0
  78. package/lib/stripe/client.js.map +1 -0
  79. package/lib/stripe/coupon.d.ts +135 -0
  80. package/lib/stripe/coupon.d.ts.map +1 -0
  81. package/lib/stripe/coupon.js +161 -0
  82. package/lib/stripe/coupon.js.map +1 -0
  83. package/lib/stripe/customer.d.ts +269 -0
  84. package/lib/stripe/customer.d.ts.map +1 -0
  85. package/lib/stripe/customer.js +289 -0
  86. package/lib/stripe/customer.js.map +1 -0
  87. package/lib/stripe/entitlements-feature.d.ts +84 -0
  88. package/lib/stripe/entitlements-feature.d.ts.map +1 -0
  89. package/lib/stripe/entitlements-feature.js +102 -0
  90. package/lib/stripe/entitlements-feature.js.map +1 -0
  91. package/lib/stripe/file.d.ts +143 -0
  92. package/lib/stripe/file.d.ts.map +1 -0
  93. package/lib/stripe/file.js +97 -0
  94. package/lib/stripe/file.js.map +1 -0
  95. package/lib/stripe/index.d.ts +11 -0
  96. package/lib/stripe/index.d.ts.map +1 -1
  97. package/lib/stripe/index.js +11 -0
  98. package/lib/stripe/index.js.map +1 -1
  99. package/lib/stripe/meter.d.ts +10 -1
  100. package/lib/stripe/meter.d.ts.map +1 -1
  101. package/lib/stripe/meter.js +13 -7
  102. package/lib/stripe/meter.js.map +1 -1
  103. package/lib/stripe/portal-configuration.d.ts +286 -0
  104. package/lib/stripe/portal-configuration.d.ts.map +1 -0
  105. package/lib/stripe/portal-configuration.js +320 -0
  106. package/lib/stripe/portal-configuration.js.map +1 -0
  107. package/lib/stripe/price.d.ts +10 -1
  108. package/lib/stripe/price.d.ts.map +1 -1
  109. package/lib/stripe/price.js +34 -13
  110. package/lib/stripe/price.js.map +1 -1
  111. package/lib/stripe/product-feature.d.ts +76 -0
  112. package/lib/stripe/product-feature.d.ts.map +1 -0
  113. package/lib/stripe/product-feature.js +74 -0
  114. package/lib/stripe/product-feature.js.map +1 -0
  115. package/lib/stripe/product.d.ts +10 -1
  116. package/lib/stripe/product.d.ts.map +1 -1
  117. package/lib/stripe/product.js +33 -14
  118. package/lib/stripe/product.js.map +1 -1
  119. package/lib/stripe/promotion-code.d.ts +138 -0
  120. package/lib/stripe/promotion-code.d.ts.map +1 -0
  121. package/lib/stripe/promotion-code.js +150 -0
  122. package/lib/stripe/promotion-code.js.map +1 -0
  123. package/lib/stripe/shipping-rate.d.ts +188 -0
  124. package/lib/stripe/shipping-rate.d.ts.map +1 -0
  125. package/lib/stripe/shipping-rate.js +198 -0
  126. package/lib/stripe/shipping-rate.js.map +1 -0
  127. package/lib/stripe/tax-rate.d.ts +130 -0
  128. package/lib/stripe/tax-rate.d.ts.map +1 -0
  129. package/lib/stripe/tax-rate.js +126 -0
  130. package/lib/stripe/tax-rate.js.map +1 -0
  131. package/lib/stripe/webhook.d.ts +10 -1
  132. package/lib/stripe/webhook.d.ts.map +1 -1
  133. package/lib/stripe/webhook.js +59 -14
  134. package/lib/stripe/webhook.js.map +1 -1
  135. package/lib/test/bun.d.ts.map +1 -1
  136. package/lib/test/bun.js +2 -6
  137. package/lib/test/bun.js.map +1 -1
  138. package/lib/test/vitest.d.ts.map +1 -1
  139. package/lib/test/vitest.js +3 -7
  140. package/lib/test/vitest.js.map +1 -1
  141. package/lib/util/retry.d.ts +1 -1
  142. package/lib/util/retry.d.ts.map +1 -1
  143. package/lib/util/retry.js.map +1 -1
  144. package/package.json +3 -2
  145. package/src/aws/control/client.ts +1 -1
  146. package/src/aws/control/resource.ts +2 -1
  147. package/src/aws/function.ts +3 -6
  148. package/src/cloudflare/ai.ts +21 -0
  149. package/src/cloudflare/bindings.ts +3 -2
  150. package/src/cloudflare/bound.ts +3 -3
  151. package/src/cloudflare/do-state-store/index.ts +1 -0
  152. package/src/cloudflare/do-state-store/internal.ts +215 -0
  153. package/src/cloudflare/do-state-store/store.ts +188 -0
  154. package/src/cloudflare/do-state-store/types.ts +74 -0
  155. package/src/cloudflare/do-state-store/worker.ts +199 -0
  156. package/src/cloudflare/index.ts +1 -0
  157. package/src/cloudflare/pipeline.ts +1 -1
  158. package/src/cloudflare/queue-consumer.ts +59 -1
  159. package/src/cloudflare/website.ts +38 -6
  160. package/src/cloudflare/worker-assets.ts +15 -3
  161. package/src/cloudflare/worker-metadata.ts +1 -1
  162. package/src/cloudflare/worker.ts +66 -46
  163. package/src/cloudflare/wrangler.json.ts +1 -1
  164. package/src/cloudflare/zone.ts +75 -2
  165. package/src/fs/file-system-state-store.ts +7 -2
  166. package/src/stripe/card.ts +320 -0
  167. package/src/stripe/client.ts +145 -0
  168. package/src/stripe/coupon.ts +280 -0
  169. package/src/stripe/customer.ts +528 -0
  170. package/src/stripe/entitlements-feature.ts +169 -0
  171. package/src/stripe/file.ts +219 -0
  172. package/src/stripe/index.ts +11 -0
  173. package/src/stripe/meter.ts +23 -7
  174. package/src/stripe/portal-configuration.ts +609 -0
  175. package/src/stripe/price.ts +47 -14
  176. package/src/stripe/product-feature.ts +150 -0
  177. package/src/stripe/product.ts +48 -15
  178. package/src/stripe/promotion-code.ts +268 -0
  179. package/src/stripe/shipping-rate.ts +362 -0
  180. package/src/stripe/tax-rate.ts +218 -0
  181. package/src/stripe/webhook.ts +82 -15
  182. package/src/test/bun.ts +2 -7
  183. package/src/test/vitest.ts +3 -8
  184. package/src/util/retry.ts +1 -1
@@ -0,0 +1,74 @@
1
+ import type { State } from "../../state.ts";
2
+
3
+ export namespace DOStateStoreAPI {
4
+ export type API = {
5
+ validate: {
6
+ method: "validate";
7
+ params: null;
8
+ result: null;
9
+ };
10
+ get: {
11
+ method: "get";
12
+ params: { key: string };
13
+ result: string | undefined;
14
+ };
15
+ getBatch: {
16
+ method: "getBatch";
17
+ params: { keys: string[] };
18
+ result: Record<string, string>;
19
+ };
20
+ all: {
21
+ method: "all";
22
+ params: { prefix: string };
23
+ result: Record<string, string>;
24
+ };
25
+ set: {
26
+ method: "set";
27
+ params: { key: string; value: State };
28
+ result: null;
29
+ };
30
+ delete: {
31
+ method: "delete";
32
+ params: { key: string };
33
+ result: null;
34
+ };
35
+ list: {
36
+ method: "list";
37
+ params: { prefix: string };
38
+ result: string[];
39
+ };
40
+ count: {
41
+ method: "count";
42
+ params: { prefix: string };
43
+ result: number;
44
+ };
45
+ };
46
+
47
+ export interface Context {
48
+ app: string;
49
+ stage: string;
50
+ }
51
+
52
+ export type Request = {
53
+ [K in keyof API]: {
54
+ method: K;
55
+ params: API[K]["params"];
56
+ };
57
+ }[keyof API];
58
+
59
+ export interface SuccessResponse<T extends keyof API = keyof API> {
60
+ success: true;
61
+ status: number;
62
+ result: API[T]["result"];
63
+ }
64
+
65
+ export interface ErrorResponse {
66
+ success: false;
67
+ status: number;
68
+ error: string;
69
+ }
70
+
71
+ export type Response<T extends keyof API = keyof API> =
72
+ | SuccessResponse<T>
73
+ | ErrorResponse;
74
+ }
@@ -0,0 +1,199 @@
1
+ import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
2
+ import { Fs } from "dofs";
3
+ import { timingSafeEqual } from "node:crypto";
4
+ import type { State } from "../../state.ts";
5
+ import type { DOStateStoreAPI } from "./types.ts";
6
+
7
+ interface Env {
8
+ DOFS_STATE_STORE: DurableObjectNamespace<DOFSStateStore>;
9
+ DOFS_TOKEN: string;
10
+ }
11
+
12
+ export default class extends WorkerEntrypoint<Env> {
13
+ override async fetch(request: Request) {
14
+ try {
15
+ const result = await this.handle(request);
16
+ const body: DOStateStoreAPI.Response = {
17
+ success: true,
18
+ status: 200,
19
+ result: result ?? undefined,
20
+ };
21
+ return Response.json(body, { status: body.status });
22
+ } catch (error) {
23
+ return APIError.fromUnknown(error).toResponse();
24
+ }
25
+ }
26
+
27
+ private async handle(request: Request) {
28
+ if (!this.authorize(request)) {
29
+ throw new APIError("Unauthorized", 401);
30
+ }
31
+ const stub = this.getStub(request);
32
+ const body = await this.parseBody(request);
33
+ switch (body.method) {
34
+ case "validate":
35
+ return null;
36
+ case "all":
37
+ return await stub.all(body.params.prefix);
38
+ case "count":
39
+ return await stub.count(body.params.prefix);
40
+ case "delete":
41
+ return await stub.delete(body.params.key);
42
+ case "get":
43
+ return await stub.get(body.params.key);
44
+ case "getBatch":
45
+ return await stub.getBatch(body.params.keys);
46
+ case "list":
47
+ return await stub.list(body.params.prefix);
48
+ case "set":
49
+ return await stub.set(body.params.key, body.params.value);
50
+ default: {
51
+ const _: never = body;
52
+ throw new APIError("Method not found", 404);
53
+ }
54
+ }
55
+ }
56
+
57
+ private authorize(request: Request): boolean {
58
+ const token = request.headers.get("Authorization")?.split(" ")[1];
59
+ if (!token) {
60
+ return false;
61
+ }
62
+ return timingSafeEqual(
63
+ new TextEncoder().encode(token),
64
+ new TextEncoder().encode(this.env.DOFS_TOKEN),
65
+ );
66
+ }
67
+
68
+ private getStub(request: Request): DurableObjectStub<DOFSStateStore> {
69
+ const url = new URL(request.url);
70
+ const app = url.searchParams.get("app");
71
+ const stage = url.searchParams.get("stage");
72
+ if (!app || !stage) {
73
+ throw new APIError("Missing app or stage", 400);
74
+ }
75
+ return this.env.DOFS_STATE_STORE.get(
76
+ this.env.DOFS_STATE_STORE.idFromName(`${app}/${stage}`),
77
+ );
78
+ }
79
+
80
+ private async parseBody(request: Request): Promise<DOStateStoreAPI.Request> {
81
+ try {
82
+ return await request.json<DOStateStoreAPI.Request>();
83
+ } catch {
84
+ throw new APIError("Invalid JSON", 400);
85
+ }
86
+ }
87
+ }
88
+
89
+ class APIError extends Error {
90
+ constructor(
91
+ message: string,
92
+ public readonly status: number,
93
+ ) {
94
+ super(message);
95
+ }
96
+
97
+ toResponse(): Response {
98
+ return Response.json(
99
+ {
100
+ success: false,
101
+ status: this.status,
102
+ error: this.message,
103
+ },
104
+ { status: this.status },
105
+ );
106
+ }
107
+
108
+ static fromUnknown(error: unknown): APIError {
109
+ if (error instanceof APIError) {
110
+ return error;
111
+ }
112
+ if (error instanceof Error) {
113
+ return new APIError(error.message, 500);
114
+ }
115
+ return new APIError("An unknown error occurred.", 500);
116
+ }
117
+ }
118
+
119
+ export class DOFSStateStore extends DurableObject<Env> {
120
+ fs = new Fs(this.ctx, this.env, {
121
+ chunkSize: 256 * 1024,
122
+ });
123
+
124
+ async all(prefix: string): Promise<Record<string, string>> {
125
+ const keys = this.list(prefix);
126
+ return await this.getBatch(keys);
127
+ }
128
+
129
+ count(prefix: string): number {
130
+ return this.list(prefix).length;
131
+ }
132
+
133
+ delete(key: string): void {
134
+ try {
135
+ this.fs.unlink(key);
136
+ } catch (error) {
137
+ if (isErrorCode(error, "ENOENT")) {
138
+ return;
139
+ }
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ async get(key: string): Promise<string | undefined> {
145
+ try {
146
+ const file = this.fs.readFile(key);
147
+ return new Response(file).text();
148
+ } catch (error) {
149
+ if (isErrorCode(error, "ENOENT")) {
150
+ return undefined;
151
+ }
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ async getBatch(keys: string[]): Promise<Record<string, string>> {
157
+ const result: Record<string, string> = {};
158
+ await Promise.all(
159
+ keys.map(async (key) => {
160
+ const value = await this.get(key);
161
+ if (value) {
162
+ result[key] = value;
163
+ }
164
+ }),
165
+ );
166
+ return result;
167
+ }
168
+
169
+ list(prefix: string): string[] {
170
+ try {
171
+ return this.fs
172
+ .listDir(prefix, { recursive: true })
173
+ .filter((item) => item !== "." && item !== "..");
174
+ } catch (error) {
175
+ if (isErrorCode(error, "ENOENT")) {
176
+ return [];
177
+ }
178
+ throw error;
179
+ }
180
+ }
181
+
182
+ async set(key: string, value: State): Promise<void> {
183
+ this.ensureDir(key);
184
+ await this.fs.writeFile(key, JSON.stringify(value));
185
+ }
186
+
187
+ private ensureDir(path: string): void {
188
+ const dir = path.split("/").slice(0, -1).join("/");
189
+ try {
190
+ this.fs.mkdir(dir, { recursive: true });
191
+ } catch {
192
+ // directory already exists, ignore
193
+ }
194
+ }
195
+ }
196
+
197
+ const isErrorCode = (error: unknown, code: string) => {
198
+ return error instanceof Error && "message" in error && error.message === code;
199
+ };
@@ -18,6 +18,7 @@ export * from "./d1-database.ts";
18
18
  export * from "./d1-export.ts";
19
19
  export * from "./d1-import.ts";
20
20
  export * from "./dns-records.ts";
21
+ export * from "./do-state-store/index.ts";
21
22
  export * from "./durable-object-namespace.ts";
22
23
  export * from "./hyperdrive.ts";
23
24
  export * from "./kv-namespace.ts";
@@ -364,7 +364,7 @@ const PipelineResource = Resource("cloudflare::Pipeline", async function <
364
364
  // Update operation
365
365
  if (this.output?.id) {
366
366
  // Check if name is being changed, which is not allowed
367
- if (props.name !== this.output.name) {
367
+ if (pipelineName !== this.output.name) {
368
368
  throw new Error(
369
369
  "Cannot update Pipeline name after creation. Pipeline name is immutable.",
370
370
  );
@@ -6,7 +6,7 @@ import {
6
6
  type CloudflareApi,
7
7
  type CloudflareApiOptions,
8
8
  } from "./api.ts";
9
- import type { QueueResource, Queue } from "./queue.ts";
9
+ import type { Queue, QueueResource } from "./queue.ts";
10
10
 
11
11
  /**
12
12
  * Settings for configuring a Queue Consumer
@@ -450,3 +450,61 @@ export async function listQueueConsumers(
450
450
  : undefined,
451
451
  }));
452
452
  }
453
+
454
+ export async function listQueueConsumersForWorker(
455
+ api: CloudflareApi,
456
+ workerName: string,
457
+ ) {
458
+ const response = await api.get(
459
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/queue-consumers?perPage=100`,
460
+ );
461
+
462
+ if (response.status === 404) {
463
+ return [];
464
+ }
465
+
466
+ if (!response.ok) {
467
+ return await handleApiError(
468
+ response,
469
+ "list",
470
+ "QueueConsumer",
471
+ `for worker ${workerName}`,
472
+ );
473
+ }
474
+
475
+ const data = (await response.json()) as {
476
+ result: Array<{
477
+ script: string;
478
+ settings?: {
479
+ batch_size?: number;
480
+ max_retries?: number;
481
+ max_wait_time_ms?: number;
482
+ retry_delay?: number;
483
+ dead_letter_queue?: string;
484
+ };
485
+ type: string;
486
+ queue_name: string;
487
+ queue_id: string;
488
+ consumer_id: string;
489
+ created_on: string;
490
+ }>;
491
+ success: boolean;
492
+ errors: any[] | null;
493
+ messages: any[] | null;
494
+ result_info: {
495
+ page: number;
496
+ per_page: number;
497
+ count: number;
498
+ total_count: number;
499
+ total_pages: number;
500
+ };
501
+ };
502
+
503
+ return data.result.map((consumer) => ({
504
+ queueName: consumer.queue_name,
505
+ queueId: consumer.queue_id,
506
+ consumerId: consumer.consumer_id,
507
+ createdOn: consumer.created_on,
508
+ settings: consumer.settings,
509
+ }));
510
+ }
@@ -15,8 +15,37 @@ export interface WebsiteProps<B extends Bindings>
15
15
  extends Omit<WorkerProps<B>, "name" | "assets" | "entrypoint"> {
16
16
  /**
17
17
  * The command to run to build the site
18
+ *
19
+ * If one is not provided, the build is assumed to have already happened.
20
+ */
21
+ command?: string;
22
+
23
+ /**
24
+ * Whether to memoize the command (only re-run if the command changes)
25
+ *
26
+ * When set to `true`, the command will only be re-executed if the command string changes.
27
+ *
28
+ * When set to an object with `patterns`, the command will be re-executed if either:
29
+ * 1. The command string changes, or
30
+ * 2. The contents of any files matching the glob patterns change
31
+ *
32
+ * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
33
+ * will not be produced if the command is memoized. This is because the command is not
34
+ * actually executed when memoized. Consider disabling memoization in CI environments:
35
+ *
36
+ * @example
37
+ * // Disable memoization in CI to ensure build outputs are always produced
38
+ * await Website("my-website", {
39
+ * command: "vite build",
40
+ * memoize: process.env.CI ? false : {
41
+ * patterns: ["./src/**"]
42
+ * }
43
+ * });
44
+ *
45
+ * @default false
18
46
  */
19
- command: string;
47
+ memoize?: boolean | { patterns: string[] };
48
+
20
49
  /**
21
50
  * The name of the worker
22
51
  *
@@ -128,11 +157,14 @@ export default {
128
157
  });
129
158
  }
130
159
 
131
- await Exec("build", {
132
- cwd,
133
- command: props.command,
134
- env: props.env,
135
- });
160
+ if (props.command) {
161
+ await Exec("build", {
162
+ cwd,
163
+ command: props.command,
164
+ env: props.env,
165
+ memoize: props.memoize,
166
+ });
167
+ }
136
168
 
137
169
  return (await Worker("worker", {
138
170
  ...workerProps,
@@ -109,8 +109,8 @@ export async function uploadAssets(
109
109
  for (const bucket of buckets) {
110
110
  const formData = new FormData();
111
111
 
112
- // Add each file in the bucket to the form
113
- for (const fileHash of bucket) {
112
+ // Prepare file reading operations for parallel execution
113
+ const fileReadPromises = bucket.map(async (fileHash) => {
114
114
  // Find the file with this hash
115
115
  const file = assets.files.find((f) => {
116
116
  const filePath = f.path.startsWith("/") ? f.path : `/${f.path}`;
@@ -127,10 +127,22 @@ export async function uploadAssets(
127
127
  // Convert to base64 as required by the API when using base64=true
128
128
  const base64Content = fileContent.toString("base64");
129
129
 
130
+ return {
131
+ fileHash,
132
+ base64Content,
133
+ filePath: file.filePath,
134
+ };
135
+ });
136
+
137
+ // Read all files in parallel
138
+ const fileResults = await Promise.all(fileReadPromises);
139
+
140
+ // Add each file to the form
141
+ for (const { fileHash, base64Content, filePath } of fileResults) {
130
142
  // Add the file to the form with the hash as the key and set the correct content type
131
143
  const blob = new Blob([base64Content], {
132
144
  // if you set application/octet-stream, you get weird `-1` errors with a message to contact support
133
- type: getContentType(file.filePath) ?? "application/null",
145
+ type: getContentType(filePath) ?? "application/null",
134
146
  });
135
147
  formData.append(fileHash, blob, fileHash);
136
148
  }
@@ -361,7 +361,7 @@ export async function prepareWorkerMetadata<B extends Bindings>(
361
361
  meta.bindings.push({
362
362
  type: "service",
363
363
  name: bindingName,
364
- service: binding.name,
364
+ service: "service" in binding ? binding.service : binding.name,
365
365
  });
366
366
  } else if (binding.type === "durable_object_namespace") {
367
367
  meta.bindings.push({
@@ -23,6 +23,7 @@ import {
23
23
  type Binding,
24
24
  type Bindings,
25
25
  Json,
26
+ type WorkerBindingService,
26
27
  type WorkerBindingSpec,
27
28
  } from "./bindings.ts";
28
29
  import type { Bound } from "./bound.ts";
@@ -46,7 +47,7 @@ import { isPipeline } from "./pipeline.ts";
46
47
  import {
47
48
  QueueConsumer,
48
49
  deleteQueueConsumer,
49
- listQueueConsumers,
50
+ listQueueConsumersForWorker,
50
51
  } from "./queue-consumer.ts";
51
52
  import { type QueueResource, isQueue } from "./queue.ts";
52
53
  import { isVectorizeIndex } from "./vectorize-index.ts";
@@ -384,6 +385,56 @@ export type Worker<
384
385
  compatibilityFlags: string[];
385
386
  };
386
387
 
388
+ /**
389
+ * Represents a reference to a Cloudflare Worker service.
390
+ *
391
+ * @template RPC - The type of the worker's RPC entrypoint, defaults to Rpc.WorkerEntrypointBranded.
392
+ *
393
+ * This interface extends all properties of WorkerBindingService except for "name".
394
+ * It also includes an optional __rpc__ property for type branding.
395
+ */
396
+ export type WorkerRef<
397
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
398
+ > = Omit<WorkerBindingService, "name"> & {
399
+ /**
400
+ * Optional type branding for the worker's RPC entrypoint.
401
+ */
402
+ __rpc__?: RPC;
403
+ };
404
+
405
+ /**
406
+ * Creates a reference to a Cloudflare Worker service.
407
+ *
408
+ * @example
409
+ * // Create a reference to a Cloudflare Worker service:
410
+ * const ref = WorkerRef({
411
+ * service: "my-worker",
412
+ * environment: "production",
413
+ * namespace: "main"
414
+ * });
415
+ *
416
+ * // Optionally, you can specify only the service:
417
+ * const ref2 = WorkerRef({ service: "my-worker" });
418
+ *
419
+ * // You can also specify the RPC type for stronger typing:
420
+ * interface MyWorkerRPC extends Rpc.WorkerEntrypointBranded {
421
+ * myMethod(arg: string): Promise<number>;
422
+ * }
423
+ * const typedRef = WorkerRef<MyWorkerRPC>({ service: "my-worker" });
424
+ */
425
+ export function WorkerRef<
426
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
427
+ >(options?: {
428
+ service: string;
429
+ environment?: string;
430
+ namespace?: string;
431
+ }): WorkerRef<RPC> {
432
+ return {
433
+ ...options,
434
+ type: "service",
435
+ } as WorkerRef<RPC>;
436
+ }
437
+
387
438
  /**
388
439
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
389
440
  *
@@ -869,7 +920,7 @@ export const _Worker = Resource(
869
920
 
870
921
  if (this.phase === "delete") {
871
922
  // Delete any queue consumers attached to this worker first
872
- await deleteQueueConsumers(this, api, workerName);
923
+ await deleteQueueConsumers(api, workerName);
873
924
 
874
925
  // @ts-ignore
875
926
  await uploadWorkerScript({
@@ -879,7 +930,7 @@ export const _Worker = Resource(
879
930
  script:
880
931
  props.format === "cjs"
881
932
  ? "addEventListener('fetch', event => { event.respondWith(new Response('hello world')); });"
882
- : "export default { fetch(request) { return new Response('hello world'); } }",
933
+ : "export default { fetch(request) { return new Response('hello world'); }, queue: () => {} }",
883
934
  bindings: {} as B,
884
935
  // we are writing a stub worker (to remove binding/event source dependencies)
885
936
  // queue consumers will no longer exist by this point
@@ -1074,7 +1125,14 @@ export async function putWorker(
1074
1125
 
1075
1126
  return formData;
1076
1127
  },
1077
- (err) => err.status === 404 || err.status === 500 || err.status === 503,
1128
+ (err) =>
1129
+ err.status === 404 ||
1130
+ err.status === 500 ||
1131
+ err.status === 503 ||
1132
+ // this is a tranient error that cloudflare throws randomly
1133
+ (err instanceof CloudflareApiError &&
1134
+ err.status === 400 &&
1135
+ err.message.match(/binding.*failed to generate/)),
1078
1136
  10,
1079
1137
  100,
1080
1138
  );
@@ -1248,53 +1306,15 @@ async function getWorkerBindings(api: CloudflareApi, workerName: string) {
1248
1306
  * @param api CloudflareApi instance
1249
1307
  * @param workerName Name of the worker script
1250
1308
  */
1251
- async function deleteQueueConsumers<B extends Bindings>(
1252
- ctx: Context<Worker<B>>,
1309
+ async function deleteQueueConsumers(
1253
1310
  api: CloudflareApi,
1254
1311
  workerName: string,
1255
1312
  ): Promise<void> {
1256
- const eventSources = ctx.output?.eventSources || [];
1257
-
1258
- // Extract queue IDs from event sources
1259
- const queueIds = eventSources.flatMap((eventSource) => {
1260
- if (isQueue(eventSource)) {
1261
- return [eventSource.id];
1262
- }
1263
- if (isQueueEventSource(eventSource)) {
1264
- return [eventSource.queue.id];
1265
- }
1266
- return [];
1267
- });
1313
+ const consumers = await listQueueConsumersForWorker(api, workerName);
1268
1314
 
1269
- // Process each queue associated with this worker
1270
1315
  await Promise.all(
1271
- queueIds.map(async (queueId) => {
1272
- try {
1273
- // List all consumers for this queue
1274
- const consumers = await listQueueConsumers(api, queueId);
1275
-
1276
- // Filter consumers by worker name
1277
- const workerConsumers = consumers.filter(
1278
- (consumer) => consumer.scriptName === workerName,
1279
- );
1280
-
1281
- // Delete all consumers for this worker in parallel
1282
- await Promise.all(
1283
- workerConsumers.map(async (consumer) => {
1284
- console.log(
1285
- `Deleting queue consumer ${consumer.id} for worker ${workerName}`,
1286
- );
1287
- // Use the deleteQueueConsumer function from queue-consumer.ts
1288
- await deleteQueueConsumer(api, consumer.queueId, consumer.id);
1289
- }),
1290
- );
1291
- } catch (err) {
1292
- if (err instanceof CloudflareApiError && err.status === 404) {
1293
- // this is OK
1294
- } else {
1295
- throw err;
1296
- }
1297
- }
1316
+ consumers.map(async (consumer) => {
1317
+ await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
1298
1318
  }),
1299
1319
  );
1300
1320
  }
@@ -447,7 +447,7 @@ function processBindings(
447
447
  // Service binding
448
448
  services.push({
449
449
  binding: bindingName,
450
- service: binding.name,
450
+ service: "name" in binding ? binding.name : binding.service,
451
451
  });
452
452
  } else if (binding.type === "kv_namespace") {
453
453
  // KV Namespace binding