alchemy 2.0.0-beta.53 → 2.0.0-beta.55
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/exec.js +1 -1
- package/bin/exec.js.map +1 -1
- package/lib/Cloudflare/AiGateway/AiGatewaySpendingLimit.d.ts +165 -0
- package/lib/Cloudflare/AiGateway/AiGatewaySpendingLimit.d.ts.map +1 -0
- package/lib/Cloudflare/AiGateway/AiGatewaySpendingLimit.js +232 -0
- package/lib/Cloudflare/AiGateway/AiGatewaySpendingLimit.js.map +1 -0
- package/lib/Cloudflare/AiGateway/index.d.ts +1 -0
- package/lib/Cloudflare/AiGateway/index.d.ts.map +1 -1
- package/lib/Cloudflare/AiGateway/index.js +1 -0
- package/lib/Cloudflare/AiGateway/index.js.map +1 -1
- package/lib/Cloudflare/Bridge.d.ts +1 -0
- package/lib/Cloudflare/Bridge.d.ts.map +1 -1
- package/lib/Cloudflare/Bridge.js +1 -0
- package/lib/Cloudflare/Bridge.js.map +1 -1
- package/lib/Cloudflare/Fetcher.d.ts +2 -2
- package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
- package/lib/Cloudflare/Fetcher.js.map +1 -1
- package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts.map +1 -1
- package/lib/Cloudflare/Hyperdrive/Hyperdrive.js +6 -6
- package/lib/Cloudflare/Hyperdrive/Hyperdrive.js.map +1 -1
- package/lib/Cloudflare/Local.js +5 -2
- package/lib/Cloudflare/Local.js.map +1 -1
- package/lib/Cloudflare/LocalRuntime.d.ts +21 -0
- package/lib/Cloudflare/LocalRuntime.d.ts.map +1 -0
- package/lib/Cloudflare/LocalRuntime.js +40 -0
- package/lib/Cloudflare/LocalRuntime.js.map +1 -0
- package/lib/Cloudflare/Providers.d.ts +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +3 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/Queue/Queue.d.ts +4 -1
- package/lib/Cloudflare/Queue/Queue.d.ts.map +1 -1
- package/lib/Cloudflare/Queue/Queue.js +61 -2
- package/lib/Cloudflare/Queue/Queue.js.map +1 -1
- package/lib/Cloudflare/Queue/QueueConsumer.d.ts +32 -26
- package/lib/Cloudflare/Queue/QueueConsumer.d.ts.map +1 -1
- package/lib/Cloudflare/Queue/QueueConsumer.js +73 -3
- package/lib/Cloudflare/Queue/QueueConsumer.js.map +1 -1
- package/lib/Cloudflare/Workers/Assets.js +2 -2
- package/lib/Cloudflare/Workers/Assets.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +3 -5
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +32 -25
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +2 -4
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +5 -7
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Local/ProviderLayer.d.ts +10 -0
- package/lib/Local/ProviderLayer.d.ts.map +1 -0
- package/lib/Local/ProviderLayer.js +7 -0
- package/lib/Local/ProviderLayer.js.map +1 -0
- package/lib/Local/RpcProvider.d.ts +4 -3
- package/lib/Local/RpcProvider.d.ts.map +1 -1
- package/lib/Local/RpcProvider.js +6 -2
- package/lib/Local/RpcProvider.js.map +1 -1
- package/lib/Local/RpcProviderProxy.js.map +1 -1
- package/lib/Local/RpcSerialization.js +2 -2
- package/lib/Local/RpcSerialization.js.map +1 -1
- package/lib/Planetscale/Branch.d.ts.map +1 -1
- package/lib/Planetscale/Branch.js +25 -3
- package/lib/Planetscale/Branch.js.map +1 -1
- package/lib/Planetscale/Database.d.ts +1 -1
- package/lib/Planetscale/Postgres/PostgresClusterSize.d.ts +1 -1
- package/lib/Planetscale/Postgres/PostgresClusterSize.d.ts.map +1 -1
- package/lib/Planetscale/Postgres/PostgresClusterSize.js +13 -3
- package/lib/Planetscale/Postgres/PostgresClusterSize.js.map +1 -1
- package/lib/Planetscale/Util.d.ts +1 -1
- package/lib/Planetscale/Util.d.ts.map +1 -1
- package/lib/Planetscale/Util.js +7 -5
- package/lib/Planetscale/Util.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/Cloudflare/AiGateway/AiGatewaySpendingLimit.ts +368 -0
- package/src/Cloudflare/AiGateway/index.ts +1 -0
- package/src/Cloudflare/Bridge.ts +1 -0
- package/src/Cloudflare/Fetcher.ts +8 -4
- package/src/Cloudflare/Hyperdrive/Hyperdrive.ts +6 -8
- package/src/Cloudflare/Local.ts +9 -5
- package/src/Cloudflare/LocalRuntime.ts +70 -0
- package/src/Cloudflare/Providers.ts +4 -0
- package/src/Cloudflare/Queue/Queue.ts +73 -2
- package/src/Cloudflare/Queue/QueueConsumer.ts +130 -28
- package/src/Cloudflare/Workers/Assets.ts +2 -2
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +39 -30
- package/src/Cloudflare/Workers/Worker.ts +5 -26
- package/src/Local/ProviderLayer.ts +22 -0
- package/src/Local/RpcProvider.ts +11 -5
- package/src/Local/RpcProviderProxy.ts +2 -2
- package/src/Local/RpcSerialization.ts +2 -2
- package/src/Planetscale/Branch.ts +38 -10
- package/src/Planetscale/Database.ts +1 -1
- package/src/Planetscale/Postgres/PostgresClusterSize.ts +16 -2
- package/src/Planetscale/Util.ts +7 -5
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import * as queues from "@distilled.cloud/cloudflare/queues";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
3
4
|
import * as Option from "effect/Option";
|
|
4
5
|
import * as Stream from "effect/Stream";
|
|
5
6
|
import { isResolved } from "../../Diff.ts";
|
|
7
|
+
import * as ProviderLayer from "../../Local/ProviderLayer.ts";
|
|
8
|
+
import * as RpcProvider from "../../Local/RpcProvider.ts";
|
|
6
9
|
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
7
10
|
import * as Provider from "../../Provider.ts";
|
|
8
11
|
import { Resource } from "../../Resource.ts";
|
|
9
12
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
13
|
+
import {
|
|
14
|
+
generateLocalId,
|
|
15
|
+
isLiveId,
|
|
16
|
+
LOCAL_ENTRY_URL,
|
|
17
|
+
LocalRuntimeState,
|
|
18
|
+
} from "../LocalRuntime.ts";
|
|
10
19
|
import type { Providers } from "../Providers.ts";
|
|
11
20
|
import { QueueBinding } from "./QueueBinding.ts";
|
|
12
21
|
|
|
@@ -96,12 +105,18 @@ export const Queue = Resource<Queue>("Cloudflare.Queue")({
|
|
|
96
105
|
bind: QueueBinding.bind,
|
|
97
106
|
});
|
|
98
107
|
|
|
99
|
-
export const
|
|
108
|
+
export const QueueProviderLive = () =>
|
|
100
109
|
Provider.succeed(Queue, {
|
|
101
|
-
|
|
110
|
+
// The `queueId` is not marked as stable because if you start in dev mode, the ID will change on first deploy.
|
|
111
|
+
stables: ["accountId"],
|
|
102
112
|
diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
|
|
103
113
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
104
114
|
if (!isResolved(news)) return undefined;
|
|
115
|
+
// If the queueId is a `dev:` ID, we need to update to a live one.
|
|
116
|
+
// The live resource doesn't exist yet, so there's no need to replace even if the name or accountId changed.
|
|
117
|
+
if (!isLiveId(output?.queueId)) {
|
|
118
|
+
return { action: "update" };
|
|
119
|
+
}
|
|
105
120
|
if ((output?.accountId ?? accountId) !== accountId) {
|
|
106
121
|
return { action: "replace" } as const;
|
|
107
122
|
}
|
|
@@ -171,6 +186,8 @@ export const QueueProvider = () =>
|
|
|
171
186
|
};
|
|
172
187
|
}),
|
|
173
188
|
delete: Effect.fn(function* ({ output }) {
|
|
189
|
+
// If the queueId is a `dev:` ID, the resource only exists locally, so we don't need to delete it from Cloudflare.
|
|
190
|
+
if (!isLiveId(output.queueId)) return;
|
|
174
191
|
yield* queues
|
|
175
192
|
.deleteQueue({
|
|
176
193
|
accountId: output.accountId,
|
|
@@ -228,3 +245,57 @@ const findQueueByName = Effect.fnUntraced(function* (queueName: string) {
|
|
|
228
245
|
Effect.map(Option.getOrUndefined),
|
|
229
246
|
);
|
|
230
247
|
});
|
|
248
|
+
|
|
249
|
+
export const QueueProviderLocal = () =>
|
|
250
|
+
RpcProvider.effect(
|
|
251
|
+
Queue,
|
|
252
|
+
LOCAL_ENTRY_URL,
|
|
253
|
+
Effect.gen(function* () {
|
|
254
|
+
const localRuntimeState = yield* LocalRuntimeState;
|
|
255
|
+
return {
|
|
256
|
+
stables: ["accountId"],
|
|
257
|
+
diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
|
|
258
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
259
|
+
if (!output?.queueId) return { action: "update" };
|
|
260
|
+
if (!isResolved(news)) return undefined;
|
|
261
|
+
const name = yield* createQueueName(id, news.name);
|
|
262
|
+
const oldName = output?.queueName
|
|
263
|
+
? yield* createQueueName(id, olds.name)
|
|
264
|
+
: yield* createQueueName(id, olds.name);
|
|
265
|
+
if (name !== oldName || output.accountId !== accountId) {
|
|
266
|
+
return { action: "replace" };
|
|
267
|
+
}
|
|
268
|
+
// If the resource is a noop, add it to the local runtime state so it's available downstream.
|
|
269
|
+
// We do it here instead of in the reconcile function so it doesn't appear as an update.
|
|
270
|
+
MutableHashMap.set(localRuntimeState.queues, output.queueId, output);
|
|
271
|
+
return { action: "noop" };
|
|
272
|
+
}),
|
|
273
|
+
read: Effect.fn(function* ({ output }) {
|
|
274
|
+
if (!output?.queueId) return undefined;
|
|
275
|
+
return MutableHashMap.get(
|
|
276
|
+
localRuntimeState.queues,
|
|
277
|
+
output.queueId,
|
|
278
|
+
).pipe(Option.getOrUndefined);
|
|
279
|
+
}),
|
|
280
|
+
reconcile: Effect.fn(function* ({ id, news = {}, output }) {
|
|
281
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
282
|
+
const queue: Queue["Attributes"] = {
|
|
283
|
+
queueId: output?.queueId ?? generateLocalId(),
|
|
284
|
+
queueName: yield* createQueueName(id, news.name),
|
|
285
|
+
accountId: output?.accountId ?? accountId,
|
|
286
|
+
};
|
|
287
|
+
MutableHashMap.set(localRuntimeState.queues, queue.queueId, queue);
|
|
288
|
+
return queue;
|
|
289
|
+
}),
|
|
290
|
+
delete: Effect.fn(function* ({ output }) {
|
|
291
|
+
MutableHashMap.remove(localRuntimeState.queues, output.queueId);
|
|
292
|
+
}),
|
|
293
|
+
};
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
export const QueueProvider = () =>
|
|
298
|
+
ProviderLayer.select({
|
|
299
|
+
local: () => QueueProviderLocal(),
|
|
300
|
+
live: () => QueueProviderLive(),
|
|
301
|
+
});
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import * as queues from "@distilled.cloud/cloudflare/queues";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
3
4
|
import * as Option from "effect/Option";
|
|
4
5
|
import * as Schedule from "effect/Schedule";
|
|
5
6
|
import * as Stream from "effect/Stream";
|
|
6
7
|
import { isResolved } from "../../Diff.ts";
|
|
8
|
+
import * as ProviderLayer from "../../Local/ProviderLayer.ts";
|
|
9
|
+
import * as RpcProvider from "../../Local/RpcProvider.ts";
|
|
7
10
|
import * as Provider from "../../Provider.ts";
|
|
8
11
|
import { Resource } from "../../Resource.ts";
|
|
9
12
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
13
|
+
import {
|
|
14
|
+
isLiveId,
|
|
15
|
+
LOCAL_ENTRY_URL,
|
|
16
|
+
LocalRuntimeState,
|
|
17
|
+
} from "../LocalRuntime.ts";
|
|
10
18
|
import type { Providers } from "../Providers.ts";
|
|
11
19
|
|
|
12
20
|
export type QueueConsumerProps = {
|
|
@@ -25,33 +33,35 @@ export type QueueConsumerProps = {
|
|
|
25
33
|
/**
|
|
26
34
|
* Consumer settings.
|
|
27
35
|
*/
|
|
28
|
-
settings?:
|
|
29
|
-
/**
|
|
30
|
-
* The maximum number of messages per batch.
|
|
31
|
-
* @default 10
|
|
32
|
-
*/
|
|
33
|
-
batchSize?: number;
|
|
34
|
-
/**
|
|
35
|
-
* The maximum number of concurrent consumer invocations.
|
|
36
|
-
*/
|
|
37
|
-
maxConcurrency?: number;
|
|
38
|
-
/**
|
|
39
|
-
* The maximum number of retries for a message.
|
|
40
|
-
* @default 3
|
|
41
|
-
*/
|
|
42
|
-
maxRetries?: number;
|
|
43
|
-
/**
|
|
44
|
-
* The maximum time to wait for a batch to fill, in milliseconds.
|
|
45
|
-
* @default 5000
|
|
46
|
-
*/
|
|
47
|
-
maxWaitTimeMs?: number;
|
|
48
|
-
/**
|
|
49
|
-
* The number of seconds to wait before retrying a message.
|
|
50
|
-
*/
|
|
51
|
-
retryDelay?: number;
|
|
52
|
-
};
|
|
36
|
+
settings?: QueueConsumerSettings;
|
|
53
37
|
};
|
|
54
38
|
|
|
39
|
+
export interface QueueConsumerSettings {
|
|
40
|
+
/**
|
|
41
|
+
* The maximum number of messages per batch.
|
|
42
|
+
* @default 10
|
|
43
|
+
*/
|
|
44
|
+
batchSize?: number;
|
|
45
|
+
/**
|
|
46
|
+
* The maximum number of concurrent consumer invocations.
|
|
47
|
+
*/
|
|
48
|
+
maxConcurrency?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The maximum number of retries for a message.
|
|
51
|
+
* @default 3
|
|
52
|
+
*/
|
|
53
|
+
maxRetries?: number;
|
|
54
|
+
/**
|
|
55
|
+
* The maximum time to wait for a batch to fill, in milliseconds.
|
|
56
|
+
* @default 5000
|
|
57
|
+
*/
|
|
58
|
+
maxWaitTimeMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* The number of seconds to wait before retrying a message.
|
|
61
|
+
*/
|
|
62
|
+
retryDelay?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export type QueueConsumer = Resource<
|
|
56
66
|
"Cloudflare.QueueConsumer",
|
|
57
67
|
QueueConsumerProps,
|
|
@@ -60,6 +70,8 @@ export type QueueConsumer = Resource<
|
|
|
60
70
|
queueId: string;
|
|
61
71
|
scriptName: string;
|
|
62
72
|
accountId: string;
|
|
73
|
+
deadLetterQueue?: string;
|
|
74
|
+
settings?: QueueConsumerSettings;
|
|
63
75
|
},
|
|
64
76
|
never,
|
|
65
77
|
Providers
|
|
@@ -116,12 +128,19 @@ const findWorkerConsumer = (acct: string, queueId: string) =>
|
|
|
116
128
|
Stream.runHead,
|
|
117
129
|
Effect.map(Option.getOrUndefined),
|
|
118
130
|
);
|
|
119
|
-
|
|
131
|
+
|
|
132
|
+
export const QueueConsumerProviderLive = () =>
|
|
120
133
|
Provider.succeed(QueueConsumer, {
|
|
121
|
-
|
|
134
|
+
// The `consumerId` is not marked as stable because if you start in dev mode, the ID will change on first deploy.
|
|
135
|
+
stables: ["accountId"],
|
|
122
136
|
diff: Effect.fn(function* ({ olds, news, output }) {
|
|
123
137
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
124
138
|
if (!isResolved(news)) return undefined;
|
|
139
|
+
// If either contain `dev:` IDs, we need to update to live ones.
|
|
140
|
+
// The live resource doesn't exist yet, so there's no need to replace even when we otherwise would.
|
|
141
|
+
if (!isLiveId(output?.queueId) || !isLiveId(output?.consumerId)) {
|
|
142
|
+
return { action: "update" };
|
|
143
|
+
}
|
|
125
144
|
if ((output?.accountId ?? accountId) !== accountId) {
|
|
126
145
|
return { action: "replace" } as const;
|
|
127
146
|
}
|
|
@@ -167,7 +186,7 @@ export const QueueConsumerProvider = () =>
|
|
|
167
186
|
// updating it could clobber another team's wiring.
|
|
168
187
|
let observed: ObservedConsumer | undefined;
|
|
169
188
|
let owned = false;
|
|
170
|
-
if (output?.consumerId) {
|
|
189
|
+
if (isLiveId(output?.consumerId)) {
|
|
171
190
|
const fetched = yield* queues
|
|
172
191
|
.getConsumer({
|
|
173
192
|
accountId: acct,
|
|
@@ -365,9 +384,14 @@ export const QueueConsumerProvider = () =>
|
|
|
365
384
|
queueId,
|
|
366
385
|
scriptName: news.scriptName!,
|
|
367
386
|
accountId: acct,
|
|
387
|
+
deadLetterQueue: news.deadLetterQueue,
|
|
388
|
+
settings: news.settings,
|
|
368
389
|
};
|
|
369
390
|
}),
|
|
370
391
|
delete: Effect.fn(function* ({ output }) {
|
|
392
|
+
// If the consumerId is a `dev:` ID, the resource only exists locally, so we don't need to delete it from Cloudflare.
|
|
393
|
+
if (!isLiveId(output.consumerId)) return;
|
|
394
|
+
|
|
371
395
|
yield* queues
|
|
372
396
|
.deleteConsumer({
|
|
373
397
|
accountId: output.accountId,
|
|
@@ -421,6 +445,8 @@ export const QueueConsumerProvider = () =>
|
|
|
421
445
|
? fetched.scriptName
|
|
422
446
|
: output.scriptName) ?? output.scriptName,
|
|
423
447
|
accountId: output.accountId,
|
|
448
|
+
deadLetterQueue: output.deadLetterQueue,
|
|
449
|
+
settings: output.settings,
|
|
424
450
|
};
|
|
425
451
|
}
|
|
426
452
|
}
|
|
@@ -439,6 +465,8 @@ export const QueueConsumerProvider = () =>
|
|
|
439
465
|
queueId: output.queueId,
|
|
440
466
|
scriptName: match.script ?? output.scriptName,
|
|
441
467
|
accountId: output.accountId,
|
|
468
|
+
deadLetterQueue: output.deadLetterQueue,
|
|
469
|
+
settings: output.settings,
|
|
442
470
|
};
|
|
443
471
|
}
|
|
444
472
|
}
|
|
@@ -465,3 +493,77 @@ const toObserved = (c: {
|
|
|
465
493
|
const queueHandlerReadinessSchedule = Schedule.spaced("2 seconds").pipe(
|
|
466
494
|
Schedule.both(Schedule.recurs(30)),
|
|
467
495
|
);
|
|
496
|
+
|
|
497
|
+
export const QueueConsumerProviderLocal = () =>
|
|
498
|
+
RpcProvider.effect(
|
|
499
|
+
QueueConsumer,
|
|
500
|
+
LOCAL_ENTRY_URL,
|
|
501
|
+
Effect.gen(function* () {
|
|
502
|
+
const localRuntimeState = yield* LocalRuntimeState;
|
|
503
|
+
return {
|
|
504
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
505
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
506
|
+
if (!output) return { action: "update" };
|
|
507
|
+
if (!isResolved(news)) return undefined;
|
|
508
|
+
if (
|
|
509
|
+
output.queueId !== news.queueId ||
|
|
510
|
+
output.accountId !== accountId
|
|
511
|
+
) {
|
|
512
|
+
return { action: "replace" };
|
|
513
|
+
}
|
|
514
|
+
if (
|
|
515
|
+
JSON.stringify(output.settings ?? {}) !==
|
|
516
|
+
JSON.stringify(news.settings ?? {}) ||
|
|
517
|
+
output.scriptName !== news.scriptName ||
|
|
518
|
+
output.deadLetterQueue !== news.deadLetterQueue
|
|
519
|
+
) {
|
|
520
|
+
return { action: "update" };
|
|
521
|
+
}
|
|
522
|
+
// If the resource is a noop, add it to the local runtime state so it's available downstream.
|
|
523
|
+
// We do it here instead of in the reconcile function so it doesn't appear as an update.
|
|
524
|
+
MutableHashMap.set(
|
|
525
|
+
localRuntimeState.queueConsumers,
|
|
526
|
+
output.consumerId,
|
|
527
|
+
output,
|
|
528
|
+
);
|
|
529
|
+
return { action: "noop" };
|
|
530
|
+
}),
|
|
531
|
+
read: Effect.fn(function* ({ output }) {
|
|
532
|
+
if (!output?.consumerId) return undefined;
|
|
533
|
+
return MutableHashMap.get(
|
|
534
|
+
localRuntimeState.queueConsumers,
|
|
535
|
+
output.consumerId,
|
|
536
|
+
).pipe(Option.getOrUndefined);
|
|
537
|
+
}),
|
|
538
|
+
reconcile: Effect.fn(function* ({ news, output }) {
|
|
539
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
540
|
+
const consumer: QueueConsumer["Attributes"] = {
|
|
541
|
+
consumerId: output?.consumerId ?? `dev:${crypto.randomUUID()}`,
|
|
542
|
+
queueId: news.queueId,
|
|
543
|
+
scriptName: news.scriptName,
|
|
544
|
+
deadLetterQueue: news.deadLetterQueue,
|
|
545
|
+
accountId,
|
|
546
|
+
settings: news.settings,
|
|
547
|
+
};
|
|
548
|
+
MutableHashMap.set(
|
|
549
|
+
localRuntimeState.queueConsumers,
|
|
550
|
+
consumer.consumerId,
|
|
551
|
+
consumer,
|
|
552
|
+
);
|
|
553
|
+
return consumer;
|
|
554
|
+
}),
|
|
555
|
+
delete: Effect.fn(function* ({ output }) {
|
|
556
|
+
MutableHashMap.remove(
|
|
557
|
+
localRuntimeState.queueConsumers,
|
|
558
|
+
output.consumerId,
|
|
559
|
+
);
|
|
560
|
+
}),
|
|
561
|
+
};
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
export const QueueConsumerProvider = () =>
|
|
566
|
+
ProviderLayer.select({
|
|
567
|
+
local: () => QueueConsumerProviderLocal(),
|
|
568
|
+
live: () => QueueConsumerProviderLive(),
|
|
569
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import createIgnore from "@alchemy.run/node-utils/ignore";
|
|
2
2
|
import * as workers from "@distilled.cloud/cloudflare/workers";
|
|
3
3
|
import * as Data from "effect/Data";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
@@ -98,7 +98,7 @@ const maybeReadString = Effect.fnUntraced(function* (file: string) {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
const createIgnoreMatcher = (patterns: string[]) => {
|
|
101
|
-
const matcher =
|
|
101
|
+
const matcher = createIgnore().add(patterns);
|
|
102
102
|
return (file: string) => matcher.ignores(file);
|
|
103
103
|
};
|
|
104
104
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
layerRuntime,
|
|
3
2
|
Runtime,
|
|
4
3
|
RuntimeError,
|
|
5
4
|
type BindingHook,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
type Module,
|
|
9
8
|
type Assets as RuntimeAssets,
|
|
10
9
|
type DurableObjectNamespace as RuntimeDurableObjectNamespace,
|
|
10
|
+
type QueueConsumer as RuntimeQueueConsumer,
|
|
11
11
|
type RuntimeServices,
|
|
12
12
|
} from "@distilled.cloud/cloudflare-runtime";
|
|
13
13
|
import {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
KvNamespace,
|
|
27
27
|
MtlsCertificate,
|
|
28
28
|
Pipelines,
|
|
29
|
+
Queue,
|
|
29
30
|
R2Bucket,
|
|
30
31
|
RateLimit,
|
|
31
32
|
SendEmail,
|
|
@@ -44,19 +45,21 @@ import * as Equal from "effect/Equal";
|
|
|
44
45
|
import * as Exit from "effect/Exit";
|
|
45
46
|
import * as Fiber from "effect/Fiber";
|
|
46
47
|
import * as Hash from "effect/Hash";
|
|
48
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
49
|
+
import * as Option from "effect/Option";
|
|
47
50
|
import * as Path from "effect/Path";
|
|
48
51
|
import * as Redacted from "effect/Redacted";
|
|
49
52
|
import * as Result from "effect/Result";
|
|
50
53
|
import * as Schema from "effect/Schema";
|
|
51
54
|
import * as Scope from "effect/Scope";
|
|
52
55
|
import * as Stream from "effect/Stream";
|
|
53
|
-
import { AlchemyContext } from "../../AlchemyContext.ts";
|
|
54
56
|
import type * as Bundle from "../../Bundle/Bundle.ts";
|
|
55
57
|
import { isResolved } from "../../Diff.ts";
|
|
56
58
|
import * as RpcProvider from "../../Local/RpcProvider.ts";
|
|
57
59
|
import type { ResourceBinding } from "../../Resource.ts";
|
|
58
60
|
import { Stack } from "../../Stack.ts";
|
|
59
61
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
62
|
+
import { LOCAL_ENTRY_URL, LocalRuntimeState } from "../LocalRuntime.ts";
|
|
60
63
|
import type { WorkerAssetsConfig, WorkerProps } from "../Workers/Worker.ts";
|
|
61
64
|
import { getCompatibility } from "./Compatibility.ts";
|
|
62
65
|
import * as Vite from "./Vite.ts";
|
|
@@ -79,41 +82,16 @@ export class WorkerValidationError extends Schema.TaggedErrorClass<WorkerValidat
|
|
|
79
82
|
},
|
|
80
83
|
) {}
|
|
81
84
|
|
|
82
|
-
export const localRuntimeServices = () =>
|
|
83
|
-
RpcProvider.providerServicesEffect(
|
|
84
|
-
Effect.gen(function* () {
|
|
85
|
-
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
86
|
-
const { dotAlchemy } = yield* AlchemyContext;
|
|
87
|
-
const path = yield* Path.Path;
|
|
88
|
-
return layerRuntime({
|
|
89
|
-
api: {
|
|
90
|
-
accountId,
|
|
91
|
-
},
|
|
92
|
-
storage: {
|
|
93
|
-
directory: path.join(dotAlchemy, "local"),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
85
|
export const LocalWorkerProvider = () =>
|
|
100
86
|
RpcProvider.effect(
|
|
101
87
|
Worker,
|
|
102
|
-
|
|
103
|
-
// `import.meta.resolve(<string>)` is a runtime API — TypeScript's
|
|
104
|
-
// `rewriteRelativeImportExtensions` does NOT touch the string literal, so
|
|
105
|
-
// we have to pick the right extension ourselves. `import.meta.url` reflects
|
|
106
|
-
// the actual on-disk extension of *this* file (`.ts` when loaded from
|
|
107
|
-
// `src/` under Bun or vitest, `.js` when loaded from the compiled `lib/`
|
|
108
|
-
// under Node), which is exactly the signal we need.
|
|
109
|
-
import.meta.url.endsWith(".ts") ? "../Local.ts" : "../Local.js",
|
|
110
|
-
import.meta.url,
|
|
111
|
-
),
|
|
88
|
+
LOCAL_ENTRY_URL,
|
|
112
89
|
Effect.gen(function* () {
|
|
113
90
|
const bundler = yield* WorkerBundle;
|
|
114
91
|
const runtime = yield* Runtime;
|
|
115
92
|
const stack = yield* Stack;
|
|
116
93
|
const path = yield* Path.Path;
|
|
94
|
+
const localRuntimeState = yield* LocalRuntimeState;
|
|
117
95
|
const workerProxy = yield* WorkerProxy.WorkerProxy;
|
|
118
96
|
const proxyInstances = new Map<
|
|
119
97
|
string,
|
|
@@ -124,6 +102,32 @@ export const LocalWorkerProvider = () =>
|
|
|
124
102
|
}
|
|
125
103
|
>();
|
|
126
104
|
|
|
105
|
+
const getQueueConsumers = Effect.fnUntraced(function* (
|
|
106
|
+
scriptName: string,
|
|
107
|
+
) {
|
|
108
|
+
const consumers: RuntimeQueueConsumer[] = [];
|
|
109
|
+
for (const consumer of MutableHashMap.values(
|
|
110
|
+
localRuntimeState.queueConsumers,
|
|
111
|
+
)) {
|
|
112
|
+
if (consumer.scriptName === scriptName) {
|
|
113
|
+
const queue = MutableHashMap.get(
|
|
114
|
+
localRuntimeState.queues,
|
|
115
|
+
consumer.queueId,
|
|
116
|
+
).pipe(Option.getOrUndefined);
|
|
117
|
+
if (queue) {
|
|
118
|
+
consumers.push({
|
|
119
|
+
queueName: queue.queueName,
|
|
120
|
+
deadLetterQueue: consumer.deadLetterQueue,
|
|
121
|
+
...consumer.settings,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
return yield* Effect.die(`Queue ${consumer.queueId} not found`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return consumers;
|
|
129
|
+
});
|
|
130
|
+
|
|
127
131
|
const startProxy = Effect.fn(function* (
|
|
128
132
|
id: string,
|
|
129
133
|
serverOptions: WorkerConfig["dev"],
|
|
@@ -211,6 +215,7 @@ export const LocalWorkerProvider = () =>
|
|
|
211
215
|
durableObjectNamespaces: toRuntimeDurableObjectNamespaces(
|
|
212
216
|
worker.durableObjectNamespaces,
|
|
213
217
|
),
|
|
218
|
+
queueConsumers: yield* getQueueConsumers(worker.name),
|
|
214
219
|
modules: yield* toRuntimeModules(bundle),
|
|
215
220
|
assets: toRuntimeAssets(worker.assets),
|
|
216
221
|
})
|
|
@@ -392,6 +397,7 @@ export const LocalWorkerProvider = () =>
|
|
|
392
397
|
worker.durableObjectNamespaces,
|
|
393
398
|
),
|
|
394
399
|
hyperdrives: worker.hyperdrives,
|
|
400
|
+
queueConsumers: yield* getQueueConsumers(worker.name),
|
|
395
401
|
assets: toRuntimeAssets(worker.assets),
|
|
396
402
|
},
|
|
397
403
|
context,
|
|
@@ -586,7 +592,10 @@ const toRuntimeBinding = Effect.fnUntraced(function* (b: WorkerBinding) {
|
|
|
586
592
|
case "plain_text":
|
|
587
593
|
return Text.local(b.name, b.text);
|
|
588
594
|
case "queue":
|
|
589
|
-
return
|
|
595
|
+
return Queue.local({
|
|
596
|
+
binding: b.name,
|
|
597
|
+
queueName: b.queueName,
|
|
598
|
+
});
|
|
590
599
|
case "r2_bucket":
|
|
591
600
|
return R2Bucket.remote(b.name, b.bucketName, b.jurisdiction);
|
|
592
601
|
case "ratelimit":
|
|
@@ -6,20 +6,19 @@ import type { ConfigError } from "effect/Config";
|
|
|
6
6
|
import * as Context from "effect/Context";
|
|
7
7
|
import * as Data from "effect/Data";
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
|
-
import * as Layer from "effect/Layer";
|
|
10
9
|
import * as Path from "effect/Path";
|
|
11
10
|
import * as Predicate from "effect/Predicate";
|
|
12
11
|
import * as Redacted from "effect/Redacted";
|
|
13
12
|
import * as Schedule from "effect/Schedule";
|
|
14
13
|
import * as crypto from "node:crypto";
|
|
15
14
|
import { Unowned } from "../../AdoptPolicy.ts";
|
|
16
|
-
import { AlchemyContext } from "../../AlchemyContext.ts";
|
|
17
15
|
import * as Artifacts from "../../Artifacts.ts";
|
|
18
16
|
import { hashDirectory, type MemoOptions } from "../../Build/Memo.ts";
|
|
19
17
|
import * as Bundle from "../../Bundle/Bundle.ts";
|
|
20
18
|
import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
|
|
21
19
|
import { isResolved } from "../../Diff.ts";
|
|
22
20
|
import type { InputProps } from "../../Input.ts";
|
|
21
|
+
import * as ProviderLayer from "../../Local/ProviderLayer.ts";
|
|
23
22
|
import { Platform, type Main, type PlatformProps } from "../../Platform.ts";
|
|
24
23
|
import * as Provider from "../../Provider.ts";
|
|
25
24
|
import { Resource, type ResourceBinding } from "../../Resource.ts";
|
|
@@ -36,10 +35,7 @@ import {
|
|
|
36
35
|
} from "./Assets.ts";
|
|
37
36
|
import { getCompatibility } from "./Compatibility.ts";
|
|
38
37
|
import { isDurableObjectExport } from "./DurableObjectNamespace.ts";
|
|
39
|
-
import {
|
|
40
|
-
LocalWorkerProvider,
|
|
41
|
-
localRuntimeServices,
|
|
42
|
-
} from "./LocalWorkerProvider.ts";
|
|
38
|
+
import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
|
|
43
39
|
import { Request } from "./Request.ts";
|
|
44
40
|
import * as Vite from "./Vite.ts";
|
|
45
41
|
import {
|
|
@@ -743,27 +739,10 @@ class MissingDurableObjectNamespaces extends Data.TaggedError(
|
|
|
743
739
|
expected: string[];
|
|
744
740
|
}> {}
|
|
745
741
|
|
|
746
|
-
const selectLayer = <
|
|
747
|
-
LayerLive extends Layer.Layer<any, any, any>,
|
|
748
|
-
LayerDev extends Layer.Layer<any, any, any>,
|
|
749
|
-
>(input: {
|
|
750
|
-
live: () => LayerLive;
|
|
751
|
-
dev: () => LayerDev;
|
|
752
|
-
}): Layer.Layer<
|
|
753
|
-
Layer.Success<LayerLive | LayerDev>,
|
|
754
|
-
Layer.Error<LayerLive | LayerDev>,
|
|
755
|
-
Layer.Services<LayerLive | LayerDev> | AlchemyContext
|
|
756
|
-
> =>
|
|
757
|
-
Layer.unwrap(
|
|
758
|
-
AlchemyContext.useSync((context) =>
|
|
759
|
-
context.dev ? input.dev() : input.live(),
|
|
760
|
-
),
|
|
761
|
-
);
|
|
762
|
-
|
|
763
742
|
export const WorkerProvider = () =>
|
|
764
|
-
|
|
765
|
-
live: LiveWorkerProvider,
|
|
766
|
-
|
|
743
|
+
ProviderLayer.select({
|
|
744
|
+
live: () => LiveWorkerProvider(),
|
|
745
|
+
local: () => LocalWorkerProvider(),
|
|
767
746
|
});
|
|
768
747
|
|
|
769
748
|
export const LiveWorkerProvider = () =>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import { AlchemyContext } from "../AlchemyContext.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a layer that selects the live or local provider based on the `AlchemyContext.dev` flag.
|
|
6
|
+
*/
|
|
7
|
+
export const select = <
|
|
8
|
+
LayerLive extends Layer.Layer<any, any, any>,
|
|
9
|
+
LayerLocal extends Layer.Layer<any, any, any>,
|
|
10
|
+
>(input: {
|
|
11
|
+
live: () => LayerLive;
|
|
12
|
+
local: () => LayerLocal;
|
|
13
|
+
}): Layer.Layer<
|
|
14
|
+
Layer.Success<LayerLive | LayerLocal>,
|
|
15
|
+
Layer.Error<LayerLive | LayerLocal>,
|
|
16
|
+
Layer.Services<LayerLive | LayerLocal> | AlchemyContext
|
|
17
|
+
> =>
|
|
18
|
+
Layer.unwrap(
|
|
19
|
+
AlchemyContext.useSync((context) =>
|
|
20
|
+
context.dev ? input.local() : input.live(),
|
|
21
|
+
),
|
|
22
|
+
);
|
package/src/Local/RpcProvider.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as Option from "effect/Option";
|
|
|
5
5
|
import * as Predicate from "effect/Predicate";
|
|
6
6
|
import type { Scope } from "effect/Scope";
|
|
7
7
|
import * as Stream from "effect/Stream";
|
|
8
|
+
import { AlchemyContext } from "../AlchemyContext.ts";
|
|
8
9
|
import { InstanceId } from "../InstanceId.ts";
|
|
9
10
|
import type { Platform } from "../Platform.ts";
|
|
10
11
|
import * as Provider from "../Provider.ts";
|
|
@@ -76,6 +77,7 @@ export const effect = <
|
|
|
76
77
|
cls,
|
|
77
78
|
Effect.gen(function* () {
|
|
78
79
|
const client = yield* Effect.serviceOption(RpcProviderProxy);
|
|
80
|
+
const context = yield* Effect.context();
|
|
79
81
|
const stack = yield* Stack;
|
|
80
82
|
|
|
81
83
|
if (client._tag === "None") {
|
|
@@ -87,6 +89,7 @@ export const effect = <
|
|
|
87
89
|
return (...args: any[]) => {
|
|
88
90
|
const result = value(...args);
|
|
89
91
|
const services = Layer.mergeAll(
|
|
92
|
+
Layer.succeedContext(context),
|
|
90
93
|
layerFallback(Stack, stack),
|
|
91
94
|
layerFallback(Stage, stack.stage),
|
|
92
95
|
Predicate.hasProperty(args[0], "instanceId") &&
|
|
@@ -127,7 +130,8 @@ const layerFallback = <I, S>(
|
|
|
127
130
|
*/
|
|
128
131
|
export const providerServices = <ROut, E, RIn>(
|
|
129
132
|
self: Layer.Layer<ROut, E, RIn>,
|
|
130
|
-
): Layer.Layer<ROut, E, RIn> =>
|
|
133
|
+
): Layer.Layer<ROut, E, RIn | AlchemyContext> =>
|
|
134
|
+
providerServicesEffect(Effect.succeed(self));
|
|
131
135
|
|
|
132
136
|
/**
|
|
133
137
|
* Conditionally constructs a layer for use by an RpcProvider.
|
|
@@ -137,10 +141,12 @@ export const providerServices = <ROut, E, RIn>(
|
|
|
137
141
|
*/
|
|
138
142
|
export const providerServicesEffect = <A, E1, R1, E, R>(
|
|
139
143
|
self: Effect.Effect<Layer.Layer<A, E1, R1>, E, R>,
|
|
140
|
-
): Layer.Layer<A, E | E1, R1 | Exclude<R, Scope
|
|
141
|
-
Effect.serviceOption(RpcProviderProxy).pipe(
|
|
142
|
-
Effect.flatMap((client) =>
|
|
143
|
-
client._tag === "None"
|
|
144
|
+
): Layer.Layer<A, E | E1, R1 | Exclude<R, Scope> | AlchemyContext> =>
|
|
145
|
+
Effect.zip(AlchemyContext, Effect.serviceOption(RpcProviderProxy)).pipe(
|
|
146
|
+
Effect.flatMap(([context, client]) =>
|
|
147
|
+
context.dev && client._tag === "None"
|
|
148
|
+
? self
|
|
149
|
+
: (Effect.succeed(Layer.empty) as never),
|
|
144
150
|
),
|
|
145
151
|
Layer.unwrap,
|
|
146
152
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { newWebSocketRpcSession
|
|
1
|
+
import { newWebSocketRpcSession } from "capnweb";
|
|
2
2
|
import * as Cache from "effect/Cache";
|
|
3
3
|
import * as Config from "effect/Config";
|
|
4
4
|
import * as Context from "effect/Context";
|
|
@@ -42,7 +42,7 @@ const make = Effect.fnUntraced(function* (spawnerUrl: string) {
|
|
|
42
42
|
body: yield* HttpBody.json(payload),
|
|
43
43
|
});
|
|
44
44
|
const websocketUrl = yield* response.text;
|
|
45
|
-
return newWebSocketRpcSession(websocketUrl)
|
|
45
|
+
return newWebSocketRpcSession<RpcProxyApi>(websocketUrl);
|
|
46
46
|
},
|
|
47
47
|
(effect, serverEntryUrl) =>
|
|
48
48
|
Effect.catch(effect, (error) =>
|
|
@@ -64,7 +64,7 @@ export const wrapRpcHandlers = <T extends Record<string, any>>(
|
|
|
64
64
|
? streamKeys?.includes(key)
|
|
65
65
|
? wrapRpcStreamHandler(value)
|
|
66
66
|
: wrapRpcEffectHandler(value)
|
|
67
|
-
: typeof value === "object" && value !== null
|
|
67
|
+
: typeof value === "object" && value !== null && !Array.isArray(value)
|
|
68
68
|
? wrapRpcHandlers(value)
|
|
69
69
|
: value,
|
|
70
70
|
]),
|
|
@@ -82,7 +82,7 @@ export const unwrapRpcHandlers = <T extends Record<string, any>>(
|
|
|
82
82
|
? streamKeys?.includes(key)
|
|
83
83
|
? unwrapRpcStreamHandler(value)
|
|
84
84
|
: unwrapRpcEffectHandler(value)
|
|
85
|
-
: typeof value === "object" && value !== null
|
|
85
|
+
: typeof value === "object" && value !== null && !Array.isArray(value)
|
|
86
86
|
? unwrapRpcHandlers(value)
|
|
87
87
|
: value,
|
|
88
88
|
]),
|