alchemy 0.70.1 → 0.70.2

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 (48) hide show
  1. package/bin/alchemy.js +1 -1
  2. package/lib/clickhouse/organization.d.ts +1 -1
  3. package/lib/clickhouse/organization.js +2 -2
  4. package/lib/clickhouse/organization.js.map +1 -1
  5. package/lib/clickhouse/service.d.ts +28 -18
  6. package/lib/clickhouse/service.d.ts.map +1 -1
  7. package/lib/clickhouse/service.js +61 -9
  8. package/lib/clickhouse/service.js.map +1 -1
  9. package/lib/cloudflare/account-api-token.js +1 -1
  10. package/lib/cloudflare/account-api-token.js.map +1 -1
  11. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  12. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  13. package/lib/cloudflare/d1-database.d.ts +1 -1
  14. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  15. package/lib/cloudflare/d1-database.js +1 -1
  16. package/lib/cloudflare/d1-database.js.map +1 -1
  17. package/lib/cloudflare/permission-groups.d.ts +1489 -26
  18. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  19. package/lib/cloudflare/permission-groups.js +1787 -6
  20. package/lib/cloudflare/permission-groups.js.map +1 -1
  21. package/lib/cloudflare/worker-metadata.d.ts +14 -2
  22. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  23. package/lib/cloudflare/worker-metadata.js +4 -1
  24. package/lib/cloudflare/worker-metadata.js.map +1 -1
  25. package/lib/cloudflare/worker-stub.d.ts +3 -1
  26. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  27. package/lib/cloudflare/worker-stub.js +19 -7
  28. package/lib/cloudflare/worker-stub.js.map +1 -1
  29. package/lib/cloudflare/worker.d.ts +74 -10
  30. package/lib/cloudflare/worker.d.ts.map +1 -1
  31. package/lib/cloudflare/worker.js +16 -7
  32. package/lib/cloudflare/worker.js.map +1 -1
  33. package/lib/util/camel-to-snake.d.ts +12 -0
  34. package/lib/util/camel-to-snake.d.ts.map +1 -0
  35. package/lib/util/camel-to-snake.js +16 -0
  36. package/lib/util/camel-to-snake.js.map +1 -0
  37. package/package.json +1 -1
  38. package/src/clickhouse/organization.ts +2 -2
  39. package/src/clickhouse/service.ts +124 -33
  40. package/src/cloudflare/account-api-token.ts +1 -1
  41. package/src/cloudflare/bun-spa/bun-spa.ts +0 -1
  42. package/src/cloudflare/d1-database.ts +2 -2
  43. package/src/cloudflare/permission-groups.ts +1832 -45
  44. package/src/cloudflare/worker-metadata.ts +20 -3
  45. package/src/cloudflare/worker-stub.ts +28 -11
  46. package/src/cloudflare/worker.ts +98 -17
  47. package/src/util/camel-to-snake.ts +88 -0
  48. package/workers/tunnel-proxy.js +1 -1
@@ -1,4 +1,5 @@
1
1
  import { assertNever } from "../util/assert-never.ts";
2
+ import { camelToSnakeObjectDeep } from "../util/camel-to-snake.ts";
2
3
  import { logger } from "../util/logger.ts";
3
4
  import { memoize } from "../util/memoize.ts";
4
5
  import { extractCloudflareResult } from "./api-response.ts";
@@ -180,8 +181,20 @@ export interface WorkerMetadata {
180
181
  compatibility_date: string;
181
182
  compatibility_flags?: string[];
182
183
  bindings: WorkerBindingSpec[];
183
- observability: {
184
- enabled: boolean;
184
+ observability?: {
185
+ enabled?: boolean;
186
+ head_sampling_rate?: number;
187
+ logs?: {
188
+ enabled?: boolean;
189
+ head_sampling_rate?: number;
190
+ invocation_logs?: boolean;
191
+ };
192
+ traces?: {
193
+ enabled?: boolean;
194
+ head_sampling_rate?: number;
195
+ persist?: boolean;
196
+ destinations?: string[];
197
+ };
185
198
  };
186
199
  logpush?: boolean;
187
200
  migrations?: SingleStepMigration;
@@ -235,6 +248,7 @@ export async function prepareWorkerMetadata(
235
248
  ...(oldSettings?.tags ?? []),
236
249
  ]),
237
250
  );
