alchemy 0.70.1 → 0.71.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.
- package/bin/alchemy.js +13 -15
- package/bin/commands/init.ts +0 -10
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +2 -0
- package/lib/apply.js.map +1 -1
- package/lib/clickhouse/organization.d.ts +1 -1
- package/lib/clickhouse/organization.js +2 -2
- package/lib/clickhouse/organization.js.map +1 -1
- package/lib/clickhouse/service.d.ts +28 -18
- package/lib/clickhouse/service.d.ts.map +1 -1
- package/lib/clickhouse/service.js +61 -9
- package/lib/clickhouse/service.js.map +1 -1
- package/lib/cloudflare/account-api-token.js +1 -1
- package/lib/cloudflare/account-api-token.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +12 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -2
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts +6 -0
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +5 -3
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.js +29 -9
- package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-database.d.ts +1 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +1 -1
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/index.d.ts +2 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +2 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/logpush-job.d.ts +349 -0
- package/lib/cloudflare/logpush-job.d.ts.map +1 -0
- package/lib/cloudflare/logpush-job.js +277 -0
- package/lib/cloudflare/logpush-job.js.map +1 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +1 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +16 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +2 -0
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/permission-groups.d.ts +1489 -26
- package/lib/cloudflare/permission-groups.d.ts.map +1 -1
- package/lib/cloudflare/permission-groups.js +1787 -6
- package/lib/cloudflare/permission-groups.js.map +1 -1
- package/lib/cloudflare/queue-consumer.js +2 -2
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/worker-loader.d.ts +9 -0
- package/lib/cloudflare/worker-loader.d.ts.map +1 -0
- package/lib/cloudflare/worker-loader.js +6 -0
- package/lib/cloudflare/worker-loader.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts +14 -2
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +10 -1
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-stub.d.ts +3 -1
- package/lib/cloudflare/worker-stub.d.ts.map +1 -1
- package/lib/cloudflare/worker-stub.js +19 -7
- package/lib/cloudflare/worker-stub.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +1 -1
- package/lib/cloudflare/worker.d.ts +74 -10
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +23 -7
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +3 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +9 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/github/comment.d.ts.map +1 -1
- package/lib/github/comment.js +14 -7
- package/lib/github/comment.js.map +1 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +6 -7
- package/lib/scope.js.map +1 -1
- package/lib/util/camel-to-snake.d.ts +12 -0
- package/lib/util/camel-to-snake.d.ts.map +1 -0
- package/lib/util/camel-to-snake.js +16 -0
- package/lib/util/camel-to-snake.js.map +1 -0
- package/lib/util/retry.d.ts +1 -1
- package/lib/util/retry.d.ts.map +1 -1
- package/lib/util/retry.js +2 -2
- package/lib/util/retry.js.map +1 -1
- package/lib/util/validate-resource-id.d.ts +2 -0
- package/lib/util/validate-resource-id.d.ts.map +1 -0
- package/lib/util/validate-resource-id.js +11 -0
- package/lib/util/validate-resource-id.js.map +1 -0
- package/package.json +2 -2
- package/src/apply.ts +2 -0
- package/src/clickhouse/organization.ts +2 -2
- package/src/clickhouse/service.ts +124 -33
- package/src/cloudflare/account-api-token.ts +1 -1
- package/src/cloudflare/bindings.ts +13 -0
- package/src/cloudflare/bound.ts +36 -32
- package/src/cloudflare/bucket.ts +13 -4
- package/src/cloudflare/bun-spa/bun-spa.ts +42 -11
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-database.ts +2 -2
- package/src/cloudflare/index.ts +2 -0
- package/src/cloudflare/logpush-job.ts +768 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +18 -6
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +2 -0
- package/src/cloudflare/permission-groups.ts +1832 -45
- package/src/cloudflare/queue-consumer.ts +2 -2
- package/src/cloudflare/worker-loader.ts +12 -0
- package/src/cloudflare/worker-metadata.ts +25 -3
- package/src/cloudflare/worker-stub.ts +28 -11
- package/src/cloudflare/worker-subdomain.ts +1 -1
- package/src/cloudflare/worker.ts +106 -17
- package/src/cloudflare/wrangler.json.ts +15 -0
- package/src/github/comment.ts +17 -8
- package/src/scope.ts +7 -10
- package/src/util/camel-to-snake.ts +88 -0
- package/src/util/retry.ts +2 -1
- package/src/util/validate-resource-id.ts +10 -0
- package/workers/tunnel-proxy.js +1 -1
|
@@ -318,7 +318,7 @@ export async function createQueueConsumer(
|
|
|
318
318
|
typeof props.settings.deadLetterQueue === "string"
|
|
319
319
|
? props.settings.deadLetterQueue
|
|
320
320
|
: props.settings.deadLetterQueue.name;
|
|
321
|
-
createPayload.
|
|
321
|
+
createPayload.dead_letter_queue = dlqName;
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -405,7 +405,7 @@ async function updateQueueConsumer(
|
|
|
405
405
|
typeof props.settings.deadLetterQueue === "string"
|
|
406
406
|
? props.settings.deadLetterQueue
|
|
407
407
|
: props.settings.deadLetterQueue.name;
|
|
408
|
-
updatePayload.
|
|
408
|
+
updatePayload.dead_letter_queue = dlqName;
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
|
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
@@ -551,6 +568,11 @@ export async function prepareWorkerMetadata(
|
|
|
551
568
|
name: bindingName,
|
|
552
569
|
namespace: binding.namespaceName,
|
|
553
570
|
});
|
|
571
|
+
} else if (binding.type === "worker_loader") {
|
|
572
|
+
meta.bindings.push({
|
|
573
|
+
type: "worker_loader",
|
|
574
|
+
name: bindingName,
|
|
575
|
+
});
|
|
554
576
|
} else if (binding.type === "secret_key") {
|
|
555
577
|
meta.bindings.push({
|
|
556
578
|
type: "secret_key",
|
|
@@ -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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -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
|
-
*
|
|
170
|
+
* Specify the observability behavior of the Worker.
|
|
170
171
|
*
|
|
171
|
-
*
|
|
172
|
-
* @default
|
|
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 (
|
|
940
|
+
if (this.output?.dev?.hasRemote !== false) {
|
|
867
941
|
const api = await createCloudflareApi(props);
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
}
|
|
@@ -901,9 +982,11 @@ const _Worker = Resource(
|
|
|
901
982
|
bundle,
|
|
902
983
|
port: props.dev?.port,
|
|
903
984
|
tunnel: props.dev?.tunnel ?? this.scope.tunnel,
|
|
985
|
+
cwd: props.cwd ?? process.cwd(),
|
|
904
986
|
});
|
|
905
987
|
this.onCleanup(() => controller.dispose());
|
|
906
988
|
}
|
|
989
|
+
|
|
907
990
|
await provisionResources(
|
|
908
991
|
{
|
|
909
992
|
...props,
|
|
@@ -1159,12 +1242,18 @@ async function provisionResources<B extends Bindings>(
|
|
|
1159
1242
|
containers: options.containers,
|
|
1160
1243
|
domains: props.domains?.map((domain) => {
|
|
1161
1244
|
if (typeof domain === "string") {
|
|
1245
|
+
if (domain === "") {
|
|
1246
|
+
throw new Error("Domain names cannot be empty strings");
|
|
1247
|
+
}
|
|
1162
1248
|
return {
|
|
1163
1249
|
name: domain,
|
|
1164
1250
|
zoneId: undefined,
|
|
1165
1251
|
adopt: props.adopt,
|
|
1166
1252
|
};
|
|
1167
1253
|
}
|
|
1254
|
+
if (domain.domainName === "") {
|
|
1255
|
+
throw new Error("Domain names cannot be empty strings");
|
|
1256
|
+
}
|
|
1168
1257
|
return {
|
|
1169
1258
|
name: domain.domainName,
|
|
1170
1259
|
zoneId: domain.zoneId,
|
|
@@ -1607,7 +1696,7 @@ async function assertWorkerDoesNotExist(
|
|
|
1607
1696
|
);
|
|
1608
1697
|
}
|
|
1609
1698
|
|
|
1610
|
-
async function getScriptMetadata(
|
|
1699
|
+
export async function getScriptMetadata(
|
|
1611
1700
|
api: CloudflareApi,
|
|
1612
1701
|
scriptName: string,
|
|
1613
1702
|
): Promise<WorkerScriptMetadata | undefined> {
|
|
@@ -376,6 +376,10 @@ export interface WranglerJsonSpec {
|
|
|
376
376
|
entrypoint?: string;
|
|
377
377
|
}[];
|
|
378
378
|
|
|
379
|
+
worker_loaders?: {
|
|
380
|
+
binding: string;
|
|
381
|
+
}[];
|
|
382
|
+
|
|
379
383
|
/**
|
|
380
384
|
* Workflow bindings
|
|
381
385
|
*/
|
|
@@ -627,6 +631,9 @@ function processBindings(
|
|
|
627
631
|
const containers: {
|
|
628
632
|
class_name: string;
|
|
629
633
|
}[] = [];
|
|
634
|
+
const workerLoaders: {
|
|
635
|
+
binding: string;
|
|
636
|
+
}[] = [];
|
|
630
637
|
|
|
631
638
|
for (const eventSource of eventSources ?? []) {
|
|
632
639
|
if (isQueueEventSource(eventSource)) {
|
|
@@ -850,6 +857,10 @@ function processBindings(
|
|
|
850
857
|
containers.push({
|
|
851
858
|
class_name: binding.className,
|
|
852
859
|
});
|
|
860
|
+
} else if (binding.type === "worker_loader") {
|
|
861
|
+
workerLoaders.push({
|
|
862
|
+
binding: bindingName,
|
|
863
|
+
});
|
|
853
864
|
} else {
|
|
854
865
|
console.log("binding", binding);
|
|
855
866
|
// biome-ignore lint/correctness/noVoidTypeReturn: it returns never
|
|
@@ -930,4 +941,8 @@ function processBindings(
|
|
|
930
941
|
bindings: unsafeBindings,
|
|
931
942
|
};
|
|
932
943
|
}
|
|
944
|
+
|
|
945
|
+
if (workerLoaders.length > 0) {
|
|
946
|
+
spec.worker_loaders = workerLoaders;
|
|
947
|
+
}
|
|
933
948
|
}
|
package/src/github/comment.ts
CHANGED
|
@@ -173,17 +173,17 @@ export const GitHubComment = Resource(
|
|
|
173
173
|
props: GitHubCommentProps,
|
|
174
174
|
): Promise<GitHubComment> {
|
|
175
175
|
// Create authenticated Octokit client - will automatically handle token resolution
|
|
176
|
-
const octokit = await createGitHubClient({
|
|
177
|
-
token: props.token?.unencrypted,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Verify authentication and permissions
|
|
181
|
-
if (!this.quiet) {
|
|
182
|
-
await verifyGitHubAuth(octokit, props.owner, props.repository);
|
|
183
|
-
}
|
|
184
176
|
|
|
185
177
|
if (this.phase === "delete") {
|
|
186
178
|
if (this.output?.commentId && props.allowDelete) {
|
|
179
|
+
const octokit = await createGitHubClient({
|
|
180
|
+
token: props.token?.unencrypted,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Verify authentication and permissions
|
|
184
|
+
if (!this.quiet) {
|
|
185
|
+
await verifyGitHubAuth(octokit, props.owner, props.repository);
|
|
186
|
+
}
|
|
187
187
|
try {
|
|
188
188
|
// Delete the comment
|
|
189
189
|
await octokit.rest.issues.deleteComment({
|
|
@@ -209,6 +209,15 @@ export const GitHubComment = Resource(
|
|
|
209
209
|
return this.destroy();
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
const octokit = await createGitHubClient({
|
|
213
|
+
token: props.token?.unencrypted,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Verify authentication and permissions
|
|
217
|
+
if (!this.quiet) {
|
|
218
|
+
await verifyGitHubAuth(octokit, props.owner, props.repository);
|
|
219
|
+
}
|
|
220
|
+
|
|
212
221
|
try {
|
|
213
222
|
if (this.phase === "update" && this.output?.commentId) {
|
|
214
223
|
// Update existing comment
|
package/src/scope.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { logger } from "./util/logger.ts";
|
|
|
30
30
|
import { AsyncMutex } from "./util/mutex.ts";
|
|
31
31
|
import { ALCHEMY_ROOT } from "./util/root-dir.ts";
|
|
32
32
|
import { createAndSendEvent } from "./util/telemetry.ts";
|
|
33
|
+
import { validateResourceID } from "./util/validate-resource-id.ts";
|
|
33
34
|
|
|
34
35
|
export class RootScopeStateAttemptError extends Error {
|
|
35
36
|
constructor() {
|
|
@@ -265,21 +266,17 @@ export class Scope {
|
|
|
265
266
|
// Store provider credentials (TypeScript ensures no conflicts with core options)
|
|
266
267
|
this.providerCredentials = providerCredentials as ProviderCredentials;
|
|
267
268
|
|
|
268
|
-
const isChild = this.parent !== undefined;
|
|
269
|
-
if (this.scopeName?.includes(":") && isChild) {
|
|
270
|
-
// TODO(sam): relax this constraint once we move to SQLite3 store
|
|
271
|
-
throw new Error(
|
|
272
|
-
`Scope name "${this.scopeName}" cannot contain double colons`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
269
|
this.stage = stage ?? this.parent?.stage ?? DEFAULT_STAGE;
|
|
277
270
|
this.profile = profile ?? this.parent?.profile;
|
|
278
271
|
this.parent?.children.set(this.scopeName!, this);
|
|
279
272
|
this.quiet = quiet ?? this.parent?.quiet ?? false;
|
|
280
|
-
if (this.parent
|
|
281
|
-
|
|
273
|
+
if (this.parent) {
|
|
274
|
+
if (!this.scopeName) {
|
|
275
|
+
throw new Error("Scope name is required when creating a child scope");
|
|
276
|
+
}
|
|
277
|
+
validateResourceID(this.scopeName, "Scope");
|
|
282
278
|
}
|
|
279
|
+
|
|
283
280
|
this.password = password ?? this.parent?.password;
|
|
284
281
|
this.noTrack = noTrack ?? this.parent?.noTrack ?? false;
|
|
285
282
|
const resolvedPhase = phase ?? this.parent?.phase;
|
|
@@ -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;
|
package/src/util/retry.ts
CHANGED
|
@@ -14,6 +14,7 @@ export async function withExponentialBackoff<T>(
|
|
|
14
14
|
isRetryable: (error: any) => any,
|
|
15
15
|
maxAttempts = 5,
|
|
16
16
|
initialDelayMs = 100,
|
|
17
|
+
maxDelayMs = 10000,
|
|
17
18
|
): Promise<T> {
|
|
18
19
|
let attempt = 0;
|
|
19
20
|
let delay = initialDelayMs;
|
|
@@ -31,7 +32,7 @@ export async function withExponentialBackoff<T>(
|
|
|
31
32
|
const jitter = Math.random() * 0.1 * delay;
|
|
32
33
|
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
33
34
|
delay *= 2; // Double the delay for next attempt
|
|
34
|
-
delay = Math.min(delay,
|
|
35
|
+
delay = Math.min(delay, maxDelayMs); // Cap at 10 seconds
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function validateResourceID(id: string, qualifier = "Resource"): void {
|
|
2
|
+
//todo(sam): during effect rewrite, this should be much more limiting
|
|
3
|
+
//todo: its hard to undo the flexibility we've offered to users now
|
|
4
|
+
if (!id) {
|
|
5
|
+
throw new Error(`${qualifier} ID cannot be an empty string: "${id}"`);
|
|
6
|
+
}
|
|
7
|
+
if (id.includes(":")) {
|
|
8
|
+
throw new Error(`${qualifier} ID cannot include colons: "${id}"`);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/workers/tunnel-proxy.js
CHANGED
|
@@ -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.
|
|
37
|
+
<p class="text-sm text-slate-500">Alchemy 0.71.0</p>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
</body>
|