alchemy 0.20.1 → 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.
@@ -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,7 +18,12 @@ 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";
@@ -94,8 +100,10 @@ export interface AssetsConfig {
94
100
  serve_directly?: boolean;
95
101
  }
96
102
 
97
- export interface BaseWorkerProps<B extends Bindings | undefined = undefined>
98
- extends CloudflareApiOptions {
103
+ export interface BaseWorkerProps<
104
+ B extends Bindings | undefined = undefined,
105
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
106
+ > extends CloudflareApiOptions {
99
107
  /**
100
108
  * Bundle options when using entryPoint
101
109
  *
@@ -202,17 +210,27 @@ export interface BaseWorkerProps<B extends Bindings | undefined = undefined>
202
210
  * Can include queues, streams, or other event sources.
203
211
  */
204
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>;
205
220
  }
206
221
 
207
- export interface InlineWorkerProps<B extends Bindings | undefined = Bindings>
208
- 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> {
209
226
  script: string;
210
227
  entrypoint?: undefined;
211
228
  }
212
229
 
213
230
  export interface EntrypointWorkerProps<
214
231
  B extends Bindings | undefined = Bindings,
215
- > extends BaseWorkerProps<B> {
232
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
233
+ > extends BaseWorkerProps<B, RPC> {
216
234
  entrypoint: string;
217
235
  script?: undefined;
218
236
  }
@@ -220,9 +238,10 @@ export interface EntrypointWorkerProps<
220
238
  /**
221
239
  * Properties for creating or updating a Worker
222
240
  */
223
- export type WorkerProps<B extends Bindings | undefined = Bindings> =
224
- | InlineWorkerProps<B>
225
- | 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>;
226
245
 
227
246
  export type FetchWorkerProps<
228
247
  B extends Bindings = Bindings,
@@ -270,65 +289,70 @@ export function isWorker(resource: Resource): resource is Worker<any> {
270
289
  /**
271
290
  * Output returned after Worker creation/update
272
291
  */
273
- export type Worker<B extends Bindings | undefined = Bindings | undefined> =
274
- Resource<"cloudflare::Worker"> &
275
- Omit<WorkerProps<B>, "url" | "script"> &
276
- globalThis.Service & {
277
- type: "service";
278
-
279
- /**
280
- * The ID of the worker
281
- */
282
- id: string;
283
-
284
- /**
285
- * The name of the worker
286
- */
287
- name: string;
288
-
289
- /**
290
- * Time at which the worker was created
291
- */
292
- createdAt: number;
293
-
294
- /**
295
- * Time at which the worker was last updated
296
- */
297
- updatedAt: number;
298
-
299
- /**
300
- * The worker's URL if enabled
301
- * Format: {name}.{subdomain}.workers.dev
302
- */
303
- url?: string;
304
-
305
- /**
306
- * The bindings that were created
307
- */
308
- bindings: B;
309
-
310
- /**
311
- * Configuration for static assets
312
- */
313
- assets?: AssetsConfig;
314
-
315
- // phantom property (for typeof myWorker.Env)
316
- Env: B extends Bindings
317
- ? {
318
- [bindingName in keyof B]: Bound<B[bindingName]>;
319
- }
320
- : undefined;
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;
321
300
 
322
- /**
323
- * The compatibility date for the worker
324
- */
325
- compatibilityDate: string;
301
+ type: "service";
326
302
 
327
- /**
328
- * The compatibility flags for the worker
329
- */
330
- compatibilityFlags: string[];
331
- };
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
+ };
332
356
 
333
357
  /**
334
358
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
@@ -445,10 +469,10 @@ export type Worker<B extends Bindings | undefined = Bindings | undefined> =
445
469
  * @see
446
470
  * https://developers.cloudflare.com/workers/
447
471
  */
448
- export function Worker<const B extends Bindings>(
449
- id: string,
450
- props: WorkerProps<B>,
451
- ): 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>>;
452
476
  export function Worker<const B extends Bindings, const E extends EventSource[]>(
453
477
  id: string,
454
478
  meta: ImportMeta,
@@ -461,6 +485,7 @@ export function Worker<const B extends Bindings>(
461
485
  ): Promise<Worker<B>> {
462
486
  const [id, meta, props] =
463
487
  args.length === 2 ? [args[0], undefined, args[1]] : args;
488
+
464
489
  if (("fetch" in props && props.fetch) || ("queue" in props && props.queue)) {
465
490
  const scope = Scope.current;
466
491
 
@@ -678,7 +703,11 @@ export const _Worker = Resource(
678
703
  const compatibilityFlags = props.compatibilityFlags ?? [];
679
704
 
680
705
  const uploadWorkerScript = async (props: WorkerProps<B>) => {
681
- 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;
682
711
 
683
712
  // Get the script content - either from props.script, or by bundling
684
713
  const scriptContent =
@@ -725,6 +754,7 @@ export const _Worker = Resource(
725
754
  const scriptMetadata = await prepareWorkerMetadata(
726
755
  this,
727
756
  oldBindings,
757
+ oldTags,
728
758
  {
729
759
  ...props,
730
760
  compatibilityDate,
@@ -736,9 +766,6 @@ export const _Worker = Resource(
736
766
 
737
767
  await putWorker(api, workerName, scriptContent, scriptMetadata);
738
768
 
739
- // TODO: it is less than ideal that this can fail, resulting in state problem
740
- await this.set("bindings", props.bindings);
741
-
742
769
  for (const workflow of workflowsBindings) {
743
770
  await upsertWorkflow(api, {
744
771
  workflowName: workflow.workflowName,
@@ -1106,34 +1133,49 @@ export async function getWorkerScriptMetadata(
1106
1133
  return ((await response.json()) as any).result as WorkerScriptMetadata;
1107
1134
  }
1108
1135
 
1109
- export async function _getWorkerBindings(
1110
- api: CloudflareApi,
1111
- workerName: string,
1112
- environment = "production",
1113
- ) {
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/
1114
1140
  const response = await api.get(
1115
- `/accounts/${api.accountId}/workers/services/${workerName}/environments/${environment}/bindings`,
1116
- {
1117
- headers: {
1118
- Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
1119
- "Content-Type": "application/json",
1120
- },
1121
- },
1141
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
1122
1142
  );
1123
-
1124
1143
  if (response.status === 404) {
1125
1144
  return undefined;
1126
1145
  }
1127
-
1128
1146
  if (!response.ok) {
1129
1147
  throw new Error(
1130
- `Failed to fetch bindings: ${response.status} ${response.statusText}`,
1148
+ `Error getting worker bindings: ${response.status} ${response.statusText}`,
1131
1149
  );
1132
1150
  }
1133
-
1134
- const data: any = await response.json();
1135
-
1136
- 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;
1137
1179
  }
1138
1180
 
1139
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);