251
+
238
252
  const oldBindings = oldSettings?.bindings;
239
253
  // we use Cloudflare Worker tags to store a mapping between Alchemy's stable identifier and the binding name
240
254
  // e.g.
@@ -315,6 +329,8 @@ export async function prepareWorkerMetadata(
315
329
  return [];
316
330
  });
317
331
 
332
+ const observability = camelToSnakeObjectDeep(props.observability);
333
+
318
334
  // Prepare metadata with bindings
319
335
  const meta: WorkerMetadata = {
320
336
  compatibility_date: props.compatibilityDate,
@@ -324,7 +340,8 @@ export async function prepareWorkerMetadata(
324
340
  ),
325
341
  bindings: [],
326
342
  observability: {
327
- enabled: props.observability?.enabled !== false,
343
+ ...observability,
344
+ enabled: observability?.enabled !== false,
328
345
  },
329
346
  logpush: props.logpush ?? false,
330
347
  // TODO(sam): base64 encode instead? 0 collision risk vs readability.
@@ -128,7 +128,7 @@ export const WorkerStub = Resource("cloudflare::WorkerStub", async function <
128
128
  } as WorkerStub<RPC>;
129
129
  });
130
130
 
131
- async function exists(
131
+ export async function exists(
132
132
  api: CloudflareApi,
133
133
  workerName: string,
134
134
  ): Promise<boolean> {
@@ -145,9 +145,10 @@ async function exists(
145
145
  }
146
146
  }
147
147
 
148
- async function createEmptyWorker(
148
+ export async function createEmptyWorker(
149
149
  api: CloudflareApi,
150
150
  workerName: string,
151
+ version?: string,
151
152
  ): Promise<void> {
152
153
  // Minimal empty worker script
153
154
  const emptyScript = `export default {
@@ -178,6 +179,12 @@ async function createEmptyWorker(
178
179
  main_module: "worker.js",
179
180
  compatibility_date: "2025-04-20",
180
181
  bindings: [],
182
+ ...(version != null && {
183
+ annotations: {
184
+ "workers/tag": version,
185
+ "workers/message": `Version ${version}`,
186
+ },
187
+ }),
181
188
  }),
182
189
  ],
183
190
  {
@@ -189,14 +196,24 @@ async function createEmptyWorker(
189
196
  // Upload worker script
190
197
  await extractCloudflareResult(
191
198
  `create empty worker "${workerName}"`,
192
- api.put(
193
- `/accounts/${api.accountId}/workers/scripts/${workerName}`,
194
- formData,
195
- {
196
- headers: {
197
- "Content-Type": "multipart/form-data",
198
- },
199
- },
200
- ),
199
+ version != null
200
+ ? api.post(
201
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/versions`,
202
+ formData,
203
+ {
204
+ headers: {
205
+ "Content-Type": "multipart/form-data",
206
+ },
207
+ },
208
+ )
209
+ : api.put(
210
+ `/accounts/${api.accountId}/workers/scripts/${workerName}`,
211
+ formData,
212
+ {
213
+ headers: {
214
+ "Content-Type": "multipart/form-data",
215
+ },
216
+ },
217
+ ),
201
218
  );
202
219
  }
@@ -61,6 +61,7 @@ import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.ts";
61
61
  import "../esbuild/bundle.ts";
62
62
  import { Scope } from "../scope.ts";
63
63
  import type { WorkerRef } from "./worker-ref.ts";
64
+ import { createEmptyWorker, exists } from "./worker-stub.ts";
64
65
 
65
66
  /**
66
67
  * Configuration options for static assets
@@ -166,18 +167,12 @@ export interface BaseWorkerProps<
166
167
  url?: boolean;
167
168
 
168
169
  /**
169
- * Observability configuration for the worker
170
+ * Specify the observability behavior of the Worker.
170
171
  *
171
- * Controls whether worker logs are enabled
172
- * @default { enabled: true }
172
+ * @see https://developers.cloudflare.com/workers/wrangler/configuration/#observability
173
+ * @default - `enabled: true`
173
174
  */
174
- observability?: {
175
- /**
176
- * Whether to enable worker logs
177
- * @default true
178
- */
179
- enabled?: boolean;
180
- };
175
+ observability?: WorkerObservability;
181
176
 
