effect-cf 0.20.0 → 0.22.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/CHANGELOG.md +56 -0
- package/README.md +2 -2
- package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
- package/dist/HyperdrivePg.d.mts +13 -5
- package/dist/HyperdrivePg.mjs +12 -7
- package/dist/index.d.mts +708 -549
- package/dist/index.mjs +946 -589
- package/package.json +10 -5
- package/src/AiGateway.ts +82 -47
- package/src/AnalyticsEngine.ts +74 -58
- package/src/Binding.ts +66 -13
- package/src/BrowserRendering.ts +30 -7
- package/src/Cache.ts +32 -8
- package/src/CloudflareOtlp.ts +32 -9
- package/src/ContainerNamespace.ts +87 -55
- package/src/D1.ts +13 -3
- package/src/DurableObject.ts +27 -164
- package/src/DurableObjectAlarm.ts +30 -9
- package/src/DurableObjectDefinition.ts +58 -54
- package/src/DurableObjectNamespace.ts +99 -96
- package/src/DurableObjectRpcWebSocket.ts +14 -6
- package/src/DurableObjectSqlite.ts +6 -3
- package/src/DurableObjectState.ts +6 -9
- package/src/DurableObjectStorage.ts +7 -1
- package/src/DurableObjectWebSocket.ts +51 -41
- package/src/Email.ts +40 -13
- package/src/Environment.ts +26 -6
- package/src/Hyperdrive.ts +2 -2
- package/src/HyperdrivePg.ts +25 -13
- package/src/Images.ts +77 -82
- package/src/Kv.ts +37 -27
- package/src/Queue.ts +6 -1
- package/src/QueueBinding.ts +18 -21
- package/src/QueueDefinition.ts +15 -14
- package/src/R2.ts +107 -29
- package/src/Rpc.ts +3 -1
- package/src/RpcDefinition.ts +177 -133
- package/src/ServiceBinding.ts +112 -108
- package/src/Vectorize.ts +59 -14
- package/src/Worker.ts +205 -198
- package/src/WorkerDefinition.ts +5 -10
- package/src/WorkersAi.ts +27 -17
- package/src/Workflow.ts +54 -49
- package/src/WorkflowBinding.ts +19 -11
- package/src/WorkflowDefinition.ts +24 -27
- package/src/index.ts +4 -3
- package/src/internal/Entrypoint.ts +3 -1
- package/src/internal/ErrorMessage.ts +24 -0
- package/src/internal/RpcInvocation.ts +11 -8
- package/src/internal/Runtime.ts +52 -0
- package/src/internal/WorkerContext.ts +14 -2
- package/src/Entry.ts +0 -259
- package/src/internal/Clock.ts +0 -37
package/src/Environment.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config, ConfigProvider, Context, Effect } from "effect";
|
|
1
|
+
import { Config, ConfigProvider, Context, Effect, type Layer } from "effect";
|
|
2
2
|
|
|
3
3
|
/** Cloudflare worker environment object (`env`). */
|
|
4
4
|
export type WorkerEnv = Cloudflare.Env;
|
|
@@ -72,8 +72,25 @@ export namespace WorkerConfig {
|
|
|
72
72
|
/** Read a scalar Cloudflare var or secret as a boolean. */
|
|
73
73
|
export const boolean = <const Name extends Key>(name: Name) => Config.boolean(name);
|
|
74
74
|
|
|
75
|
-
/**
|
|
76
|
-
export
|
|
75
|
+
/** Options for building a `ConfigProvider` from a Cloudflare worker `env` object. */
|
|
76
|
+
export interface ProviderOptions {
|
|
77
|
+
/**
|
|
78
|
+
* Keep empty-string env values as explicit `""` config values.
|
|
79
|
+
*
|
|
80
|
+
* By default (absent or `false`), empty strings are treated as missing
|
|
81
|
+
* config, matching `ConfigProvider.fromUnknown`.
|
|
82
|
+
*/
|
|
83
|
+
readonly preserveEmptyStrings?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a `ConfigProvider` from a Cloudflare worker `env` object.
|
|
88
|
+
*
|
|
89
|
+
* Empty-string values are treated as missing config by default; pass
|
|
90
|
+
* `{ preserveEmptyStrings: true }` to keep them as explicit values.
|
|
91
|
+
*/
|
|
92
|
+
export const providerFromEnv = (env: WorkerEnv, options?: ProviderOptions) =>
|
|
93
|
+
ConfigProvider.fromUnknown(env, options);
|
|
77
94
|
|
|
78
95
|
/**
|
|
79
96
|
* Build a `ConfigProvider` from the current `WorkerEnvironment` with a custom
|
|
@@ -89,12 +106,15 @@ export namespace WorkerConfig {
|
|
|
89
106
|
* Replace the active Effect `ConfigProvider` with one backed by the current
|
|
90
107
|
* Cloudflare worker `env` object.
|
|
91
108
|
*/
|
|
92
|
-
export const providerLayer =
|
|
109
|
+
export const providerLayer: Layer.Layer<never, never, WorkerEnvironment> =
|
|
110
|
+
ConfigProvider.layer(provider);
|
|
93
111
|
|
|
94
112
|
/** Alias for `providerLayer` for concise use in worker layers. */
|
|
95
|
-
export const layer = providerLayer;
|
|
113
|
+
export const layer: Layer.Layer<never, never, WorkerEnvironment> = providerLayer;
|
|
96
114
|
|
|
97
115
|
/** Build a `ConfigProvider` layer with a custom `env` conversion function. */
|
|
98
|
-
export const layerWith = (
|
|
116
|
+
export const layerWith = (
|
|
117
|
+
makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider,
|
|
118
|
+
): Layer.Layer<never, never, WorkerEnvironment> =>
|
|
99
119
|
ConfigProvider.layer(providerWith(makeProvider));
|
|
100
120
|
}
|
package/src/Hyperdrive.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface HyperdriveDefinition {
|
|
|
13
13
|
|
|
14
14
|
export interface HyperdriveClient {
|
|
15
15
|
readonly connectionString: string;
|
|
16
|
-
readonly
|
|
16
|
+
readonly rawUnsafe: Effect.Effect<Hyperdrive>;
|
|
17
17
|
readonly definition: HyperdriveDefinition;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -60,7 +60,7 @@ export const makeClient =
|
|
|
60
60
|
(hyperdrive: Hyperdrive): HyperdriveClient => ({
|
|
61
61
|
definition,
|
|
62
62
|
connectionString: hyperdrive.connectionString,
|
|
63
|
-
|
|
63
|
+
rawUnsafe: Effect.succeed(hyperdrive),
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
export const layer = <Self>(
|
package/src/HyperdrivePg.ts
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
import { PgClient } from "@effect/sql-pg";
|
|
2
2
|
import { Effect, Layer, Redacted } from "effect";
|
|
3
|
+
import type { SqlClient, SqlError } from "effect/unstable/sql";
|
|
3
4
|
|
|
5
|
+
import type * as Binding from "./Binding";
|
|
6
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
4
7
|
import type * as Hyperdrive from "./Hyperdrive";
|
|
5
8
|
|
|
9
|
+
/** Hyperdrive owns database pooling, so app-side pool and connection options are not exposed. */
|
|
6
10
|
export type PgLayerOptions = Omit<
|
|
7
|
-
PgClient.
|
|
11
|
+
Parameters<typeof PgClient.makeClient>[0],
|
|
8
12
|
"url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream"
|
|
9
13
|
>;
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Provides `PgClient` and `SqlClient` backed by a Hyperdrive binding.
|
|
17
|
+
*
|
|
18
|
+
* Note: since Effect `4.0.0-beta.99`, `PgClient.makeClient` connects eagerly,
|
|
19
|
+
* so the TCP connection to Hyperdrive is opened when this layer is built.
|
|
20
|
+
* In per-request Workers runtimes, build the layer within the request
|
|
21
|
+
* lifecycle rather than sharing it across requests.
|
|
22
|
+
*/
|
|
11
23
|
export const layer = <Self, Id extends string>(
|
|
12
24
|
tag: Hyperdrive.TagClass<Self, Id>,
|
|
13
25
|
binding: Hyperdrive.LayerOptions,
|
|
14
26
|
options?: PgLayerOptions,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
+
): Layer.Layer<
|
|
28
|
+
PgClient.PgClient | SqlClient.SqlClient,
|
|
29
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError | SqlError.SqlError,
|
|
30
|
+
WorkerEnvironment
|
|
31
|
+
> =>
|
|
32
|
+
PgClient.layerFrom(
|
|
33
|
+
Effect.flatMap(tag, (hyperdrive) =>
|
|
34
|
+
PgClient.makeClient({
|
|
35
|
+
...options,
|
|
36
|
+
url: Redacted.make(hyperdrive.connectionString),
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
27
39
|
).pipe(Layer.provide(tag.layer(binding)));
|
package/src/Images.ts
CHANGED
|
@@ -16,12 +16,16 @@ import type {
|
|
|
16
16
|
ImageUploadOptions as CloudflareImageUploadOptions,
|
|
17
17
|
Response as CloudflareResponse,
|
|
18
18
|
} from "@cloudflare/workers-types";
|
|
19
|
-
import { Context, Data, Effect, Option, type Layer } from "effect";
|
|
19
|
+
import { Context, Data, Effect, Function, Option, type Layer } from "effect";
|
|
20
20
|
|
|
21
21
|
import * as Binding from "./Binding";
|
|
22
22
|
import type { WorkerEnvironment } from "./Environment";
|
|
23
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
24
|
+
|
|
25
|
+
const TypeId = "~effect-cf/Images/Steps" as const;
|
|
26
|
+
|
|
27
|
+
export type TypeId = typeof TypeId;
|
|
23
28
|
|
|
24
|
-
const TypeId = "effect-cf/Images/Steps" as const;
|
|
25
29
|
const expectedImagesBinding = "Images binding with info() and input()";
|
|
26
30
|
|
|
27
31
|
/** Error raised when a Cloudflare Images operation fails. */
|
|
@@ -29,7 +33,11 @@ export class ImagesOperationError extends Data.TaggedError("ImagesOperationError
|
|
|
29
33
|
readonly binding: string;
|
|
30
34
|
readonly operation: string;
|
|
31
35
|
readonly cause: unknown;
|
|
32
|
-
}> {
|
|
36
|
+
}> {
|
|
37
|
+
override get message(): string {
|
|
38
|
+
return `Images ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
33
41
|
|
|
34
42
|
/** Typed Cloudflare Images binding definition. */
|
|
35
43
|
export interface ImagesDefinition {
|
|
@@ -64,6 +72,8 @@ export type Step = Data.TaggedEnum<{
|
|
|
64
72
|
readonly Draw: DrawStepOptions;
|
|
65
73
|
}>;
|
|
66
74
|
|
|
75
|
+
export const Step = Data.taggedEnum<Step>();
|
|
76
|
+
|
|
67
77
|
export interface Steps {
|
|
68
78
|
readonly [TypeId]: typeof TypeId;
|
|
69
79
|
readonly steps: ReadonlyArray<Step>;
|
|
@@ -101,7 +111,7 @@ export interface HostedImagesClient {
|
|
|
101
111
|
options?: ImageUploadOptions,
|
|
102
112
|
) => Effect.Effect<ImageMetadata, ImagesOperationError>;
|
|
103
113
|
readonly list: (options?: ImageListOptions) => Effect.Effect<ImageList, ImagesOperationError>;
|
|
104
|
-
readonly
|
|
114
|
+
readonly rawUnsafe: Effect.Effect<CloudflareHostedImagesBinding>;
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
export interface ImagesRuntimeBinding {
|
|
@@ -130,7 +140,7 @@ export interface ImagesClient {
|
|
|
130
140
|
options: ProcessOptions,
|
|
131
141
|
) => Effect.Effect<ImagesTransformationResultClient, ImagesOperationError>;
|
|
132
142
|
readonly hosted: Option.Option<HostedImagesClient>;
|
|
133
|
-
readonly
|
|
143
|
+
readonly rawUnsafe: Effect.Effect<ImagesRuntimeBinding>;
|
|
134
144
|
readonly definition: ImagesDefinition;
|
|
135
145
|
}
|
|
136
146
|
|
|
@@ -170,47 +180,23 @@ const makeSteps = (steps: ReadonlyArray<Step>): Steps => ({
|
|
|
170
180
|
/** Empty Images transformation pipeline. */
|
|
171
181
|
export const empty: Steps = makeSteps([]);
|
|
172
182
|
|
|
173
|
-
export
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
},
|
|
191
|
-
]);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export function draw(draw: DrawStepOptions): (steps: Steps) => Steps;
|
|
195
|
-
export function draw(steps: Steps, draw: DrawStepOptions): Steps;
|
|
196
|
-
export function draw(
|
|
197
|
-
stepsOrDraw: Steps | DrawStepOptions,
|
|
198
|
-
drawValue?: DrawStepOptions,
|
|
199
|
-
): Steps | ((steps: Steps) => Steps) {
|
|
200
|
-
if (drawValue === undefined) {
|
|
201
|
-
return (steps) => draw(steps, stepsOrDraw as DrawStepOptions);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const steps = stepsOrDraw as Steps;
|
|
205
|
-
|
|
206
|
-
return makeSteps([
|
|
207
|
-
...steps.steps,
|
|
208
|
-
{
|
|
209
|
-
_tag: "Draw",
|
|
210
|
-
...drawValue,
|
|
211
|
-
},
|
|
212
|
-
]);
|
|
213
|
-
}
|
|
183
|
+
export const transform: {
|
|
184
|
+
(transform: ImageTransform): (steps: Steps) => Steps;
|
|
185
|
+
(steps: Steps, transform: ImageTransform): Steps;
|
|
186
|
+
} = Function.dual(
|
|
187
|
+
2,
|
|
188
|
+
(steps: Steps, transformValue: ImageTransform): Steps =>
|
|
189
|
+
makeSteps([...steps.steps, Step.Transform({ transform: transformValue })]),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
export const draw: {
|
|
193
|
+
(draw: DrawStepOptions): (steps: Steps) => Steps;
|
|
194
|
+
(steps: Steps, draw: DrawStepOptions): Steps;
|
|
195
|
+
} = Function.dual(
|
|
196
|
+
2,
|
|
197
|
+
(steps: Steps, drawValue: DrawStepOptions): Steps =>
|
|
198
|
+
makeSteps([...steps.steps, Step.Draw(drawValue)]),
|
|
199
|
+
);
|
|
214
200
|
|
|
215
201
|
const imagesError = (binding: string, operation: string, cause: unknown) =>
|
|
216
202
|
new ImagesOperationError({ binding, operation, cause });
|
|
@@ -266,10 +252,20 @@ const wrapResult = (
|
|
|
266
252
|
|
|
267
253
|
const wrapHandle = (binding: string, handle: CloudflareImageHandle): ImageHandleClient => ({
|
|
268
254
|
raw: handle,
|
|
269
|
-
details: tryImagesPromise(binding, "details", () => handle.details()).pipe(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
255
|
+
details: tryImagesPromise(binding, "details", () => handle.details()).pipe(
|
|
256
|
+
Effect.map(maybe),
|
|
257
|
+
Effect.withSpan("Images.details"),
|
|
258
|
+
),
|
|
259
|
+
bytes: tryImagesPromise(binding, "bytes", () => handle.bytes()).pipe(
|
|
260
|
+
Effect.map(maybe),
|
|
261
|
+
Effect.withSpan("Images.bytes"),
|
|
262
|
+
),
|
|
263
|
+
update: Effect.fn("Images.update")((options: ImageUpdateOptions) =>
|
|
264
|
+
tryImagesPromise(binding, "update", () => handle.update(options)),
|
|
265
|
+
),
|
|
266
|
+
delete: tryImagesPromise(binding, "delete", () => handle.delete()).pipe(
|
|
267
|
+
Effect.withSpan("Images.delete"),
|
|
268
|
+
),
|
|
273
269
|
});
|
|
274
270
|
|
|
275
271
|
const wrapHosted = (
|
|
@@ -277,10 +273,13 @@ const wrapHosted = (
|
|
|
277
273
|
hosted: CloudflareHostedImagesBinding,
|
|
278
274
|
): HostedImagesClient => ({
|
|
279
275
|
image: (imageId) => wrapHandle(binding, hosted.image(imageId)),
|
|
280
|
-
upload: (image, options) =>
|
|
276
|
+
upload: Effect.fn("Images.upload")((image: ImageUploadValue, options?: ImageUploadOptions) =>
|
|
281
277
|
tryImagesPromise(binding, "upload", () => hosted.upload(image, options)),
|
|
282
|
-
|
|
283
|
-
|
|
278
|
+
),
|
|
279
|
+
list: Effect.fn("Images.list")((options?: ImageListOptions) =>
|
|
280
|
+
tryImagesPromise(binding, "list", () => hosted.list(options)),
|
|
281
|
+
),
|
|
282
|
+
rawUnsafe: Effect.succeed(hosted),
|
|
284
283
|
});
|
|
285
284
|
|
|
286
285
|
export const makeClient =
|
|
@@ -289,44 +288,40 @@ export const makeClient =
|
|
|
289
288
|
const input = (image: ImageInputValue, options?: ImageInputOptions) =>
|
|
290
289
|
tryImagesSync(definition.binding, "input", () => images.input(image, options));
|
|
291
290
|
|
|
292
|
-
const process = (steps: Steps, options: ProcessOptions)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
transformer
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
transformer.output(options.outputOptions),
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
return wrapResult(definition.binding, result);
|
|
318
|
-
});
|
|
291
|
+
const process = Effect.fn("Images.process")(function* (steps: Steps, options: ProcessOptions) {
|
|
292
|
+
let transformer = yield* input(options.stream, options.inputOptions);
|
|
293
|
+
|
|
294
|
+
for (const step of steps.steps) {
|
|
295
|
+
transformer = yield* Step.$match(step, {
|
|
296
|
+
Draw: (drawStep) =>
|
|
297
|
+
tryImagesSync(definition.binding, "draw", () =>
|
|
298
|
+
transformer.draw(drawStep.image, drawStep.options),
|
|
299
|
+
),
|
|
300
|
+
Transform: (transformStep) =>
|
|
301
|
+
tryImagesSync(definition.binding, "transform", () =>
|
|
302
|
+
transformer.transform(transformStep.transform),
|
|
303
|
+
),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const result = yield* tryImagesPromise(definition.binding, "output", () =>
|
|
308
|
+
transformer.output(options.outputOptions),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
return wrapResult(definition.binding, result);
|
|
312
|
+
});
|
|
319
313
|
|
|
320
314
|
return {
|
|
321
315
|
definition,
|
|
322
|
-
info: (image, options) =>
|
|
316
|
+
info: Effect.fn("Images.info")((image: ImageInputValue, options?: ImageInputOptions) =>
|
|
323
317
|
tryImagesPromise(definition.binding, "info", () => images.info(image, options)),
|
|
318
|
+
),
|
|
324
319
|
input,
|
|
325
320
|
process,
|
|
326
321
|
hosted: isHostedImagesBinding(images.hosted)
|
|
327
322
|
? Option.some(wrapHosted(definition.binding, images.hosted))
|
|
328
323
|
: Option.none(),
|
|
329
|
-
|
|
324
|
+
rawUnsafe: Effect.succeed(images),
|
|
330
325
|
};
|
|
331
326
|
};
|
|
332
327
|
|
package/src/Kv.ts
CHANGED
|
@@ -6,16 +6,24 @@ import { Context, Data, Effect, Option, Schema as S, type Layer } from "effect";
|
|
|
6
6
|
|
|
7
7
|
import * as Binding from "./Binding";
|
|
8
8
|
import type { WorkerEnvironment } from "./Environment";
|
|
9
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
9
10
|
|
|
10
11
|
const expectedKvNamespace =
|
|
11
12
|
"KV namespace binding with get(), put(), delete(), getWithMetadata(), and list()";
|
|
12
13
|
|
|
14
|
+
/** KV operation represented by {@link KvOperationError}. */
|
|
15
|
+
export type KvOperation = "put" | "get" | "getWithMetadata" | "list" | "delete";
|
|
16
|
+
|
|
13
17
|
/** Error raised when a KV operation fails. */
|
|
14
18
|
export class KvOperationError extends Data.TaggedError("KvOperationError")<{
|
|
15
19
|
readonly binding: string;
|
|
16
|
-
readonly operation:
|
|
20
|
+
readonly operation: KvOperation;
|
|
17
21
|
readonly cause: unknown;
|
|
18
|
-
}> {
|
|
22
|
+
}> {
|
|
23
|
+
override get message(): string {
|
|
24
|
+
return `Kv ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
|
|
20
28
|
/** `KVNamespace.put` options. */
|
|
21
29
|
export type KvPutOptions = CloudflareKVNamespacePutOptions;
|
|
@@ -95,8 +103,8 @@ export interface KvClient<Key, Value, EncodedValue> {
|
|
|
95
103
|
readonly list: <Metadata = unknown>(
|
|
96
104
|
options?: KvListOptions<Metadata>,
|
|
97
105
|
) => Effect.Effect<KvListResult<Key, Metadata>, KvOperationError | S.SchemaError>;
|
|
98
|
-
readonly
|
|
99
|
-
readonly
|
|
106
|
+
readonly delete: (key: Key) => Effect.Effect<void, KvOperationError | S.SchemaError>;
|
|
107
|
+
readonly rawUnsafe: Effect.Effect<KVNamespace>;
|
|
100
108
|
readonly definition: KvDefinition<Key, Value, EncodedValue>;
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -123,18 +131,12 @@ export interface TagClass<
|
|
|
123
131
|
>;
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
const
|
|
127
|
-
value === null || value === undefined ? Option.none() : Option.some(value);
|
|
128
|
-
|
|
129
|
-
const maybeNumber = (value: number | undefined): Option.Option<number> =>
|
|
130
|
-
value === undefined ? Option.none() : Option.some(value);
|
|
131
|
-
|
|
132
|
-
const kvError = (binding: string, operation: string, cause: unknown) =>
|
|
134
|
+
const kvError = (binding: string, operation: KvOperation, cause: unknown) =>
|
|
133
135
|
new KvOperationError({ binding, operation, cause });
|
|
134
136
|
|
|
135
137
|
const tryKvPromise = <A>(
|
|
136
138
|
binding: string,
|
|
137
|
-
operation:
|
|
139
|
+
operation: KvOperation,
|
|
138
140
|
evaluate: () => Promise<A>,
|
|
139
141
|
): Effect.Effect<A, KvOperationError> =>
|
|
140
142
|
Effect.tryPromise({
|
|
@@ -163,12 +165,15 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
163
165
|
): ((kv: KVNamespace) => KvClient<Key, Value, EncodedValue>) => {
|
|
164
166
|
const encodeKey = S.encodeEffect(definition.key);
|
|
165
167
|
const decodeKey = S.decodeUnknownEffect(definition.key);
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
+
const jsonValue = S.fromJsonString(S.toCodecJson(definition.value));
|
|
169
|
+
const encodeValue = S.encodeEffect(jsonValue);
|
|
170
|
+
const decodeValue = S.decodeUnknownEffect(jsonValue);
|
|
168
171
|
|
|
169
172
|
return (kv) => ({
|
|
170
173
|
definition,
|
|
171
|
-
put: Effect.
|
|
174
|
+
put: Effect.fn("Kv.put", {
|
|
175
|
+
attributes: { binding: definition.binding, operation: "put" },
|
|
176
|
+
})(function* (key: Key, value: Value, options?: KvPutOptions) {
|
|
172
177
|
const keyEncoded = yield* encodeKey(key);
|
|
173
178
|
const valueEncoded = yield* encodeValue(value);
|
|
174
179
|
|
|
@@ -176,7 +181,9 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
176
181
|
kv.put(keyEncoded, valueEncoded, options),
|
|
177
182
|
);
|
|
178
183
|
}),
|
|
179
|
-
get: Effect.
|
|
184
|
+
get: Effect.fn("Kv.get", {
|
|
185
|
+
attributes: { binding: definition.binding, operation: "get" },
|
|
186
|
+
})(function* (key: Key) {
|
|
180
187
|
const keyEncoded = yield* encodeKey(key);
|
|
181
188
|
const valueEncoded = yield* tryKvPromise(definition.binding, "get", () => kv.get(keyEncoded));
|
|
182
189
|
|
|
@@ -186,10 +193,9 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
186
193
|
|
|
187
194
|
return yield* decodeValue(valueEncoded).pipe(Effect.map(Option.some));
|
|
188
195
|
}),
|
|
189
|
-
getWithMetadata: Effect.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
) {
|
|
196
|
+
getWithMetadata: Effect.fn("Kv.getWithMetadata", {
|
|
197
|
+
attributes: { binding: definition.binding, operation: "getWithMetadata" },
|
|
198
|
+
})(function* <Metadata>(key: Key, metadataSchema: S.Codec<Metadata, unknown>) {
|
|
193
199
|
const keyEncoded = yield* encodeKey(key);
|
|
194
200
|
const result = yield* tryKvPromise(definition.binding, "getWithMetadata", () =>
|
|
195
201
|
kv.getWithMetadata<Metadata>(keyEncoded),
|
|
@@ -208,10 +214,12 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
208
214
|
return Option.some({
|
|
209
215
|
value,
|
|
210
216
|
metadata,
|
|
211
|
-
cacheStatus:
|
|
217
|
+
cacheStatus: Option.fromNullishOr(result.cacheStatus),
|
|
212
218
|
});
|
|
213
219
|
}),
|
|
214
|
-
list: Effect.
|
|
220
|
+
list: Effect.fn("Kv.list", {
|
|
221
|
+
attributes: { binding: definition.binding, operation: "list" },
|
|
222
|
+
})(function* <Metadata = unknown>(options?: KvListOptions<Metadata>) {
|
|
215
223
|
const { metadataSchema, ...kvOptions } = options ?? {};
|
|
216
224
|
const result = yield* tryKvPromise(definition.binding, "list", () =>
|
|
217
225
|
kv.list<Metadata>(kvOptions),
|
|
@@ -229,7 +237,7 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
229
237
|
|
|
230
238
|
keys.push({
|
|
231
239
|
name: decodedName,
|
|
232
|
-
expiration:
|
|
240
|
+
expiration: Option.fromUndefinedOr(key.expiration),
|
|
233
241
|
metadata: decodedMetadata,
|
|
234
242
|
});
|
|
235
243
|
}
|
|
@@ -237,16 +245,18 @@ export const makeClient = <Key, Value, EncodedValue>(
|
|
|
237
245
|
return {
|
|
238
246
|
keys,
|
|
239
247
|
listComplete: result.list_complete,
|
|
240
|
-
cursor:
|
|
241
|
-
cacheStatus:
|
|
248
|
+
cursor: Option.fromNullishOr("cursor" in result ? result.cursor : undefined),
|
|
249
|
+
cacheStatus: Option.fromNullishOr(result.cacheStatus),
|
|
242
250
|
};
|
|
243
251
|
}),
|
|
244
|
-
|
|
252
|
+
delete: Effect.fn("Kv.delete", {
|
|
253
|
+
attributes: { binding: definition.binding, operation: "delete" },
|
|
254
|
+
})(function* (key: Key) {
|
|
245
255
|
const keyEncoded = yield* encodeKey(key);
|
|
246
256
|
|
|
247
257
|
yield* tryKvPromise(definition.binding, "delete", () => kv.delete(keyEncoded));
|
|
248
258
|
}),
|
|
249
|
-
|
|
259
|
+
rawUnsafe: Effect.succeed(kv),
|
|
250
260
|
});
|
|
251
261
|
};
|
|
252
262
|
|
package/src/Queue.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Data, Effect, type Scope } from "effect";
|
|
|
3
3
|
import type { ExecutionContext, WorkerContext } from "./Worker";
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
5
5
|
import * as QueueDefinition from "./QueueDefinition";
|
|
6
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
6
7
|
|
|
7
8
|
export interface QueueMessage<Body> {
|
|
8
9
|
readonly raw: globalThis.Message<unknown>;
|
|
@@ -28,7 +29,11 @@ export class QueueMessageDecodeError extends Data.TaggedError("QueueMessageDecod
|
|
|
28
29
|
readonly messageId: string;
|
|
29
30
|
readonly index: number;
|
|
30
31
|
readonly cause: unknown;
|
|
31
|
-
}> {
|
|
32
|
+
}> {
|
|
33
|
+
override get message(): string {
|
|
34
|
+
return `Queue "${this.queue}" failed to decode message "${this.messageId}" at index ${this.index}: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
32
37
|
|
|
33
38
|
type RuntimeContext<ROut> = ExecutionContext | WorkerContext | WorkerEnvironment | ROut;
|
|
34
39
|
|
package/src/QueueBinding.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface QueueBindingClient<Message extends RpcDefinition.ServiceFreeSch
|
|
|
41
41
|
options?: QueueSendBatchOptions,
|
|
42
42
|
) => Effect.Effect<void, QueueOperationError | S.SchemaError>;
|
|
43
43
|
readonly metrics: () => Effect.Effect<QueueMetrics, QueueOperationError>;
|
|
44
|
-
readonly
|
|
44
|
+
readonly rawUnsafe: Effect.Effect<CloudflareQueue<S.Codec.Encoded<Message>>>;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export class QueueOperationError extends Data.TaggedError("QueueOperationError")<{
|
|
@@ -86,15 +86,16 @@ export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
|
86
86
|
const encodeMessage = S.encodeEffect(definition.message);
|
|
87
87
|
|
|
88
88
|
return (queue) => ({
|
|
89
|
-
send: Effect.
|
|
89
|
+
send: Effect.fn("QueueBinding.send", {
|
|
90
|
+
attributes: { binding: definition.binding, operation: "send" },
|
|
91
|
+
})(function* (message: Body, options?: QueueSendOptions) {
|
|
90
92
|
const encoded = yield* encodeMessage(message);
|
|
91
93
|
|
|
92
94
|
yield* tryQueuePromise(definition.binding, "send", () => queue.send(encoded, options));
|
|
93
95
|
}),
|
|
94
|
-
sendBatch: Effect.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
) {
|
|
96
|
+
sendBatch: Effect.fn("QueueBinding.sendBatch", {
|
|
97
|
+
attributes: { binding: definition.binding, operation: "sendBatch" },
|
|
98
|
+
})(function* (messages: Iterable<MessageSendRequest<Body>>, options?: QueueSendBatchOptions) {
|
|
98
99
|
const encodedMessages: Array<MessageSendRequest<EncodedBody>> = [];
|
|
99
100
|
|
|
100
101
|
for (const message of messages) {
|
|
@@ -106,22 +107,18 @@ export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
|
106
107
|
|
|
107
108
|
const sendBatch = queue.sendBatch;
|
|
108
109
|
|
|
109
|
-
if (sendBatch
|
|
110
|
-
yield*
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
if (sendBatch === undefined) {
|
|
111
|
+
return yield* new QueueOperationError({
|
|
112
|
+
binding: definition.binding,
|
|
113
|
+
operation: "sendBatch",
|
|
114
|
+
cause: new Error(`Queue binding "${definition.binding}" does not provide sendBatch()`),
|
|
115
|
+
message: `Cloudflare queue binding "${definition.binding}" does not provide sendBatch()`,
|
|
116
|
+
});
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
yield* tryQueuePromise(definition.binding, "sendBatch",
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
contentType: message.contentType,
|
|
121
|
-
delaySeconds: message.delaySeconds ?? options?.delaySeconds,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
});
|
|
119
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", () =>
|
|
120
|
+
sendBatch.call(queue, encodedMessages, options),
|
|
121
|
+
);
|
|
125
122
|
}),
|
|
126
123
|
metrics: () => {
|
|
127
124
|
const metrics = queue.metrics;
|
|
@@ -139,7 +136,7 @@ export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
|
139
136
|
|
|
140
137
|
return tryQueuePromise(definition.binding, "metrics", () => metrics.call(queue));
|
|
141
138
|
},
|
|
142
|
-
|
|
139
|
+
rawUnsafe: Effect.succeed(queue as CloudflareQueue<EncodedBody>),
|
|
143
140
|
});
|
|
144
141
|
};
|
|
145
142
|
|