alchemy 0.20.0 → 0.21.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 (39) hide show
  1. package/lib/cloudflare/bound.d.ts +4 -1
  2. package/lib/cloudflare/bound.d.ts.map +1 -1
  3. package/lib/cloudflare/durable-object-namespace.d.ts +2 -0
  4. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  5. package/lib/cloudflare/durable-object-namespace.js +3 -0
  6. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  7. package/lib/cloudflare/worker-metadata.d.ts +1 -1
  8. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  9. package/lib/cloudflare/worker-metadata.js +115 -24
  10. package/lib/cloudflare/worker-metadata.js.map +1 -1
  11. package/lib/cloudflare/worker.d.ts +41 -10
  12. package/lib/cloudflare/worker.d.ts.map +1 -1
  13. package/lib/cloudflare/worker.js +34 -16
  14. package/lib/cloudflare/worker.js.map +1 -1
  15. package/lib/cloudflare/workflow.d.ts +2 -0
  16. package/lib/cloudflare/workflow.d.ts.map +1 -1
  17. package/lib/cloudflare/workflow.js +3 -0
  18. package/lib/cloudflare/workflow.js.map +1 -1
  19. package/lib/cloudflare/wrangler.json.d.ts +1 -1
  20. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  21. package/lib/cloudflare/wrangler.json.js +4 -0
  22. package/lib/cloudflare/wrangler.json.js.map +1 -1
  23. package/lib/index.d.ts +1 -0
  24. package/lib/index.d.ts.map +1 -1
  25. package/lib/index.js +1 -0
  26. package/lib/index.js.map +1 -1
  27. package/lib/type.d.ts +6 -0
  28. package/lib/type.d.ts.map +1 -0
  29. package/lib/type.js +7 -0
  30. package/lib/type.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/cloudflare/bound.ts +45 -35
  33. package/src/cloudflare/durable-object-namespace.ts +10 -0
  34. package/src/cloudflare/worker-metadata.ts +136 -36
  35. package/src/cloudflare/worker.ts +183 -93
  36. package/src/cloudflare/workflow.ts +5 -0
  37. package/src/cloudflare/wrangler.json.ts +5 -1
  38. package/src/index.ts +1 -0
  39. package/src/type.ts +9 -0
@@ -8,6 +8,7 @@ import { bootstrapPlugin } from "../runtime/plugin.js";
8
8
  import { Scope } from "../scope.js";
9
9
  import { Secret, secret } from "../secret.js";
10
10
  import { serializeScope } from "../serde.js";
11
+ import type { type } from "../type.js";
11
12
  import { withExponentialBackoff } from "../util/retry.js";
12
13
  import { slugify } from "../util/slugify.js";
13
14
  import { CloudflareApiError, handleApiError } from "./api-error.js";
@@ -17,11 +18,20 @@ import {
17
18
  createCloudflareApi,
18
19
  } from "./api.js";
19
20
  import type { Assets } from "./assets.js";
20
- import { type Binding, type Bindings, Json } from "./bindings.js";
21
+ import {
22
+ type Binding,
23
+ type Bindings,
24
+ Json,
25
+ type WorkerBindingSpec,
26
+ } from "./bindings.js";
21
27
  import type { Bound } from "./bound.js";
22
28
  import { isBucket } from "./bucket.js";
23
29
  import { bundleWorkerScript } from "./bundle/bundle-worker.js";
24
30
  import { isD1Database } from "./d1-database.js";