182
177
  /**
183
178
  * Enable Workers Logpush to export trace events (request/response metadata,
@@ -393,6 +388,85 @@ export interface BaseWorkerProps<
393
388
  tailConsumers?: Array<Worker | { service: string }>;
394
389
  }
395
390
 
391
+ export interface WorkerObservability {
392
+ /**
393
+ * If observability is enabled for this Worker
394
+ *
395
+ * @default true
396
+ */
397
+ enabled?: boolean;
398
+
399
+ /**
400
+ * A number between 0 and 1, where 0 indicates zero out of one hundred requests are logged, and 1 indicates every request is logged.
401
+ * If head_sampling_rate is unspecified, it is configured to a default value of 1 (100%).
402
+ * @see https://developers.cloudflare.com/workers/observability/logs/workers-logs/#head-based-sampling
403
+ * @default 1
404
+ */
405
+ headSamplingRate?: number;
406
+
407
+ /**
408
+ * Configuration for worker logs
409
+ */
410
+ logs?: {
411
+ /**
412
+ * Whether logs are enabled
413
+ * @default true
414
+ */
415
+ enabled?: boolean;
416
+
417
+ /**
418
+ * The sampling rate for logs
419
+ */
420
+ headSamplingRate?: number;
421
+
422
+ /**
423
+ * Set to false to disable invocation logs
424
+ * @default true
425
+ */
426
+ invocationLogs?: boolean;
427
+
428
+ /**
429
+ * If logs should be persisted to the Cloudflare observability platform where they can be queried in the dashboard.
430
+ * @default true
431
+ */
432
+ persist?: boolean;
433
+
434
+ /**
435
+ * What destinations logs emitted from the Worker should be sent to.
436
+ * @default []
437
+ */
438
+ destinations?: string[];
439
+ };
440
+
441
+ /**
442
+ * Configuration for worker traces
443
+ */
444
+ traces?: {
445
+ /**
446
+ * Whether traces are enabled
447
+ * @default true
448
+ */
449
+ enabled?: boolean;
450
+
451
+ /**
452
+ * The sampling rate for traces
453
+ */
454
+ headSamplingRate?: number;
455
+
456
+ /**
457
+ * If traces should be persisted to the Cloudflare observability platform where they can be queried in the dashboard.
458
+ * @default true
459
+ */
460
+ persist?: boolean;
461
+
462
+ /**
463
+ * What destinations traces emitted from the Worker should be sent to.
464
+ * @default []
465
+ */
466
+ destinations?: string[];
467
+ };
468
+ }
469
+
396
470
  export interface InlineWorkerProps<
397
471
  B extends Bindings | undefined = Bindings,
398
472
  RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
@@ -863,13 +937,20 @@ const _Worker = Resource(
863
937
  durableObjects: options.durableObjects,
864
938
  workflows: options.workflows,
865
939
  });
866
- if (!props.version && this.output?.dev?.hasRemote !== false) {
940
+ if (this.output?.dev?.hasRemote !== false) {
867
941
  const api = await createCloudflareApi(props);
868
- await deleteQueueConsumers(api, options.name);
869
- await deleteWorker(api, {
870
- scriptName: options.name,
871
- dispatchNamespace: options.dispatchNamespace,
872
- });
942
+ if (props.version) {
943
+ //* if the worker exists we deploy an empty version so we can destroy
944
+ if (await exists(api, options.name)) {
945
+ await createEmptyWorker(api, options.name, props.version);
946
+ }
947
+ } else {
948
+ await deleteQueueConsumers(api, options.name);
949
+ await deleteWorker(api, {
950
+ scriptName: options.name,
951
+ dispatchNamespace: options.dispatchNamespace,
952
+ });
953
+ }
873
954
  }
874
955
  return this.destroy();
875
956
  }
@@ -1607,7 +1688,7 @@ async function assertWorkerDoesNotExist(
1607
1688
  );
1608
1689
  }
1609
1690
 
