effect-cf 0.9.1 → 0.10.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 +12 -0
- package/dist/{Hyperdrive-LuQaVhXU.mjs → Hyperdrive-89hg_Fck.mjs} +80 -10
- package/dist/{Hyperdrive-By6DrSS_.d.mts → Hyperdrive-L6ldMlRF.d.mts} +10 -3
- package/dist/HyperdrivePg.d.mts +1 -1
- package/dist/HyperdrivePg.mjs +1 -1
- package/dist/index.d.mts +6 -4
- package/dist/index.mjs +49 -23
- package/package.json +5 -5
- package/src/Binding.ts +123 -5
- package/src/D1.ts +4 -1
- package/src/DurableObjectNamespace.ts +4 -0
- package/src/Environment.ts +1 -1
- package/src/Hyperdrive.ts +6 -1
- package/src/Images.ts +5 -1
- package/src/Kv.ts +4 -0
- package/src/QueueBinding.ts +52 -17
- package/src/R2.ts +6 -1
- package/src/ServiceBinding.ts +2 -0
- package/src/Workflow.ts +4 -8
- package/src/WorkflowBinding.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#32](https://github.com/danieljvdm/effect-cf/pull/32) [`4812c94`](https://github.com/danieljvdm/effect-cf/commit/4812c9457592f3416f6f303f0b8620ba52e46765) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Raise the minimum supported Effect beta to `4.0.0-beta.70` and update service tag access to use beta 70's direct yieldable tags instead of the removed `.asEffect()` helper.
|
|
8
|
+
|
|
9
|
+
## 0.9.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#30](https://github.com/danieljvdm/effect-cf/pull/30) [`29c3fd4`](https://github.com/danieljvdm/effect-cf/commit/29c3fd491bad0c8d6994e9a666f6e501c1a337a2) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Accept local queue producer bindings that only expose `send`, including Wrangler local dev bindings. Binding validation errors now include the binding name, expected shape, and actual resource shape in pretty output across Cloudflare bindings.
|
|
14
|
+
|
|
3
15
|
## 0.9.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -13,7 +13,7 @@ let WorkerConfig;
|
|
|
13
13
|
_WorkerConfig.integer = (name) => Config.int(name);
|
|
14
14
|
_WorkerConfig.boolean = (name) => Config.boolean(name);
|
|
15
15
|
const providerFromEnv = _WorkerConfig.providerFromEnv = (env) => ConfigProvider.fromUnknown(env);
|
|
16
|
-
const providerWith = _WorkerConfig.providerWith = (makeProvider) => Effect.map(WorkerEnvironment
|
|
16
|
+
const providerWith = _WorkerConfig.providerWith = (makeProvider) => Effect.map(WorkerEnvironment, makeProvider);
|
|
17
17
|
const provider = _WorkerConfig.provider = providerWith(providerFromEnv);
|
|
18
18
|
_WorkerConfig.layer = _WorkerConfig.providerLayer = ConfigProvider.layer(provider);
|
|
19
19
|
_WorkerConfig.layerWith = (makeProvider) => ConfigProvider.layer(providerWith(makeProvider));
|
|
@@ -33,21 +33,90 @@ const TypeId = "effect-cf/Binding";
|
|
|
33
33
|
var BindingNotFoundError = class extends Data.TaggedError("BindingNotFoundError") {};
|
|
34
34
|
/** Error raised when a binding exists but does not match the expected shape. */
|
|
35
35
|
var BindingValidationError = class extends Data.TaggedError("BindingValidationError") {};
|
|
36
|
+
const defaultExpected = "Cloudflare binding resource";
|
|
36
37
|
const isPropertyTarget = (value) => (typeof value === "object" || typeof value === "function") && value !== null;
|
|
37
|
-
const
|
|
38
|
-
|
|
38
|
+
const getObjectName = (value) => {
|
|
39
|
+
const tag = (() => {
|
|
40
|
+
try {
|
|
41
|
+
return Object.prototype.toString.call(value).slice(8, -1);
|
|
42
|
+
} catch {
|
|
43
|
+
return typeof value;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
const constructorName = (() => {
|
|
47
|
+
try {
|
|
48
|
+
return "constructor" in value && typeof value.constructor === "function" && typeof value.constructor.name === "string" ? value.constructor.name : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
if (tag !== "Object") return tag;
|
|
54
|
+
if (constructorName !== void 0 && constructorName !== "" && constructorName !== "Object") return constructorName;
|
|
55
|
+
return tag;
|
|
56
|
+
};
|
|
57
|
+
const propertyNames = (value) => {
|
|
58
|
+
const names = /* @__PURE__ */ new Set();
|
|
59
|
+
for (const target of [value, Object.getPrototypeOf(value)]) {
|
|
60
|
+
if (target === null || target === Object.prototype || target === Function.prototype) continue;
|
|
61
|
+
try {
|
|
62
|
+
for (const name of Object.getOwnPropertyNames(target)) names.add(name);
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [...names].filter((name) => name !== "constructor").sort();
|
|
68
|
+
};
|
|
69
|
+
const isMethod = (value, name) => {
|
|
70
|
+
try {
|
|
71
|
+
return typeof Reflect.get(value, name) === "function";
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const describeActual = (value) => {
|
|
77
|
+
if (value === null) return "null";
|
|
78
|
+
if (typeof value !== "object" && typeof value !== "function") return typeof value;
|
|
79
|
+
const names = propertyNames(value);
|
|
80
|
+
const methods = names.filter((name) => isMethod(value, name));
|
|
81
|
+
const properties = names.filter((name) => !methods.includes(name));
|
|
82
|
+
const details = [methods.length > 0 ? `methods ${methods.join(", ")}` : void 0, properties.length > 0 ? `properties ${properties.join(", ")}` : void 0].filter((detail) => detail !== void 0);
|
|
83
|
+
if (details.length === 0) return getObjectName(value);
|
|
84
|
+
return `${getObjectName(value)} with ${details.join("; ")}`;
|
|
85
|
+
};
|
|
86
|
+
const getBinding = (env, binding, isResource, options) => Effect.gen(function* () {
|
|
87
|
+
if (!isPropertyTarget(env)) {
|
|
88
|
+
const actual = describeActual(env);
|
|
89
|
+
return yield* Effect.fail(new BindingValidationError({
|
|
90
|
+
binding,
|
|
91
|
+
expected: "WorkerEnvironment object",
|
|
92
|
+
actual,
|
|
93
|
+
message: `Cloudflare binding "${binding}" failed validation. Expected WorkerEnvironment object; got ${actual}`
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
39
96
|
const resource = Reflect.get(env, binding);
|
|
40
|
-
if (resource === void 0) return yield* Effect.fail(new BindingNotFoundError({
|
|
41
|
-
|
|
97
|
+
if (resource === void 0) return yield* Effect.fail(new BindingNotFoundError({
|
|
98
|
+
binding,
|
|
99
|
+
message: `Cloudflare binding "${binding}" was not found in WorkerEnvironment`
|
|
100
|
+
}));
|
|
101
|
+
if (!isResource(resource)) {
|
|
102
|
+
const expected = options?.expected ?? defaultExpected;
|
|
103
|
+
const actual = describeActual(resource);
|
|
104
|
+
return yield* Effect.fail(new BindingValidationError({
|
|
105
|
+
binding,
|
|
106
|
+
expected,
|
|
107
|
+
actual,
|
|
108
|
+
message: `Cloudflare binding "${binding}" failed validation. Expected ${expected}; got ${actual}`
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
42
111
|
return resource;
|
|
43
112
|
});
|
|
44
|
-
const layer$1 = (tag, binding, isResource, wrap) => Layer.effect(tag, Effect.gen(function* () {
|
|
45
|
-
const resource = yield* getBinding(yield* WorkerEnvironment, binding, isResource);
|
|
113
|
+
const layer$1 = (tag, binding, isResource, wrap, options) => Layer.effect(tag, Effect.gen(function* () {
|
|
114
|
+
const resource = yield* getBinding(yield* WorkerEnvironment, binding, isResource, options);
|
|
46
115
|
return wrap === void 0 ? resource : wrap(resource);
|
|
47
116
|
}));
|
|
48
|
-
const Service = () => (id, binding, isResource, wrap) => {
|
|
117
|
+
const Service = () => (id, binding, isResource, wrap, options) => {
|
|
49
118
|
const tag = Context.Service()(id);
|
|
50
|
-
const serviceLayer = layer$1(tag, binding, isResource, wrap);
|
|
119
|
+
const serviceLayer = layer$1(tag, binding, isResource, wrap, options);
|
|
51
120
|
return Object.assign(tag, {
|
|
52
121
|
[TypeId]: TypeId,
|
|
53
122
|
binding,
|
|
@@ -63,6 +132,7 @@ var Hyperdrive_exports = /* @__PURE__ */ __exportAll({
|
|
|
63
132
|
make: () => make,
|
|
64
133
|
makeClient: () => makeClient
|
|
65
134
|
});
|
|
135
|
+
const expectedHyperdrive = "Hyperdrive binding with connectionString";
|
|
66
136
|
const isHyperdrive = (value) => {
|
|
67
137
|
if (typeof value !== "object" || value === null) return false;
|
|
68
138
|
return typeof value.connectionString === "string";
|
|
@@ -72,7 +142,7 @@ const makeClient = (definition) => (hyperdrive) => ({
|
|
|
72
142
|
connectionString: hyperdrive.connectionString,
|
|
73
143
|
unsafeRaw: Effect.succeed(hyperdrive)
|
|
74
144
|
});
|
|
75
|
-
const layer = (tag, definition) => layer$1(tag, definition.binding, isHyperdrive, makeClient(definition));
|
|
145
|
+
const layer = (tag, definition) => layer$1(tag, definition.binding, isHyperdrive, makeClient(definition), { expected: expectedHyperdrive });
|
|
76
146
|
const make = (id) => Tag()(id);
|
|
77
147
|
const Tag = () => (id) => {
|
|
78
148
|
const tag = Context.Service()(id);
|
|
@@ -76,7 +76,7 @@ declare namespace WorkerConfig {
|
|
|
76
76
|
const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
|
|
77
77
|
}
|
|
78
78
|
declare namespace Binding_d_exports {
|
|
79
|
-
export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, layer$1 as layer };
|
|
79
|
+
export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, ValidationOptions, layer$1 as layer };
|
|
80
80
|
}
|
|
81
81
|
/** Internal type id marker used by binding helper services. */
|
|
82
82
|
declare const TypeId: "effect-cf/Binding";
|
|
@@ -86,6 +86,7 @@ declare const BindingNotFoundError_base: new <A extends Record<string, any> = {}
|
|
|
86
86
|
/** Error raised when a configured binding does not exist on `env`. */
|
|
87
87
|
declare class BindingNotFoundError extends BindingNotFoundError_base<{
|
|
88
88
|
readonly binding: string;
|
|
89
|
+
readonly message: string;
|
|
89
90
|
}> {}
|
|
90
91
|
declare const BindingValidationError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
|
|
91
92
|
readonly _tag: "BindingValidationError";
|
|
@@ -93,7 +94,13 @@ declare const BindingValidationError_base: new <A extends Record<string, any> =
|
|
|
93
94
|
/** Error raised when a binding exists but does not match the expected shape. */
|
|
94
95
|
declare class BindingValidationError extends BindingValidationError_base<{
|
|
95
96
|
readonly binding: string;
|
|
97
|
+
readonly expected: string;
|
|
98
|
+
readonly actual: string;
|
|
99
|
+
readonly message: string;
|
|
96
100
|
}> {}
|
|
101
|
+
interface ValidationOptions {
|
|
102
|
+
readonly expected?: string;
|
|
103
|
+
}
|
|
97
104
|
/**
|
|
98
105
|
* Creates a Context tag + layer for reading and validating a Cloudflare binding.
|
|
99
106
|
*/
|
|
@@ -102,8 +109,8 @@ interface BindingService<Self, Id extends string, Service> extends Context.Servi
|
|
|
102
109
|
readonly binding: string;
|
|
103
110
|
readonly layer: Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
104
111
|
}
|
|
105
|
-
declare const layer$1: <Self, Resource, Service = Resource>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
106
|
-
declare const Service: <Self>() => <Id extends string, Resource, Service = Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service) => BindingService<Self, Id, Service>;
|
|
112
|
+
declare const layer$1: <Self, Resource, Service = Resource>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service, options?: ValidationOptions) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
113
|
+
declare const Service: <Self>() => <Id extends string, Resource, Service = Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service, options?: ValidationOptions) => BindingService<Self, Id, Service>;
|
|
107
114
|
declare namespace Hyperdrive_d_exports {
|
|
108
115
|
export { HyperdriveClient, HyperdriveDefinition, HyperdriveService, LayerOptions, Tag, TagClass, isHyperdrive, layer, make, makeClient };
|
|
109
116
|
}
|
package/dist/HyperdrivePg.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-
|
|
1
|
+
import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-L6ldMlRF.mjs";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
3
|
import { PgClient } from "@effect/sql-pg";
|
|
4
4
|
import * as _$effect_unstable_sql_SqlClient0 from "effect/unstable/sql/SqlClient";
|
package/dist/HyperdrivePg.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as BindingService, c as WorkerConfig, i as BindingNotFoundError, l as WorkerEnv, o as BindingValidationError, s as Binding_d_exports, t as Hyperdrive_d_exports, u as WorkerEnvironment } from "./Hyperdrive-
|
|
1
|
+
import { a as BindingService, c as WorkerConfig, i as BindingNotFoundError, l as WorkerEnv, o as BindingValidationError, s as Binding_d_exports, t as Hyperdrive_d_exports, u as WorkerEnvironment } from "./Hyperdrive-L6ldMlRF.mjs";
|
|
2
2
|
import { Cause, Context, Data, DateTime, Duration, Effect, Layer, Option, Schema, Scope } from "effect";
|
|
3
3
|
import { D1Client } from "@effect/sql-d1";
|
|
4
4
|
import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint, WorkflowEntrypoint, WorkflowEvent, WorkflowSleepDuration, WorkflowStep, WorkflowStepConfig, WorkflowStepContext, WorkflowStepEvent, WorkflowTimeoutDuration } from "cloudflare:workers";
|
|
@@ -184,7 +184,7 @@ declare const encodeSuccess: <const Self extends Definition$9.Any, MethodName ex
|
|
|
184
184
|
declare const decodeSuccess: <const Self extends Definition$9.Any, MethodName extends Definition$9.MethodNames<Self>>(definition: Self, methodName: MethodName, value: unknown) => Effect.Effect<Method$5.Success<Self["methods"][MethodName]>, RpcSuccessDecodeError>;
|
|
185
185
|
declare const make$9: <Id extends string, const MethodsShape extends Methods$4>(id: Id, methods: MethodsShape) => Definition$9<Id, MethodsShape>;
|
|
186
186
|
declare namespace QueueBinding_d_exports {
|
|
187
|
-
export { MessageSendRequest, QueueBindingClient, QueueBindingDefinition, QueueMetrics, QueueOperationError, QueueSendBatchOptions, QueueSendBatchResponse, QueueSendOptions, QueueSendResponse, isQueue, layer$7 as layer, makeClient$6 as makeClient };
|
|
187
|
+
export { MessageSendRequest, QueueBindingClient, QueueBindingDefinition, QueueMetrics, QueueOperationError, QueueProducer, QueueSendBatchOptions, QueueSendBatchResponse, QueueSendOptions, QueueSendResponse, isQueue, layer$7 as layer, makeClient$6 as makeClient };
|
|
188
188
|
}
|
|
189
189
|
type QueueSendOptions = globalThis.QueueSendOptions;
|
|
190
190
|
type QueueSendResponse = globalThis.QueueSendResponse;
|
|
@@ -192,6 +192,7 @@ type QueueSendBatchOptions = globalThis.QueueSendBatchOptions;
|
|
|
192
192
|
type QueueSendBatchResponse = globalThis.QueueSendBatchResponse;
|
|
193
193
|
type QueueMetrics = globalThis.QueueMetrics;
|
|
194
194
|
type MessageSendRequest<Body> = globalThis.MessageSendRequest<Body>;
|
|
195
|
+
type QueueProducer<Body> = Pick<globalThis.Queue<Body>, "send"> & Partial<Pick<globalThis.Queue<Body>, "sendBatch" | "metrics">>;
|
|
195
196
|
interface QueueBindingDefinition<Message extends ServiceFreeSchema$4> {
|
|
196
197
|
/** Binding name as configured in `wrangler.jsonc`. */
|
|
197
198
|
readonly binding: string;
|
|
@@ -211,9 +212,10 @@ declare class QueueOperationError extends QueueOperationError_base<{
|
|
|
211
212
|
readonly binding: string;
|
|
212
213
|
readonly operation: string;
|
|
213
214
|
readonly cause: unknown;
|
|
215
|
+
readonly message: string;
|
|
214
216
|
}> {}
|
|
215
|
-
declare const isQueue: <Body>(value: unknown) => value is
|
|
216
|
-
declare const makeClient$6: <Message extends ServiceFreeSchema$4>(definition: QueueBindingDefinition<Message>) => ((queue:
|
|
217
|
+
declare const isQueue: <Body>(value: unknown) => value is QueueProducer<Body>;
|
|
218
|
+
declare const makeClient$6: <Message extends ServiceFreeSchema$4>(definition: QueueBindingDefinition<Message>) => ((queue: QueueProducer<Schema.Codec.Encoded<Message>>) => QueueBindingClient<Message>);
|
|
217
219
|
declare const layer$7: <Self, Message extends ServiceFreeSchema$4>(tag: Context.Service<Self, QueueBindingClient<Message>>, definition: QueueBindingDefinition<Message>) => _$effect_Layer0.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
218
220
|
declare namespace QueueDefinition_d_exports {
|
|
219
221
|
export { Definition$8 as Definition, Handler$3 as Handler, LayerOptions$11 as LayerOptions, Options$7 as Options, Queue$1 as Queue, Tag$11 as Tag, TagClass$10 as TagClass, implement$7 as implement, make$8 as make };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
-
import { a as WorkerConfig, i as layer$8, n as Binding_exports, o as WorkerEnvironment, r as Service$2, t as Hyperdrive_exports } from "./Hyperdrive-
|
|
2
|
+
import { a as WorkerConfig, i as layer$8, n as Binding_exports, o as WorkerEnvironment, r as Service$2, t as Hyperdrive_exports } from "./Hyperdrive-89hg_Fck.mjs";
|
|
3
3
|
import { Cause, Clock, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, Layer, ManagedRuntime, Option, Queue, Schema } from "effect";
|
|
4
4
|
import { D1Client } from "@effect/sql-d1";
|
|
5
5
|
import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint, WorkflowEntrypoint } from "cloudflare:workers";
|
|
@@ -13,6 +13,7 @@ var D1_exports = /* @__PURE__ */ __exportAll({
|
|
|
13
13
|
make: () => make$10
|
|
14
14
|
});
|
|
15
15
|
const TypeId$2 = "effect-cf/D1";
|
|
16
|
+
const expectedD1Database = "D1 database binding with prepare(), batch(), and exec()";
|
|
16
17
|
const isD1Database = (value) => {
|
|
17
18
|
if (typeof value !== "object" || value === null) return false;
|
|
18
19
|
const resource = value;
|
|
@@ -38,7 +39,7 @@ const make$10 = (id, definition) => Service$1()(id, definition);
|
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
41
|
const Service$1 = () => (id, definition) => {
|
|
41
|
-
const tag = Service$2()(id, definition.binding, isD1Database);
|
|
42
|
+
const tag = Service$2()(id, definition.binding, isD1Database, void 0, { expected: expectedD1Database });
|
|
42
43
|
const sqlLayer = (options) => Layer.unwrap(Effect.gen(function* () {
|
|
43
44
|
const db = yield* tag;
|
|
44
45
|
return D1Client.layer({
|
|
@@ -60,20 +61,20 @@ var QueueBinding_exports = /* @__PURE__ */ __exportAll({
|
|
|
60
61
|
layer: () => layer$7,
|
|
61
62
|
makeClient: () => makeClient$6
|
|
62
63
|
});
|
|
64
|
+
const expectedQueueProducer = "Queue producer binding with send(); optional sendBatch()/metrics()";
|
|
63
65
|
var QueueOperationError = class extends Data.TaggedError("QueueOperationError") {};
|
|
64
|
-
const queueError = (binding, operation, cause) => new QueueOperationError({
|
|
65
|
-
binding,
|
|
66
|
-
operation,
|
|
67
|
-
cause
|
|
68
|
-
});
|
|
69
66
|
const tryQueuePromise = (binding, operation, evaluate) => Effect.tryPromise({
|
|
70
67
|
try: evaluate,
|
|
71
|
-
catch: (cause) =>
|
|
68
|
+
catch: (cause) => new QueueOperationError({
|
|
69
|
+
binding,
|
|
70
|
+
operation,
|
|
71
|
+
cause,
|
|
72
|
+
message: `Cloudflare queue binding "${binding}" operation "${operation}" failed`
|
|
73
|
+
})
|
|
72
74
|
});
|
|
73
75
|
const isQueue = (value) => {
|
|
74
76
|
if (typeof value !== "object" || value === null) return false;
|
|
75
|
-
|
|
76
|
-
return typeof resource.send === "function" && typeof resource.sendBatch === "function" && typeof resource.metrics === "function";
|
|
77
|
+
return typeof value.send === "function";
|
|
77
78
|
};
|
|
78
79
|
const makeClient$6 = (definition) => {
|
|
79
80
|
const encodeMessage = Schema.encodeEffect(definition.message);
|
|
@@ -88,13 +89,32 @@ const makeClient$6 = (definition) => {
|
|
|
88
89
|
...message,
|
|
89
90
|
body: yield* encodeMessage(message.body)
|
|
90
91
|
});
|
|
91
|
-
|
|
92
|
+
const sendBatch = queue.sendBatch;
|
|
93
|
+
if (sendBatch !== void 0) {
|
|
94
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", () => sendBatch.call(queue, encodedMessages, options));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", async () => {
|
|
98
|
+
for (const message of encodedMessages) await queue.send(message.body, {
|
|
99
|
+
contentType: message.contentType,
|
|
100
|
+
delaySeconds: message.delaySeconds ?? options?.delaySeconds
|
|
101
|
+
});
|
|
102
|
+
});
|
|
92
103
|
}),
|
|
93
|
-
metrics: () =>
|
|
104
|
+
metrics: () => {
|
|
105
|
+
const metrics = queue.metrics;
|
|
106
|
+
if (metrics === void 0) return Effect.fail(new QueueOperationError({
|
|
107
|
+
binding: definition.binding,
|
|
108
|
+
operation: "metrics",
|
|
109
|
+
cause: /* @__PURE__ */ new Error(`Queue binding "${definition.binding}" does not provide metrics()`),
|
|
110
|
+
message: `Cloudflare queue binding "${definition.binding}" does not provide metrics()`
|
|
111
|
+
}));
|
|
112
|
+
return tryQueuePromise(definition.binding, "metrics", () => metrics.call(queue));
|
|
113
|
+
},
|
|
94
114
|
unsafeRaw: Effect.succeed(queue)
|
|
95
115
|
});
|
|
96
116
|
};
|
|
97
|
-
const layer$7 = (tag, definition) => layer$8(tag, definition.binding, (value) => isQueue(value), makeClient$6(definition));
|
|
117
|
+
const layer$7 = (tag, definition) => layer$8(tag, definition.binding, (value) => isQueue(value), makeClient$6(definition), { expected: expectedQueueProducer });
|
|
98
118
|
//#endregion
|
|
99
119
|
//#region src/QueueDefinition.ts
|
|
100
120
|
var QueueDefinition_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -378,6 +398,7 @@ var ServiceBinding_exports = /* @__PURE__ */ __exportAll({
|
|
|
378
398
|
makeDirectMethods: () => makeDirectMethods$1
|
|
379
399
|
});
|
|
380
400
|
const TypeId$1 = "effect-cf/ServiceBinding";
|
|
401
|
+
const expectedServiceBinding = "Worker service binding with fetch()";
|
|
381
402
|
/**
|
|
382
403
|
* Failure raised when calling `fetch` on a service binding.
|
|
383
404
|
*/
|
|
@@ -436,7 +457,7 @@ const makeClient$5 = (definition) => {
|
|
|
436
457
|
});
|
|
437
458
|
};
|
|
438
459
|
};
|
|
439
|
-
const layer$6 = (tag, definition) => layer$8(tag, definition.binding, (value) => isServiceBindingClient(value), makeClient$5(definition));
|
|
460
|
+
const layer$6 = (tag, definition) => layer$8(tag, definition.binding, (value) => isServiceBindingClient(value), makeClient$5(definition), { expected: expectedServiceBinding });
|
|
440
461
|
/**
|
|
441
462
|
* Creates a typed Effect service for a Worker service binding.
|
|
442
463
|
*
|
|
@@ -1003,6 +1024,7 @@ var DurableObjectNamespace_exports = /* @__PURE__ */ __exportAll({
|
|
|
1003
1024
|
makeClient: () => makeClient$4,
|
|
1004
1025
|
makeDirectMethods: () => makeDirectMethods
|
|
1005
1026
|
});
|
|
1027
|
+
const expectedDurableObjectNamespace = "Durable Object namespace binding with getByName(), get(), idFromName(), idFromString(), newUniqueId(), and jurisdiction()";
|
|
1006
1028
|
/**
|
|
1007
1029
|
* Failure raised when invoking an RPC method on a Durable Object stub.
|
|
1008
1030
|
*/
|
|
@@ -1080,7 +1102,7 @@ const makeClient$4 = (definition) => {
|
|
|
1080
1102
|
});
|
|
1081
1103
|
};
|
|
1082
1104
|
};
|
|
1083
|
-
const layer$5 = (tag, definition) => layer$8(tag, definition.binding, (value) => isDurableObjectNamespaceClient(value), makeClient$4(definition));
|
|
1105
|
+
const layer$5 = (tag, definition) => layer$8(tag, definition.binding, (value) => isDurableObjectNamespaceClient(value), makeClient$4(definition), { expected: expectedDurableObjectNamespace });
|
|
1084
1106
|
const makeDirectMethods = (rpcDefinition, helpers) => {
|
|
1085
1107
|
const methods = {};
|
|
1086
1108
|
if (rpcDefinition !== void 0) {
|
|
@@ -1752,6 +1774,7 @@ var Images_exports = /* @__PURE__ */ __exportAll({
|
|
|
1752
1774
|
transform: () => transform
|
|
1753
1775
|
});
|
|
1754
1776
|
const TypeId = "effect-cf/Images/Steps";
|
|
1777
|
+
const expectedImagesBinding = "Images binding with info() and input()";
|
|
1755
1778
|
/** Error raised when a Cloudflare Images operation fails. */
|
|
1756
1779
|
var ImagesOperationError = class extends Data.TaggedError("ImagesOperationError") {};
|
|
1757
1780
|
const makeSteps = (steps) => ({
|
|
@@ -1834,7 +1857,7 @@ const makeClient$3 = (definition) => (images) => {
|
|
|
1834
1857
|
unsafeRaw: Effect.succeed(images)
|
|
1835
1858
|
};
|
|
1836
1859
|
};
|
|
1837
|
-
const layer$3 = (tag, definition) => layer$8(tag, definition.binding, isImagesBinding, makeClient$3(definition));
|
|
1860
|
+
const layer$3 = (tag, definition) => layer$8(tag, definition.binding, isImagesBinding, makeClient$3(definition), { expected: expectedImagesBinding });
|
|
1838
1861
|
const make$3 = (id) => Tag$4()(id);
|
|
1839
1862
|
const Tag$4 = () => (id) => {
|
|
1840
1863
|
const tag = Context.Service()(id);
|
|
@@ -1853,6 +1876,7 @@ var Kv_exports = /* @__PURE__ */ __exportAll({
|
|
|
1853
1876
|
layer: () => layer$2,
|
|
1854
1877
|
makeClient: () => makeClient$2
|
|
1855
1878
|
});
|
|
1879
|
+
const expectedKvNamespace = "KV namespace binding with get(), put(), delete(), getWithMetadata(), and list()";
|
|
1856
1880
|
/** Error raised when a KV operation fails. */
|
|
1857
1881
|
var KvOperationError = class extends Data.TaggedError("KvOperationError") {};
|
|
1858
1882
|
const maybeString = (value) => value == null ? Option.none() : Option.some(value);
|
|
@@ -1928,7 +1952,7 @@ const makeClient$2 = (definition) => {
|
|
|
1928
1952
|
unsafeRaw: Effect.succeed(kv)
|
|
1929
1953
|
});
|
|
1930
1954
|
};
|
|
1931
|
-
const layer$2 = (tag, definition) => layer$8(tag, definition.binding, isKvNamespace, makeClient$2(definition));
|
|
1955
|
+
const layer$2 = (tag, definition) => layer$8(tag, definition.binding, isKvNamespace, makeClient$2(definition), { expected: expectedKvNamespace });
|
|
1932
1956
|
const makeDefinition$1 = (id, definition) => {
|
|
1933
1957
|
const kvDefinition = {
|
|
1934
1958
|
id,
|
|
@@ -1962,6 +1986,7 @@ var R2_exports = /* @__PURE__ */ __exportAll({
|
|
|
1962
1986
|
make: () => make$2,
|
|
1963
1987
|
makeClient: () => makeClient$1
|
|
1964
1988
|
});
|
|
1989
|
+
const expectedR2Bucket = "R2 bucket binding with head(), get(), put(), createMultipartUpload(), resumeMultipartUpload(), delete(), and list()";
|
|
1965
1990
|
/** Error raised when an R2 operation fails. */
|
|
1966
1991
|
var R2OperationError = class extends Data.TaggedError("R2OperationError") {};
|
|
1967
1992
|
const r2Error = (binding, operation, cause) => new R2OperationError({
|
|
@@ -2044,7 +2069,7 @@ const makeClient$1 = (definition) => {
|
|
|
2044
2069
|
};
|
|
2045
2070
|
};
|
|
2046
2071
|
};
|
|
2047
|
-
const layer$1 = (tag, definition) => layer$8(tag, definition.binding, isR2Bucket, makeClient$1(definition));
|
|
2072
|
+
const layer$1 = (tag, definition) => layer$8(tag, definition.binding, isR2Bucket, makeClient$1(definition), { expected: expectedR2Bucket });
|
|
2048
2073
|
const make$2 = (id) => Tag$2()(id);
|
|
2049
2074
|
const Tag$2 = () => (id) => {
|
|
2050
2075
|
const tag = Context.Service()(id);
|
|
@@ -2063,6 +2088,7 @@ var WorkflowBinding_exports = /* @__PURE__ */ __exportAll({
|
|
|
2063
2088
|
layer: () => layer,
|
|
2064
2089
|
makeClient: () => makeClient
|
|
2065
2090
|
});
|
|
2091
|
+
const expectedWorkflow = "Workflow binding with create(), createBatch(), and get()";
|
|
2066
2092
|
var WorkflowOperationError = class extends Data.TaggedError("WorkflowOperationError") {};
|
|
2067
2093
|
var WorkflowResultDecodeError = class extends Data.TaggedError("WorkflowResultDecodeError") {};
|
|
2068
2094
|
const workflowError = (binding, operation, cause) => new WorkflowOperationError({
|
|
@@ -2129,7 +2155,7 @@ const makeClient = (definition) => {
|
|
|
2129
2155
|
unsafeRaw: Effect.succeed(workflow)
|
|
2130
2156
|
});
|
|
2131
2157
|
};
|
|
2132
|
-
const layer = (tag, definition) => layer$8(tag, definition.binding, (value) => isWorkflow(value), makeClient(definition));
|
|
2158
|
+
const layer = (tag, definition) => layer$8(tag, definition.binding, (value) => isWorkflow(value), makeClient(definition), { expected: expectedWorkflow });
|
|
2133
2159
|
//#endregion
|
|
2134
2160
|
//#region src/WorkflowDefinition.ts
|
|
2135
2161
|
var WorkflowDefinition_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -2256,10 +2282,10 @@ const make = (layer, options) => {
|
|
|
2256
2282
|
}
|
|
2257
2283
|
return assumeEntrypointClass(EffectWorkflow);
|
|
2258
2284
|
};
|
|
2259
|
-
const step = (name, effect, config) => Effect.flatMap(WorkflowStep$1
|
|
2260
|
-
const sleep = (name, duration) => Effect.flatMap(WorkflowStep$1
|
|
2261
|
-
const sleepUntil = (name, timestamp) => Effect.flatMap(WorkflowStep$1
|
|
2262
|
-
const waitForEvent = (name, options) => Effect.flatMap(WorkflowStep$1
|
|
2285
|
+
const step = (name, effect, config) => Effect.flatMap(WorkflowStep$1, (workflowStep) => workflowStep.do(name, effect, config));
|
|
2286
|
+
const sleep = (name, duration) => Effect.flatMap(WorkflowStep$1, (workflowStep) => workflowStep.sleep(name, duration));
|
|
2287
|
+
const sleepUntil = (name, timestamp) => Effect.flatMap(WorkflowStep$1, (workflowStep) => workflowStep.sleepUntil(name, timestamp));
|
|
2288
|
+
const waitForEvent = (name, options) => Effect.flatMap(WorkflowStep$1, (workflowStep) => workflowStep.waitForEvent(name, options));
|
|
2263
2289
|
const Tag = Tag$1;
|
|
2264
2290
|
const implement = implement$1;
|
|
2265
2291
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-cf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Effect-native primitives for Cloudflare Workers and bindings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@cloudflare/vitest-pool-workers": "^0.15.0",
|
|
43
43
|
"@cloudflare/workers-types": "^4.20260421.1",
|
|
44
44
|
"@effect/sql-d1": "catalog:",
|
|
45
|
-
"@effect/sql-pg": "
|
|
45
|
+
"@effect/sql-pg": "catalog:",
|
|
46
46
|
"@effect/vitest": "catalog:",
|
|
47
47
|
"effect": "catalog:",
|
|
48
48
|
"typescript": "^6.0.2",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"vitest": "catalog:"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@effect/sql-d1": "^4.0.0-beta.
|
|
54
|
-
"@effect/sql-pg": "^4.0.0-beta.
|
|
55
|
-
"effect": "^4.0.0-beta.
|
|
53
|
+
"@effect/sql-d1": "^4.0.0-beta.70",
|
|
54
|
+
"@effect/sql-pg": "^4.0.0-beta.70",
|
|
55
|
+
"effect": "^4.0.0-beta.70"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
58
58
|
"@effect/sql-pg": {
|
package/src/Binding.ts
CHANGED
|
@@ -9,34 +9,150 @@ export const TypeId = "effect-cf/Binding" as const;
|
|
|
9
9
|
/** Error raised when a configured binding does not exist on `env`. */
|
|
10
10
|
export class BindingNotFoundError extends Data.TaggedError("BindingNotFoundError")<{
|
|
11
11
|
readonly binding: string;
|
|
12
|
+
readonly message: string;
|
|
12
13
|
}> {}
|
|
13
14
|
|
|
14
15
|
/** Error raised when a binding exists but does not match the expected shape. */
|
|
15
16
|
export class BindingValidationError extends Data.TaggedError("BindingValidationError")<{
|
|
16
17
|
readonly binding: string;
|
|
18
|
+
readonly expected: string;
|
|
19
|
+
readonly actual: string;
|
|
20
|
+
readonly message: string;
|
|
17
21
|
}> {}
|
|
18
22
|
|
|
23
|
+
export interface ValidationOptions {
|
|
24
|
+
readonly expected?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const defaultExpected = "Cloudflare binding resource";
|
|
28
|
+
|
|
19
29
|
const isPropertyTarget = (value: unknown): value is object =>
|
|
20
30
|
(typeof value === "object" || typeof value === "function") && value !== null;
|
|
21
31
|
|
|
32
|
+
const getObjectName = (value: object | Function): string => {
|
|
33
|
+
const tag = (() => {
|
|
34
|
+
try {
|
|
35
|
+
return Object.prototype.toString.call(value).slice("[object ".length, -1);
|
|
36
|
+
} catch {
|
|
37
|
+
return typeof value;
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
const constructorName = (() => {
|
|
41
|
+
try {
|
|
42
|
+
return "constructor" in value &&
|
|
43
|
+
typeof value.constructor === "function" &&
|
|
44
|
+
typeof value.constructor.name === "string"
|
|
45
|
+
? value.constructor.name
|
|
46
|
+
: undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
|
|
52
|
+
if (tag !== "Object") {
|
|
53
|
+
return tag;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (constructorName !== undefined && constructorName !== "" && constructorName !== "Object") {
|
|
57
|
+
return constructorName;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return tag;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const propertyNames = (value: object | Function): ReadonlyArray<string> => {
|
|
64
|
+
const names = new Set<string>();
|
|
65
|
+
|
|
66
|
+
for (const target of [value, Object.getPrototypeOf(value)] as const) {
|
|
67
|
+
if (target === null || target === Object.prototype || target === Function.prototype) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
for (const name of Object.getOwnPropertyNames(target)) {
|
|
73
|
+
names.add(name);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [...names].filter((name) => name !== "constructor").sort();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const isMethod = (value: object | Function, name: string): boolean => {
|
|
84
|
+
try {
|
|
85
|
+
return typeof Reflect.get(value, name) === "function";
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const describeActual = (value: unknown): string => {
|
|
92
|
+
if (value === null) {
|
|
93
|
+
return "null";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof value !== "object" && typeof value !== "function") {
|
|
97
|
+
return typeof value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const names = propertyNames(value);
|
|
101
|
+
const methods = names.filter((name) => isMethod(value, name));
|
|
102
|
+
const properties = names.filter((name) => !methods.includes(name));
|
|
103
|
+
const details = [
|
|
104
|
+
methods.length > 0 ? `methods ${methods.join(", ")}` : undefined,
|
|
105
|
+
properties.length > 0 ? `properties ${properties.join(", ")}` : undefined,
|
|
106
|
+
].filter((detail) => detail !== undefined);
|
|
107
|
+
|
|
108
|
+
if (details.length === 0) {
|
|
109
|
+
return getObjectName(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return `${getObjectName(value)} with ${details.join("; ")}`;
|
|
113
|
+
};
|
|
114
|
+
|
|
22
115
|
const getBinding = <Resource>(
|
|
23
116
|
env: WorkerEnv,
|
|
24
117
|
binding: string,
|
|
25
118
|
isResource: (value: unknown) => value is Resource,
|
|
119
|
+
options?: ValidationOptions,
|
|
26
120
|
): Effect.Effect<Resource, BindingNotFoundError | BindingValidationError> =>
|
|
27
121
|
Effect.gen(function* () {
|
|
28
122
|
if (!isPropertyTarget(env)) {
|
|
29
|
-
|
|
123
|
+
const actual = describeActual(env);
|
|
124
|
+
return yield* Effect.fail(
|
|
125
|
+
new BindingValidationError({
|
|
126
|
+
binding,
|
|
127
|
+
expected: "WorkerEnvironment object",
|
|
128
|
+
actual,
|
|
129
|
+
message: `Cloudflare binding "${binding}" failed validation. Expected WorkerEnvironment object; got ${actual}`,
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
30
132
|
}
|
|
31
133
|
|
|
32
134
|
const resource = Reflect.get(env, binding);
|
|
33
135
|
|
|
34
136
|
if (resource === undefined) {
|
|
35
|
-
return yield* Effect.fail(
|
|
137
|
+
return yield* Effect.fail(
|
|
138
|
+
new BindingNotFoundError({
|
|
139
|
+
binding,
|
|
140
|
+
message: `Cloudflare binding "${binding}" was not found in WorkerEnvironment`,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
36
143
|
}
|
|
37
144
|
|
|
38
145
|
if (!isResource(resource)) {
|
|
39
|
-
|
|
146
|
+
const expected = options?.expected ?? defaultExpected;
|
|
147
|
+
const actual = describeActual(resource);
|
|
148
|
+
return yield* Effect.fail(
|
|
149
|
+
new BindingValidationError({
|
|
150
|
+
binding,
|
|
151
|
+
expected,
|
|
152
|
+
actual,
|
|
153
|
+
message: `Cloudflare binding "${binding}" failed validation. Expected ${expected}; got ${actual}`,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
40
156
|
}
|
|
41
157
|
|
|
42
158
|
return resource;
|
|
@@ -64,12 +180,13 @@ export const layer = <Self, Resource, Service = Resource>(
|
|
|
64
180
|
binding: string,
|
|
65
181
|
isResource: (value: unknown) => value is Resource,
|
|
66
182
|
wrap?: (resource: Resource) => Service,
|
|
183
|
+
options?: ValidationOptions,
|
|
67
184
|
): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> =>
|
|
68
185
|
Layer.effect(
|
|
69
186
|
tag,
|
|
70
187
|
Effect.gen(function* () {
|
|
71
188
|
const env = yield* WorkerEnvironment;
|
|
72
|
-
const resource = yield* getBinding(env, binding, isResource);
|
|
189
|
+
const resource = yield* getBinding(env, binding, isResource, options);
|
|
73
190
|
return wrap === undefined ? (resource as unknown as Service) : wrap(resource);
|
|
74
191
|
}),
|
|
75
192
|
);
|
|
@@ -81,9 +198,10 @@ export const Service =
|
|
|
81
198
|
binding: string,
|
|
82
199
|
isResource: (value: unknown) => value is Resource,
|
|
83
200
|
wrap?: (resource: Resource) => Service,
|
|
201
|
+
options?: ValidationOptions,
|
|
84
202
|
): BindingService<Self, Id, Service> => {
|
|
85
203
|
const tag = Context.Service<Self, Service>()(id);
|
|
86
|
-
const serviceLayer = layer(tag, binding, isResource, wrap);
|
|
204
|
+
const serviceLayer = layer(tag, binding, isResource, wrap, options);
|
|
87
205
|
|
|
88
206
|
return Object.assign(tag, {
|
|
89
207
|
[TypeId]: TypeId,
|
package/src/D1.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Effect, Layer } from "effect";
|
|
|
4
4
|
import * as Binding from "./Binding";
|
|
5
5
|
|
|
6
6
|
const TypeId = "effect-cf/D1" as const;
|
|
7
|
+
const expectedD1Database = "D1 database binding with prepare(), batch(), and exec()";
|
|
7
8
|
|
|
8
9
|
/** Typed D1 binding definition. */
|
|
9
10
|
export interface D1Definition {
|
|
@@ -61,7 +62,9 @@ export const make = <Id extends string>(id: Id, definition: D1Definition) =>
|
|
|
61
62
|
export const Service =
|
|
62
63
|
<Self>() =>
|
|
63
64
|
<Id extends string>(id: Id, definition: D1Definition) => {
|
|
64
|
-
const tag = Binding.Service<Self>()(id, definition.binding, isD1Database
|
|
65
|
+
const tag = Binding.Service<Self>()(id, definition.binding, isD1Database, undefined, {
|
|
66
|
+
expected: expectedD1Database,
|
|
67
|
+
});
|
|
65
68
|
|
|
66
69
|
const sqlLayer = (options?: D1SqlLayerOptions) =>
|
|
67
70
|
Layer.unwrap(
|
|
@@ -6,6 +6,9 @@ import type * as DurableObjectDefinition from "./DurableObjectDefinition";
|
|
|
6
6
|
import * as RpcDefinition from "./RpcDefinition";
|
|
7
7
|
import * as RpcInvocation from "./internal/RpcInvocation";
|
|
8
8
|
|
|
9
|
+
const expectedDurableObjectNamespace =
|
|
10
|
+
"Durable Object namespace binding with getByName(), get(), idFromName(), idFromString(), newUniqueId(), and jurisdiction()";
|
|
11
|
+
|
|
9
12
|
interface DurableObjectFetcher {
|
|
10
13
|
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
11
14
|
}
|
|
@@ -548,6 +551,7 @@ export const layer = <
|
|
|
548
551
|
(value): value is DurableObjectNamespaceClient<Api> =>
|
|
549
552
|
isDurableObjectNamespaceClient<Api>(value),
|
|
550
553
|
makeClient<Api, Definition>(definition),
|
|
554
|
+
{ expected: expectedDurableObjectNamespace },
|
|
551
555
|
);
|
|
552
556
|
|
|
553
557
|
export const makeDirectMethods = <
|
package/src/Environment.ts
CHANGED
|
@@ -80,7 +80,7 @@ export namespace WorkerConfig {
|
|
|
80
80
|
* conversion function.
|
|
81
81
|
*/
|
|
82
82
|
export const providerWith = (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) =>
|
|
83
|
-
Effect.map(WorkerEnvironment
|
|
83
|
+
Effect.map(WorkerEnvironment, makeProvider);
|
|
84
84
|
|
|
85
85
|
/** Build a `ConfigProvider` from the current `WorkerEnvironment`. */
|
|
86
86
|
export const provider = providerWith(providerFromEnv);
|
package/src/Hyperdrive.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Context, Effect, type Layer } from "effect";
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
5
5
|
|
|
6
|
+
const expectedHyperdrive = "Hyperdrive binding with connectionString";
|
|
7
|
+
|
|
6
8
|
/** Typed Hyperdrive binding definition. */
|
|
7
9
|
export interface HyperdriveDefinition {
|
|
8
10
|
/** Binding name as configured in `wrangler.jsonc`. */
|
|
@@ -64,7 +66,10 @@ export const makeClient =
|
|
|
64
66
|
export const layer = <Self>(
|
|
65
67
|
tag: Context.Service<Self, HyperdriveClient>,
|
|
66
68
|
definition: HyperdriveDefinition,
|
|
67
|
-
) =>
|
|
69
|
+
) =>
|
|
70
|
+
Binding.layer(tag, definition.binding, isHyperdrive, makeClient(definition), {
|
|
71
|
+
expected: expectedHyperdrive,
|
|
72
|
+
});
|
|
68
73
|
|
|
69
74
|
export const make = <Id extends string>(id: Id) => Tag<HyperdriveService<Id>>()<Id>(id);
|
|
70
75
|
|
package/src/Images.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as Binding from "./Binding";
|
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
5
5
|
|
|
6
6
|
const TypeId = "effect-cf/Images/Steps" as const;
|
|
7
|
+
const expectedImagesBinding = "Images binding with info() and input()";
|
|
7
8
|
|
|
8
9
|
/** Error raised when a Cloudflare Images operation fails. */
|
|
9
10
|
export class ImagesOperationError extends Data.TaggedError("ImagesOperationError")<{
|
|
@@ -314,7 +315,10 @@ export const makeClient =
|
|
|
314
315
|
export const layer = <Self>(
|
|
315
316
|
tag: Context.Service<Self, ImagesClient>,
|
|
316
317
|
definition: ImagesDefinition,
|
|
317
|
-
) =>
|
|
318
|
+
) =>
|
|
319
|
+
Binding.layer(tag, definition.binding, isImagesBinding, makeClient(definition), {
|
|
320
|
+
expected: expectedImagesBinding,
|
|
321
|
+
});
|
|
318
322
|
|
|
319
323
|
export const make = <Id extends string>(id: Id) => Tag<ImagesService<Id>>()<Id>(id);
|
|
320
324
|
|
package/src/Kv.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Context, Data, Effect, Option, Schema as S, type Layer } from "effect";
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
5
5
|
|
|
6
|
+
const expectedKvNamespace =
|
|
7
|
+
"KV namespace binding with get(), put(), delete(), getWithMetadata(), and list()";
|
|
8
|
+
|
|
6
9
|
/** Error raised when a KV operation fails. */
|
|
7
10
|
export class KvOperationError extends Data.TaggedError("KvOperationError")<{
|
|
8
11
|
readonly binding: string;
|
|
@@ -250,6 +253,7 @@ export const layer = <Self, Key, Value, EncodedValue>(
|
|
|
250
253
|
definition.binding,
|
|
251
254
|
isKvNamespace,
|
|
252
255
|
makeClient<Key, Value, EncodedValue>(definition),
|
|
256
|
+
{ expected: expectedKvNamespace },
|
|
253
257
|
);
|
|
254
258
|
|
|
255
259
|
const makeDefinition = <Id extends string, Key, Value, EncodedValue>(
|
package/src/QueueBinding.ts
CHANGED
|
@@ -10,6 +10,11 @@ export type QueueSendBatchResponse = globalThis.QueueSendBatchResponse;
|
|
|
10
10
|
export type QueueMetrics = globalThis.QueueMetrics;
|
|
11
11
|
export type MessageSendRequest<Body> = globalThis.MessageSendRequest<Body>;
|
|
12
12
|
|
|
13
|
+
export type QueueProducer<Body> = Pick<globalThis.Queue<Body>, "send"> &
|
|
14
|
+
Partial<Pick<globalThis.Queue<Body>, "sendBatch" | "metrics">>;
|
|
15
|
+
|
|
16
|
+
const expectedQueueProducer = "Queue producer binding with send(); optional sendBatch()/metrics()";
|
|
17
|
+
|
|
13
18
|
export interface QueueBindingDefinition<Message extends RpcDefinition.ServiceFreeSchema> {
|
|
14
19
|
/** Binding name as configured in `wrangler.jsonc`. */
|
|
15
20
|
readonly binding: string;
|
|
@@ -34,11 +39,9 @@ export class QueueOperationError extends Data.TaggedError("QueueOperationError")
|
|
|
34
39
|
readonly binding: string;
|
|
35
40
|
readonly operation: string;
|
|
36
41
|
readonly cause: unknown;
|
|
42
|
+
readonly message: string;
|
|
37
43
|
}> {}
|
|
38
44
|
|
|
39
|
-
const queueError = (binding: string, operation: string, cause: unknown) =>
|
|
40
|
-
new QueueOperationError({ binding, operation, cause });
|
|
41
|
-
|
|
42
45
|
const tryQueuePromise = <A>(
|
|
43
46
|
binding: string,
|
|
44
47
|
operation: string,
|
|
@@ -46,26 +49,28 @@ const tryQueuePromise = <A>(
|
|
|
46
49
|
): Effect.Effect<A, QueueOperationError> =>
|
|
47
50
|
Effect.tryPromise({
|
|
48
51
|
try: evaluate,
|
|
49
|
-
catch: (cause) =>
|
|
52
|
+
catch: (cause) =>
|
|
53
|
+
new QueueOperationError({
|
|
54
|
+
binding,
|
|
55
|
+
operation,
|
|
56
|
+
cause,
|
|
57
|
+
message: `Cloudflare queue binding "${binding}" operation "${operation}" failed`,
|
|
58
|
+
}),
|
|
50
59
|
});
|
|
51
60
|
|
|
52
|
-
export const isQueue = <Body>(value: unknown): value is
|
|
61
|
+
export const isQueue = <Body>(value: unknown): value is QueueProducer<Body> => {
|
|
53
62
|
if (typeof value !== "object" || value === null) {
|
|
54
63
|
return false;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
const resource = value as Record<string, unknown>;
|
|
58
67
|
|
|
59
|
-
return
|
|
60
|
-
typeof resource.send === "function" &&
|
|
61
|
-
typeof resource.sendBatch === "function" &&
|
|
62
|
-
typeof resource.metrics === "function"
|
|
63
|
-
);
|
|
68
|
+
return typeof resource.send === "function";
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
67
72
|
definition: QueueBindingDefinition<Message>,
|
|
68
|
-
): ((queue:
|
|
73
|
+
): ((queue: QueueProducer<S.Codec.Encoded<Message>>) => QueueBindingClient<Message>) => {
|
|
69
74
|
type Body = S.Schema.Type<Message>;
|
|
70
75
|
type EncodedBody = S.Codec.Encoded<Message>;
|
|
71
76
|
|
|
@@ -90,12 +95,41 @@ export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
|
90
95
|
});
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
98
|
+
const sendBatch = queue.sendBatch;
|
|
99
|
+
|
|
100
|
+
if (sendBatch !== undefined) {
|
|
101
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", () =>
|
|
102
|
+
sendBatch.call(queue, encodedMessages, options),
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", async () => {
|
|
108
|
+
for (const message of encodedMessages) {
|
|
109
|
+
await queue.send(message.body, {
|
|
110
|
+
contentType: message.contentType,
|
|
111
|
+
delaySeconds: message.delaySeconds ?? options?.delaySeconds,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
96
115
|
}),
|
|
97
|
-
metrics: () =>
|
|
98
|
-
|
|
116
|
+
metrics: () => {
|
|
117
|
+
const metrics = queue.metrics;
|
|
118
|
+
|
|
119
|
+
if (metrics === undefined) {
|
|
120
|
+
return Effect.fail(
|
|
121
|
+
new QueueOperationError({
|
|
122
|
+
binding: definition.binding,
|
|
123
|
+
operation: "metrics",
|
|
124
|
+
cause: new Error(`Queue binding "${definition.binding}" does not provide metrics()`),
|
|
125
|
+
message: `Cloudflare queue binding "${definition.binding}" does not provide metrics()`,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return tryQueuePromise(definition.binding, "metrics", () => metrics.call(queue));
|
|
131
|
+
},
|
|
132
|
+
unsafeRaw: Effect.succeed(queue as globalThis.Queue<EncodedBody>),
|
|
99
133
|
});
|
|
100
134
|
};
|
|
101
135
|
|
|
@@ -106,7 +140,8 @@ export const layer = <Self, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
|
106
140
|
Binding.layer(
|
|
107
141
|
tag,
|
|
108
142
|
definition.binding,
|
|
109
|
-
(value): value is
|
|
143
|
+
(value): value is QueueProducer<S.Codec.Encoded<Message>> =>
|
|
110
144
|
isQueue<S.Codec.Encoded<Message>>(value),
|
|
111
145
|
makeClient(definition),
|
|
146
|
+
{ expected: expectedQueueProducer },
|
|
112
147
|
);
|
package/src/R2.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Context, Data, Effect, Option, type Layer } from "effect";
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
5
5
|
|
|
6
|
+
const expectedR2Bucket =
|
|
7
|
+
"R2 bucket binding with head(), get(), put(), createMultipartUpload(), resumeMultipartUpload(), delete(), and list()";
|
|
8
|
+
|
|
6
9
|
/** Error raised when an R2 operation fails. */
|
|
7
10
|
export class R2OperationError extends Data.TaggedError("R2OperationError")<{
|
|
8
11
|
readonly binding: string;
|
|
@@ -272,7 +275,9 @@ export const makeClient = (
|
|
|
272
275
|
};
|
|
273
276
|
|
|
274
277
|
export const layer = <Self>(tag: Context.Service<Self, R2Client>, definition: R2Definition) =>
|
|
275
|
-
Binding.layer(tag, definition.binding, isR2Bucket, makeClient(definition)
|
|
278
|
+
Binding.layer(tag, definition.binding, isR2Bucket, makeClient(definition), {
|
|
279
|
+
expected: expectedR2Bucket,
|
|
280
|
+
});
|
|
276
281
|
|
|
277
282
|
export const make = <Id extends string>(id: Id) => Tag<R2Service<Id>>()<Id>(id);
|
|
278
283
|
|
package/src/ServiceBinding.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type * as WorkerDefinition from "./WorkerDefinition";
|
|
|
7
7
|
import * as RpcInvocation from "./internal/RpcInvocation";
|
|
8
8
|
|
|
9
9
|
const TypeId = "effect-cf/ServiceBinding" as const;
|
|
10
|
+
const expectedServiceBinding = "Worker service binding with fetch()";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Minimum shape for a Cloudflare service binding.
|
|
@@ -359,6 +360,7 @@ export const layer = <
|
|
|
359
360
|
definition.binding,
|
|
360
361
|
(value): value is ServiceBindingClient<Api> => isServiceBindingClient<Api>(value),
|
|
361
362
|
makeClient<Api, Definition>(definition),
|
|
363
|
+
{ expected: expectedServiceBinding },
|
|
362
364
|
);
|
|
363
365
|
|
|
364
366
|
/**
|
package/src/Workflow.ts
CHANGED
|
@@ -223,7 +223,7 @@ export const step = <A, E, R>(
|
|
|
223
223
|
effect: Effect.Effect<A, E, R>,
|
|
224
224
|
config?: WorkflowStepConfig,
|
|
225
225
|
): Effect.Effect<A, unknown, WorkflowStep | Exclude<R, WorkflowStepContext>> =>
|
|
226
|
-
Effect.flatMap(WorkflowStep
|
|
226
|
+
Effect.flatMap(WorkflowStep, (workflowStep) =>
|
|
227
227
|
workflowStep.do(name, effect, config),
|
|
228
228
|
) as Effect.Effect<A, unknown, WorkflowStep | Exclude<R, WorkflowStepContext>>;
|
|
229
229
|
|
|
@@ -231,15 +231,13 @@ export const sleep = (
|
|
|
231
231
|
name: string,
|
|
232
232
|
duration: WorkflowSleepDuration,
|
|
233
233
|
): Effect.Effect<void, unknown, WorkflowStep> =>
|
|
234
|
-
Effect.flatMap(WorkflowStep
|
|
234
|
+
Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.sleep(name, duration));
|
|
235
235
|
|
|
236
236
|
export const sleepUntil = (
|
|
237
237
|
name: string,
|
|
238
238
|
timestamp: Date | number,
|
|
239
239
|
): Effect.Effect<void, unknown, WorkflowStep> =>
|
|
240
|
-
Effect.flatMap(WorkflowStep
|
|
241
|
-
workflowStep.sleepUntil(name, timestamp),
|
|
242
|
-
);
|
|
240
|
+
Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.sleepUntil(name, timestamp));
|
|
243
241
|
|
|
244
242
|
export const waitForEvent = <Payload>(
|
|
245
243
|
name: string,
|
|
@@ -248,9 +246,7 @@ export const waitForEvent = <Payload>(
|
|
|
248
246
|
readonly timeout?: WorkflowTimeoutDuration | number;
|
|
249
247
|
},
|
|
250
248
|
): Effect.Effect<WorkflowStepEvent<Payload>, unknown, WorkflowStep> =>
|
|
251
|
-
Effect.flatMap(WorkflowStep
|
|
252
|
-
workflowStep.waitForEvent<Payload>(name, options),
|
|
253
|
-
);
|
|
249
|
+
Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.waitForEvent<Payload>(name, options));
|
|
254
250
|
|
|
255
251
|
export type Definition<
|
|
256
252
|
Id extends string = string,
|
package/src/WorkflowBinding.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Context, Data, Effect, Option, Schema as S } from "effect";
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type * as RpcDefinition from "./RpcDefinition";
|
|
5
5
|
|
|
6
|
+
const expectedWorkflow = "Workflow binding with create(), createBatch(), and get()";
|
|
7
|
+
|
|
6
8
|
export type WorkflowInstanceCreateOptions<Payload> = Omit<
|
|
7
9
|
globalThis.WorkflowInstanceCreateOptions<Payload>,
|
|
8
10
|
"params"
|
|
@@ -242,4 +244,5 @@ export const layer = <
|
|
|
242
244
|
(value): value is globalThis.Workflow<S.Codec.Encoded<Payload>> =>
|
|
243
245
|
isWorkflow<S.Codec.Encoded<Payload>>(value),
|
|
244
246
|
makeClient(definition),
|
|
247
|
+
{ expected: expectedWorkflow },
|
|
245
248
|
);
|