31
+ import {
32
+ DurableObjectNamespace,
33
+ isDurableObjectNamespace,
34
+ } from "./durable-object-namespace.js";
25
35
  import {
26
36
  type EventSource,
27
37
  type QueueEventSource,
@@ -90,8 +100,10 @@ export interface AssetsConfig {
90
100
  serve_directly?: boolean;
91
101
  }
92
102
 
93
- export interface BaseWorkerProps<B extends Bindings = Bindings>
94
- extends CloudflareApiOptions {
103
+ export interface BaseWorkerProps<
104
+ B extends Bindings | undefined = undefined,
105
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
106
+ > extends CloudflareApiOptions {
95
107
  /**
96
108
  * Bundle options when using entryPoint
97
109
  *
@@ -198,16 +210,27 @@ export interface BaseWorkerProps<B extends Bindings = Bindings>
198
210
  * Can include queues, streams, or other event sources.
199
211
  */
200
212
  eventSources?: EventSource[];
213
+
214
+ /**
215
+ * The RPC class to use for the worker.
216
+ *
217
+ * This is only used when using the rpc property.
218
+ */
219
+ rpc?: (new (...args: any[]) => RPC) | type<RPC>;
201
220
  }
202
221
 
203
- export interface InlineWorkerProps<B extends Bindings = Bindings>
204
- extends BaseWorkerProps<B> {
222
+ export interface InlineWorkerProps<
223
+ B extends Bindings | undefined = Bindings,
224
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
225
+ > extends BaseWorkerProps<B, RPC> {
205
226
  script: string;
206
227
  entrypoint?: undefined;
207
228
  }
208
229
 
209
- export interface EntrypointWorkerProps<B extends Bindings = Bindings>
210
- extends BaseWorkerProps<B> {
230
+ export interface EntrypointWorkerProps<
231
+ B extends Bindings | undefined = Bindings,
232
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
233
+ > extends BaseWorkerProps<B, RPC> {
211
234
  entrypoint: string;
212
235
  script?: undefined;
213
236
  }
@@ -215,9 +238,10 @@ export interface EntrypointWorkerProps<B extends Bindings = Bindings>
215
238
  /**
216
239
  * Properties for creating or updating a Worker
217
240
  */
218
- export type WorkerProps<B extends Bindings = Bindings> =
219
- | InlineWorkerProps<B>
220
- | EntrypointWorkerProps<B>;
241
+ export type WorkerProps<
242
+ B extends Bindings | undefined = Bindings,
243
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
244
+ > = InlineWorkerProps<B, RPC> | EntrypointWorkerProps<B, RPC>;
221
245
 
222
246
  export type FetchWorkerProps<
223
247
  B extends Bindings = Bindings,
@@ -265,63 +289,70 @@ export function isWorker(resource: Resource): resource is Worker<any> {
265
289
  /**
266
290
  * Output returned after Worker creation/update
267
291
  */
268
- export type Worker<B extends Bindings = Bindings> =
269
- Resource<"cloudflare::Worker"> &
270
- Omit<WorkerProps<B>, "url" | "script"> &
271
- globalThis.Service & {
272
- type: "service";
273
-
274
- /**
275
- * The ID of the worker
276
- */
277
- id: string;
278
-
279
- /**
280
- * The name of the worker
281
- */
282
- name: string;
283
-
284
- /**
285
- * Time at which the worker was created
286
- */
287
- createdAt: number;
288
-
289
- /**
290
- * Time at which the worker was last updated
291
- */
292
- updatedAt: number;
293
-
294
- /**
295
- * The worker's URL if enabled
296
- * Format: {name}.{subdomain}.workers.dev
297
- */
298
- url?: string;
299
-
300
- /**
301
- * The bindings that were created
302
- */
303
- bindings: B | undefined;
304
-
305
- /**
306
- * Configuration for static assets
307
- */
308
- assets?: AssetsConfig;
309
-
310
- // phantom property (for typeof myWorker.Env)
311
- Env: {
312
- [bindingName in keyof B]: Bound<B[bindingName]>;
313
- };
292
+ export type Worker<
293
+ B extends Bindings | undefined = Bindings | undefined,
294
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
295
+ > = Resource<"cloudflare::Worker"> &
296
+ Omit<WorkerProps<B>, "url" | "script"> &
297
+ globalThis.Service & {
298
+ /** @internal phantom property */
299
+ __rpc__?: RPC;
314
300
 
315
- /**
316
- * The compatibility date for the worker
317
- */
318
- compatibilityDate: string;
301
+ type: "service";
319
302
 
320
- /**
321
- * The compatibility flags for the worker
322
- */
323
- compatibilityFlags: string[];
324
- };
303
+ /**
304
+ * The ID of the worker
305
+ */
306
+ id: string;
307
+
308
+ /**
309
+ * The name of the worker
310
+ */
311
+ name: string;
312
+
313
+ /**
314
+ * Time at which the worker was created
315
+ */
316
+ createdAt: number;
317
+
318
+ /**
319
+ * Time at which the worker was last updated
320
+ */
321
+ updatedAt: number;
322
+
323
+ /**
324
+ * The worker's URL if enabled
325
+ * Format: {name}.{subdomain}.workers.dev
326
+ */
327
+ url?: string;
328
+
329
+ /**
330
+ * The bindings that were created
331
+ */
332
+ bindings: B;
333
+
334
+ /**
335
+ * Configuration for static assets
336
+ */
337
+ assets?: AssetsConfig;
338
+
339
+ // phantom property (for typeof myWorker.Env)
340
+ Env: B extends Bindings
341
+ ? {
342
+ [bindingName in keyof B]: Bound<B[bindingName]>;
343
+ }
344
+ : undefined;
345
+
346
+ /**
347
+ * The compatibility date for the worker
348
+ */
349
+ compatibilityDate: string;
350
+
351
+ /**
352
+ * The compatibility flags for the worker
353
+ */
354
+ compatibilityFlags: string[];
355
+ };
325
356
 
326
357
  /**
327
358
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
@@ -412,13 +443,36 @@ export type Worker<B extends Bindings = Bindings> =
412
443
  * crons: ['* 15 * * *', '0 0 * * *', '0 12 * * MON']
413
444
  * })
414
445
  *
446
+ * @example
447
+ * // Create cross-script durable object binding where one worker
448
+ * // defines the durable object and another worker accesses it:
449
+ * const dataWorker = await Worker("data-worker", {
450
+ * name: "data-worker",
451
+ * entrypoint: "./src/data.ts",
452
+ * bindings: {
453
+ * // Bind to its own durable object
454
+ * STORAGE: new DurableObjectNamespace("storage", {
455
+ * className: "DataStorage"
456
+ * })
457
+ * }
458
+ * });
459
+ *
460
+ * const apiWorker = await Worker("api-worker", {
461
+ * name: "api-worker",
462
+ * entrypoint: "./src/api.ts",
463
+ * bindings: {
464
+ * // Cross-script binding to the data worker's durable object
465
+ * SHARED_STORAGE: dataWorker.bindings.STORAGE
466
+ * }
467
+ * });
468
+ *
415
469
  * @see
416
470
  * https://developers.cloudflare.com/workers/
417
471
  */
418
- export function Worker<const B extends Bindings>(
419
- id: string,
420
- props: WorkerProps<B>,
421
- ): Promise<Worker<B>>;
472
+ export function Worker<
473
+ const B extends Bindings,
474
+ RPC extends Rpc.WorkerEntrypointBranded,
475
+ >(id: string, props: WorkerProps<B, RPC>): Promise<Worker<B, RPC>>;
422
476
  export function Worker<const B extends Bindings, const E extends EventSource[]>(
423
477
  id: string,
424
478
  meta: ImportMeta,
@@ -431,6 +485,7 @@ export function Worker<const B extends Bindings>(
431
485
  ): Promise<Worker<B>> {
432
486
  const [id, meta, props] =
433
487
  args.length === 2 ? [args[0], undefined, args[1]] : args;
488
+
434
489
  if (("fetch" in props && props.fetch) || ("queue" in props && props.queue)) {
435
490
  const scope = Scope.current;
436
491
 
@@ -648,7 +703,11 @@ export const _Worker = Resource(
648
703
  const compatibilityFlags = props.compatibilityFlags ?? [];
649
704
 
650
705
  const uploadWorkerScript = async (props: WorkerProps<B>) => {
651
- const oldBindings = await this.get<Bindings>("bindings");
706
+ const [oldBindings, oldMetadata] = await Promise.all([
707
+ getWorkerBindings(api, workerName),
708
+ getWorkerScriptMetadata(api, workerName),
709
+ ]);
710
+ const oldTags = oldMetadata?.default_environment?.script?.tags;
652
711
 
653
712
  // Get the script content - either from props.script, or by bundling
654
713
  const scriptContent =
@@ -695,6 +754,7 @@ export const _Worker = Resource(
695
754
  const scriptMetadata = await prepareWorkerMetadata(
696
755
  this,
697
756
  oldBindings,
757
+ oldTags,
698
758
  {
699
759
  ...props,
700
760
  compatibilityDate,
@@ -706,9 +766,6 @@ export const _Worker = Resource(
706
766
 
707
767
  await putWorker(api, workerName, scriptContent, scriptMetadata);
708
768
 
709
- // TODO: it is less than ideal that this can fail, resulting in state problem
710
- await this.set("bindings", props.bindings);
711
-
712
769
  for (const workflow of workflowsBindings) {
713
770
  await upsertWorkflow(api, {
714
771
  workflowName: workflow.workflowName,
@@ -816,6 +873,24 @@ export const _Worker = Resource(
816
873
 
817
874
  const { scriptMetadata, workerUrl, now } = await uploadWorkerScript(props);
818
875
 
876
+ function exportBindings() {
877
+ return Object.fromEntries(
878
+ Object.entries(props.bindings ?? ({} as B)).map(
879
+ ([bindingName, binding]) => [
880
+ bindingName,
881
+ isDurableObjectNamespace(binding) &&
882
+ binding.scriptName === undefined
883
+ ? new DurableObjectNamespace(binding.id, {
884
+ ...binding,
885
+ // re-export this binding mapping to the host worker (this worker)
886
+ scriptName: workerName,
887
+ })
888
+ : binding,
889
+ ],
890
+ ),
891
+ );
892
+ }
893
+
819
894
  // Construct the output
820
895
  return this({
821
896
  ...props,
@@ -826,7 +901,7 @@ export const _Worker = Resource(
826
901
  compatibilityDate,
827
902
  compatibilityFlags,
828
903
  format: props.format || "esm", // Include format in the output
829
- bindings: props.bindings ?? ({} as B),
904
+ bindings: exportBindings(),
830
905
  env: props.env,
831
906
  observability: scriptMetadata.observability,
832
907
  createdAt: now,
@@ -1058,34 +1133,49 @@ export async function getWorkerScriptMetadata(
1058
1133
  return ((await response.json()) as any).result as WorkerScriptMetadata;
1059
1134
  }
1060
1135
 
1061
- export async function _getWorkerBindings(
1062
- api: CloudflareApi,
1063
- workerName: string,
1064
- environment = "production",
1065
- ) {
1136
+ async function getWorkerBindings(api: CloudflareApi, workerName: string) {
1137
+ // Fetch the bindings for a worker by calling the Cloudflare API endpoint:
1138
+ // GET /accounts/:account_id/workers/scripts/:script_name/bindings
1139
+ // See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
1066
1140
  const response = await api.get(
1067
- `/accounts/${api.accountId}/workers/services/${workerName}/environments/${environment}/bindings`,
1068
- {
1069
- headers: {
1070
- Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
1071
- "Content-Type": "application/json",
1072
- },
1073
- },
1141
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
1074
1142
  );
1075
-
1076
1143
  if (response.status === 404) {
1077
1144
  return undefined;
1078
1145
  }
1079
-
1080
1146
  if (!response.ok) {
1081
1147
  throw new Error(
1082
- `Failed to fetch bindings: ${response.status} ${response.statusText}`,
1148
+ `Error getting worker bindings: ${response.status} ${response.statusText}`,
1083
1149
  );
1084
1150
  }
1085
-
1086
- const data: any = await response.json();
1087
-
1088
- return data.result;
1151
+ // The result is an object with a "result" property containing the bindings array
1152
+ const { result, success, errors } = (await response.json()) as {
1153
+ result: {
1154
+ bindings: WorkerBindingSpec[];
1155
+ compatibility_date: string;
1156
+ compatibility_flags: string[];
1157
+ [key: string]: any;
1158
+ };
1159
+ success: boolean;
1160
+ errors: Array<{
1161
+ code: number;
1162
+ message: string;
1163
+ documentation_url: string;
1164
+ [key: string]: any;
1165
+ }>;
1166
+ messages: Array<{
1167
+ code: number;
1168
+ message: string;
1169
+ documentation_url: string;
1170
+ [key: string]: any;
1171
+ }>;
1172
+ };
1173
+ if (!success) {
1174
+ throw new Error(
1175
+ `Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
1176
+ );
1177
+ }
1178
+ return result.bindings;
1089
1179
  }
1090
1180
 
1091
1181
  /**
@@ -1,5 +1,6 @@
1
1
  import { handleApiError } from "./api-error.js";
2
2
  import type { CloudflareApi } from "./api.js";
3
+ import type { Binding } from "./bindings.js";
3
4
 
4
5
  export interface WorkflowProps {
5
6
  /**
@@ -20,6 +21,10 @@ export interface WorkflowProps {
20
21
  className?: string;
21
22
  }
22
23
 
24
+ export function isWorkflow(binding: Binding): binding is Workflow {
25
+ return typeof binding === "object" && binding.type === "workflow";
26
+ }
27
+
23
28
  export class Workflow<PARAMS = unknown> {
24
29
  public readonly type = "workflow";
25
30
  /**
@@ -17,7 +17,7 @@ export interface WranglerJsonProps {
17
17
  /**
18
18
  * The worker to generate the wrangler.json file for
19
19
  */
20
- worker: Worker;
20
+ worker: Worker<any>;
21
21
  /**
22
22
  * Path to write the wrangler.json file to
23
23
  *
@@ -393,6 +393,10 @@ function processBindings(
393
393
  }
394
394
  // Process each binding
395
395
  for (const [bindingName, binding] of Object.entries(bindings)) {
396
+ if (typeof binding === "function") {
397
+ // this is only reachable in the
398
+ throw new Error(`Invalid binding ${bindingName} is a function`);
399
+ }
396
400
  if (typeof binding === "string") {
397
401
  // Plain text binding - add to vars
398
402
  if (!spec.vars) {
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export type * from "./scope.js";
5
5
  export * from "./secret.js";
6
6
  export * from "./serde.js";
7
7
  export * from "./state.js";
8
+ export * from "./type.js";
8
9
  export * from "./util/ignore.js";
9
10
 
10
11
  import { alchemy } from "./alchemy.js";
package/src/type.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type type<T> = typeof type<T>;
2
+ /**
3
+ * Used to construct type-level alias information.
4
+ */
5
+ export const type = ((): any => {
6
+ throw new Error("Should never be called, purely for type-level aliasing");
7
+ }) as (<T>() => T) &
8
+ // we also want to make this a "class type" so that syntax highlighting is always as a type
9
+ (new <T>() => T);