1610
- async function getScriptMetadata(
1691
+ export async function getScriptMetadata(
1611
1692
  api: CloudflareApi,
1612
1693
  scriptName: string,
1613
1694
  ): Promise<WorkerScriptMetadata | undefined> {
@@ -0,0 +1,88 @@
1
+ export interface LogPushProps {
2
+ maxFooBar: string;
3
+ }
4
+
5
+ export function camelToSnakeObjectDeep<T extends object | undefined>(
6
+ obj: T,
7
+ ): T extends undefined ? undefined : CamelToSnake<T> {
8
+ return (
9
+ obj
10
+ ? Object.fromEntries(
11
+ Object.entries(obj).map(([key, value]) => [
12
+ key
13
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") // Handle consecutive capitals: "FOOBar" -> "FOO_Bar"
14
+ .replace(/([a-z])([A-Z])/g, "$1_$2") // Handle normal camelCase: "fooBar" -> "foo_Bar"
15
+ .toLowerCase(),
16
+ Array.isArray(value)
17
+ ? value.map(camelToSnakeObjectDeep)
18
+ : typeof value === "object" && value !== null
19
+ ? camelToSnakeObjectDeep(value)
20
+ : value,
21
+ ]),
22
+ )
23
+ : undefined
24
+ ) as T extends undefined ? undefined : CamelToSnake<T>;
25
+ }
26
+
27
+ // Helper to check if a character is uppercase
28
+ type IsUpper<C extends string> = C extends Uppercase<C>
29
+ ? C extends Lowercase<C>
30
+ ? false // Not a letter
31
+ : true // Uppercase letter
32
+ : false;
33
+
34
+ // Helper to check if a character is lowercase
35
+ type IsLower<C extends string> = C extends Lowercase<C>
36
+ ? C extends Uppercase<C>
37
+ ? false // Not a letter
38
+ : true // Lowercase letter
39
+ : false;
40
+
41
+ // Convert a camelCase string to snake_case at the type level
42
+ type CamelToSnakeString<
43
+ S extends string,
44
+ Acc extends string = "",
45
+ > = S extends `${infer First}${infer Second}${infer Rest}`
46
+ ? IsUpper<First> extends true
47
+ ? IsUpper<Second> extends true
48
+ ? // FOO... pattern - check if next char is lowercase
49
+ Rest extends `${infer Third}${infer _}`
50
+ ? IsLower<Third> extends true
51
+ ? // FOOBar -> add First, add _, add Second lowercase, continue with Rest
52
+ CamelToSnakeString<
53
+ Rest,
54
+ `${Acc}${Lowercase<First>}_${Lowercase<Second>}`
55
+ >
56
+ : // FOO -> continue
57
+ CamelToSnakeString<`${Second}${Rest}`, `${Acc}${Lowercase<First>}`>
58
+ : // Only two chars left, both upper
59
+ CamelToSnakeString<`${Second}${Rest}`, `${Acc}${Lowercase<First>}`>
60
+ : // FooBar or Foo - add underscore before uppercase if not at start
61
+ CamelToSnakeString<
62
+ `${Second}${Rest}`,
63
+ Acc extends "" ? `${Lowercase<First>}` : `${Acc}_${Lowercase<First>}`
64
+ >
65
+ : IsLower<First> extends true
66
+ ? IsUpper<Second> extends true
67
+ ? // fooBar -> just add the lowercase char, underscore will be added when processing uppercase
68
+ CamelToSnakeString<`${Second}${Rest}`, `${Acc}${First}`>
69
+ : // foobar -> continue
70
+ CamelToSnakeString<`${Second}${Rest}`, `${Acc}${First}`>
71
+ : // Number or special char
72
+ CamelToSnakeString<`${Second}${Rest}`, `${Acc}${First}`>
73
+ : S extends `${infer Last}`
74
+ ? `${Acc}${Lowercase<Last>}`
75
+ : Acc;
76
+
77
+ // Deep transformation for objects
78
+ type CamelToSnake<T> = T extends object
79
+ ? T extends Array<infer U>
80
+ ? Array<CamelToSnake<U>>
81
+ : T extends Date | RegExp | Function
82
+ ? T // Don't transform special object types
83
+ : {
84
+ [K in keyof T as K extends string
85
+ ? CamelToSnakeString<K>
86
+ : K]: CamelToSnake<T[K]>;
87
+ }
88
+ : T;
@@ -34,7 +34,7 @@ var h={async fetch(e,r){let n=new URL(e.url);n.host=r.TUNNEL_HOST;let l=new Head
34
34
  Alchemy</a>.</p>
35
35
  </div>
36
36
  <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
37
- <p class="text-sm text-slate-500">Alchemy 0.70.1</p>
37
+ <p class="text-sm text-slate-500">Alchemy 0.70.2</p>
38
38
  </div>
39
39
  </div>
40
40
  </body>