effect-cf 0.20.1 → 0.23.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 +52 -0
- package/README.md +27 -3
- 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 +1113 -922
- package/dist/index.mjs +980 -604
- 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 +251 -80
- 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 +63 -39
- 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 +172 -192
- 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 +39 -17
- package/src/Entry.ts +0 -259
- package/src/internal/Clock.ts +0 -37
package/src/Email.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Context, Data, Effect, type Layer } from "effect";
|
|
|
9
9
|
|
|
10
10
|
import * as Binding from "./Binding";
|
|
11
11
|
import type { WorkerEnvironment } from "./Environment";
|
|
12
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
12
13
|
|
|
13
14
|
const expectedSendEmailBinding = "Send Email binding with send()";
|
|
14
15
|
const textEncoder = new TextEncoder();
|
|
@@ -27,7 +28,14 @@ export const sendLimits = {
|
|
|
27
28
|
maxAllowlistHeaders: 20,
|
|
28
29
|
} as const;
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Error codes reported by Cloudflare Email Sending.
|
|
33
|
+
*
|
|
34
|
+
* Surfaced on {@link EmailOperationError} `code` on a best-effort basis: the
|
|
35
|
+
* runtime attaches them as a non-standard `code` property on thrown errors,
|
|
36
|
+
* which may be missing depending on the runtime or dev-time proxying
|
|
37
|
+
* (miniflare remote bindings serialize errors and can strip it).
|
|
38
|
+
*/
|
|
31
39
|
export const emailErrorCodes = [
|
|
32
40
|
"E_VALIDATION_ERROR",
|
|
33
41
|
"E_FIELD_MISSING",
|
|
@@ -59,9 +67,21 @@ export class EmailOperationError extends Data.TaggedError("EmailOperationError")
|
|
|
59
67
|
readonly binding: string;
|
|
60
68
|
readonly operation: string;
|
|
61
69
|
readonly cause: unknown;
|
|
62
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Cloudflare error code taken from the thrown error, when present.
|
|
72
|
+
*
|
|
73
|
+
* Best-effort: may be `undefined` depending on the runtime or dev-time
|
|
74
|
+
* proxying — miniflare remote bindings (`"remote": true`) serialize thrown
|
|
75
|
+
* errors and can strip the non-standard `code` property.
|
|
76
|
+
*/
|
|
63
77
|
readonly code?: EmailErrorCode | (string & {});
|
|
64
|
-
}> {
|
|
78
|
+
}> {
|
|
79
|
+
override get message(): string {
|
|
80
|
+
const code = this.code === undefined ? "" : ` (${this.code})`;
|
|
81
|
+
|
|
82
|
+
return `Email ${this.operation} failed for binding "${this.binding}"${code}: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
65
85
|
|
|
66
86
|
/** A single message field that violates a documented Email Sending limit. */
|
|
67
87
|
export interface EmailViolation {
|
|
@@ -76,7 +96,11 @@ export class EmailValidationError extends Data.TaggedError("EmailValidationError
|
|
|
76
96
|
readonly binding: string;
|
|
77
97
|
readonly operation: string;
|
|
78
98
|
readonly violations: ReadonlyArray<EmailViolation>;
|
|
79
|
-
}> {
|
|
99
|
+
}> {
|
|
100
|
+
override get message(): string {
|
|
101
|
+
return `Email ${this.operation} for binding "${this.binding}" failed validation: ${ErrorMessage.violationsMessage(this.violations)}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
80
104
|
|
|
81
105
|
/** Typed Cloudflare Send Email binding definition. */
|
|
82
106
|
export interface EmailDefinition {
|
|
@@ -108,7 +132,7 @@ export interface EmailClient {
|
|
|
108
132
|
(message: EmailMessage): Effect.Effect<EmailSendResult, EmailSendError>;
|
|
109
133
|
(builder: EmailMessageBuilder): Effect.Effect<EmailSendResult, EmailSendError>;
|
|
110
134
|
};
|
|
111
|
-
readonly
|
|
135
|
+
readonly rawUnsafe: Effect.Effect<EmailBinding>;
|
|
112
136
|
readonly definition: EmailDefinition;
|
|
113
137
|
}
|
|
114
138
|
|
|
@@ -141,6 +165,12 @@ export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
|
141
165
|
>;
|
|
142
166
|
}
|
|
143
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Best-effort extraction of the Cloudflare error `code` from a thrown error.
|
|
170
|
+
* Returns `undefined` when the property is absent — e.g. when a dev-time
|
|
171
|
+
* proxy such as a miniflare remote binding (`"remote": true`) serializes the
|
|
172
|
+
* error and strips non-standard properties.
|
|
173
|
+
*/
|
|
144
174
|
const emailErrorCode = (cause: unknown): EmailOperationError["code"] => {
|
|
145
175
|
if (typeof cause !== "object" || cause === null) {
|
|
146
176
|
return undefined;
|
|
@@ -439,21 +469,18 @@ export const makeClient =
|
|
|
439
469
|
(email: EmailBinding): EmailClient => {
|
|
440
470
|
const runtime = email as EmailRuntimeBinding;
|
|
441
471
|
const validate = options?.validate ?? true;
|
|
442
|
-
const send = ((message: EmailSendInput)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (!validate || !isEmailMessageBuilder(message)) {
|
|
447
|
-
return dispatch();
|
|
472
|
+
const send = Effect.fn("Email.send")(function* (message: EmailSendInput) {
|
|
473
|
+
if (validate && isEmailMessageBuilder(message)) {
|
|
474
|
+
yield* validateSendInput(definition.binding, "send", message);
|
|
448
475
|
}
|
|
449
476
|
|
|
450
|
-
return
|
|
477
|
+
return yield* tryEmailPromise(definition.binding, "send", () => runtime.send(message));
|
|
451
478
|
}) as EmailClient["send"];
|
|
452
479
|
|
|
453
480
|
return {
|
|
454
481
|
definition,
|
|
455
482
|
send,
|
|
456
|
-
|
|
483
|
+
rawUnsafe: Effect.succeed(email),
|
|
457
484
|
};
|
|
458
485
|
};
|
|
459
486
|
|
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
|
|