effect-cf 0.6.0 → 0.7.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 +8 -0
- package/README.md +30 -29
- package/dist/index.d.mts +837 -1027
- package/dist/index.mjs +328 -407
- package/package.json +1 -1
- package/src/Binding.ts +17 -10
- package/src/DurableObject.ts +117 -23
- package/src/DurableObjectDefinition.ts +251 -72
- package/src/DurableObjectNamespace.ts +239 -287
- package/src/Kv.ts +173 -199
- package/src/Queue.ts +18 -4
- package/src/QueueBinding.ts +45 -97
- package/src/QueueDefinition.ts +87 -41
- package/src/ServiceBinding.ts +260 -116
- package/src/Worker.ts +114 -20
- package/src/WorkerDefinition.ts +187 -63
- package/src/Workflow.ts +23 -4
- package/src/WorkflowBinding.ts +100 -109
- package/src/WorkflowDefinition.ts +116 -51
package/src/Kv.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Data, Effect, Option, Schema as S } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Option, Schema as S, type Layer } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
|
-
|
|
5
|
-
const TypeId = "effect-cf/Kv" as const;
|
|
4
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
6
5
|
|
|
7
6
|
/** Error raised when a KV operation fails. */
|
|
8
7
|
export class KvOperationError extends Data.TaggedError("KvOperationError")<{
|
|
@@ -72,18 +71,6 @@ export namespace Definition {
|
|
|
72
71
|
export type Any = Definition<string, any, any, any>;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
declare const KvServiceTypeId: unique symbol;
|
|
76
|
-
|
|
77
|
-
/** Nominal service marker for KV services created with {@link make}. */
|
|
78
|
-
export interface KvService<Id extends string, Key, Value, EncodedValue> {
|
|
79
|
-
readonly [KvServiceTypeId]: {
|
|
80
|
-
readonly id: Id;
|
|
81
|
-
readonly key: Key;
|
|
82
|
-
readonly value: Value;
|
|
83
|
-
readonly encodedValue: EncodedValue;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
74
|
export interface KvClient<Key, Value, EncodedValue> {
|
|
88
75
|
readonly put: (
|
|
89
76
|
key: Key,
|
|
@@ -106,14 +93,48 @@ export interface KvClient<Key, Value, EncodedValue> {
|
|
|
106
93
|
readonly definition: KvDefinition<Key, Value, EncodedValue>;
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
|
|
96
|
+
export type LayerOptions = {
|
|
97
|
+
readonly binding: string;
|
|
98
|
+
};
|
|
110
99
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
100
|
+
export interface TagClass<
|
|
101
|
+
Self,
|
|
102
|
+
Id extends string,
|
|
103
|
+
Key,
|
|
104
|
+
Value,
|
|
105
|
+
EncodedValue,
|
|
106
|
+
> extends Context.ServiceClass<Self, Id, KvClient<Key, Value, EncodedValue>> {
|
|
107
|
+
readonly id: Id;
|
|
108
|
+
readonly keySchema: S.Codec<Key, string>;
|
|
109
|
+
readonly valueSchema: S.Codec<Value, EncodedValue>;
|
|
110
|
+
readonly layer: (
|
|
111
|
+
options: LayerOptions,
|
|
112
|
+
) => Layer.Layer<
|
|
113
|
+
Self,
|
|
114
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
115
|
+
WorkerEnvironment
|
|
116
|
+
>;
|
|
117
|
+
readonly put: (
|
|
118
|
+
key: Key,
|
|
119
|
+
value: Value,
|
|
120
|
+
options?: KvPutOptions,
|
|
121
|
+
) => Effect.Effect<void, KvOperationError | S.SchemaError, Self>;
|
|
122
|
+
readonly get: (
|
|
123
|
+
key: Key,
|
|
124
|
+
) => Effect.Effect<Option.Option<Value>, KvOperationError | S.SchemaError, Self>;
|
|
125
|
+
readonly getWithMetadata: <Metadata>(
|
|
126
|
+
key: Key,
|
|
127
|
+
metadataSchema: S.Codec<Metadata, unknown>,
|
|
128
|
+
) => Effect.Effect<
|
|
129
|
+
Option.Option<KvWithMetadata<Value, Metadata>>,
|
|
130
|
+
KvOperationError | S.SchemaError,
|
|
131
|
+
Self
|
|
132
|
+
>;
|
|
133
|
+
readonly list: <Metadata = unknown>(
|
|
134
|
+
options?: KvListOptions<Metadata>,
|
|
135
|
+
) => Effect.Effect<KvListResult<Key, Metadata>, KvOperationError | S.SchemaError, Self>;
|
|
136
|
+
readonly remove: (key: Key) => Effect.Effect<void, KvOperationError | S.SchemaError, Self>;
|
|
137
|
+
readonly unsafeRaw: () => Effect.Effect<KVNamespace, never, Self>;
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
const maybeString = (value: string | null | undefined): Option.Option<string> =>
|
|
@@ -135,14 +156,122 @@ const tryKvPromise = <A>(
|
|
|
135
156
|
catch: (cause) => kvError(binding, operation, cause),
|
|
136
157
|
});
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
export const isKvNamespace = (value: unknown): value is KVNamespace => {
|
|
160
|
+
if (typeof value !== "object" || value === null) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const resource = value as Record<string, unknown>;
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
typeof resource.get === "function" &&
|
|
168
|
+
typeof resource.put === "function" &&
|
|
169
|
+
typeof resource.delete === "function" &&
|
|
170
|
+
typeof resource.getWithMetadata === "function" &&
|
|
171
|
+
typeof resource.list === "function"
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const makeClient = <Key, Value, EncodedValue>(
|
|
176
|
+
definition: KvDefinition<Key, Value, EncodedValue>,
|
|
177
|
+
): ((kv: KVNamespace) => KvClient<Key, Value, EncodedValue>) => {
|
|
178
|
+
const encodeKey = S.encodeEffect(definition.key);
|
|
179
|
+
const decodeKey = S.decodeUnknownEffect(definition.key);
|
|
180
|
+
const encodeValue = S.encodeEffect(S.fromJsonString(S.toCodecJson(definition.value)));
|
|
181
|
+
const decodeValue = S.decodeUnknownEffect(S.fromJsonString(S.toCodecJson(definition.value)));
|
|
182
|
+
|
|
183
|
+
return (kv) => ({
|
|
184
|
+
definition,
|
|
185
|
+
put: Effect.fnUntraced(function* (key: Key, value: Value, options?: KvPutOptions) {
|
|
186
|
+
const keyEncoded = yield* encodeKey(key);
|
|
187
|
+
const valueEncoded = yield* encodeValue(value);
|
|
188
|
+
yield* tryKvPromise(definition.binding, "put", () =>
|
|
189
|
+
kv.put(keyEncoded, valueEncoded, options),
|
|
190
|
+
);
|
|
191
|
+
}),
|
|
192
|
+
get: Effect.fnUntraced(function* (key: Key) {
|
|
193
|
+
const keyEncoded = yield* encodeKey(key);
|
|
194
|
+
const valueEncoded = yield* tryKvPromise(definition.binding, "get", () => kv.get(keyEncoded));
|
|
195
|
+
|
|
196
|
+
if (valueEncoded === null) {
|
|
197
|
+
return Option.none();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return yield* decodeValue(valueEncoded).pipe(Effect.map(Option.some));
|
|
201
|
+
}),
|
|
202
|
+
getWithMetadata: Effect.fnUntraced(function* <Metadata>(
|
|
203
|
+
key: Key,
|
|
204
|
+
metadataSchema: S.Codec<Metadata, unknown>,
|
|
205
|
+
) {
|
|
206
|
+
const keyEncoded = yield* encodeKey(key);
|
|
207
|
+
const result = yield* tryKvPromise(definition.binding, "getWithMetadata", () =>
|
|
208
|
+
kv.getWithMetadata<Metadata>(keyEncoded),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (result.value === null) {
|
|
212
|
+
return Option.none();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const value = yield* decodeValue(result.value);
|
|
216
|
+
const metadata =
|
|
217
|
+
result.metadata === null
|
|
218
|
+
? Option.none<Metadata>()
|
|
219
|
+
: Option.some(yield* S.decodeUnknownEffect(metadataSchema)(result.metadata));
|
|
220
|
+
|
|
221
|
+
return Option.some({
|
|
222
|
+
value,
|
|
223
|
+
metadata,
|
|
224
|
+
cacheStatus: maybeString(result.cacheStatus),
|
|
225
|
+
});
|
|
226
|
+
}),
|
|
227
|
+
list: Effect.fnUntraced(function* <Metadata = unknown>(options?: KvListOptions<Metadata>) {
|
|
228
|
+
const { metadataSchema, ...kvOptions } = options ?? {};
|
|
229
|
+
const result = yield* tryKvPromise(definition.binding, "list", () =>
|
|
230
|
+
kv.list<Metadata>(kvOptions),
|
|
231
|
+
);
|
|
232
|
+
const keys: Array<KvListKey<Key, Metadata>> = [];
|
|
233
|
+
|
|
234
|
+
for (const key of result.keys) {
|
|
235
|
+
const decodedName = yield* decodeKey(key.name);
|
|
236
|
+
const decodedMetadata =
|
|
237
|
+
key.metadata === undefined
|
|
238
|
+
? Option.none<Metadata>()
|
|
239
|
+
: metadataSchema === undefined
|
|
240
|
+
? Option.some(key.metadata as Metadata)
|
|
241
|
+
: Option.some(yield* S.decodeUnknownEffect(metadataSchema)(key.metadata));
|
|
242
|
+
|
|
243
|
+
keys.push({
|
|
244
|
+
name: decodedName,
|
|
245
|
+
expiration: maybeNumber(key.expiration),
|
|
246
|
+
metadata: decodedMetadata,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
keys,
|
|
252
|
+
listComplete: result.list_complete,
|
|
253
|
+
cursor: maybeString("cursor" in result ? result.cursor : undefined),
|
|
254
|
+
cacheStatus: maybeString(result.cacheStatus),
|
|
255
|
+
};
|
|
256
|
+
}),
|
|
257
|
+
remove: Effect.fnUntraced(function* (key: Key) {
|
|
258
|
+
const keyEncoded = yield* encodeKey(key);
|
|
259
|
+
yield* tryKvPromise(definition.binding, "delete", () => kv.delete(keyEncoded));
|
|
260
|
+
}),
|
|
261
|
+
unsafeRaw: Effect.succeed(kv),
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const layer = <Self, Key, Value, EncodedValue>(
|
|
266
|
+
tag: Context.Service<Self, KvClient<Key, Value, EncodedValue>>,
|
|
143
267
|
definition: KvDefinition<Key, Value, EncodedValue>,
|
|
144
268
|
) =>
|
|
145
|
-
|
|
269
|
+
Binding.layer(
|
|
270
|
+
tag,
|
|
271
|
+
definition.binding,
|
|
272
|
+
isKvNamespace,
|
|
273
|
+
makeClient<Key, Value, EncodedValue>(definition),
|
|
274
|
+
);
|
|
146
275
|
|
|
147
276
|
const makeDefinition = <Id extends string, Key, Value, EncodedValue>(
|
|
148
277
|
id: Id,
|
|
@@ -156,34 +285,20 @@ const makeDefinition = <Id extends string, Key, Value, EncodedValue>(
|
|
|
156
285
|
};
|
|
157
286
|
|
|
158
287
|
return Object.assign(kvDefinition, {
|
|
159
|
-
|
|
160
|
-
<Self
|
|
161
|
-
|
|
162
|
-
bindingId: BindingId,
|
|
163
|
-
binding: Omit<KvDefinition<Key, Value, EncodedValue>, "key" | "value">,
|
|
164
|
-
) =>
|
|
165
|
-
Service<Self>()<BindingId, Key, Value, EncodedValue>(bindingId, {
|
|
166
|
-
...binding,
|
|
167
|
-
key: definition.key,
|
|
168
|
-
value: definition.value,
|
|
169
|
-
}),
|
|
170
|
-
binding: <BindingId extends string>(
|
|
171
|
-
bindingId: BindingId,
|
|
172
|
-
binding: Omit<KvDefinition<Key, Value, EncodedValue>, "key" | "value">,
|
|
288
|
+
layer: <Self>(
|
|
289
|
+
tag: Context.Service<Self, KvClient<Key, Value, EncodedValue>>,
|
|
290
|
+
binding: LayerOptions,
|
|
173
291
|
) =>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
value: definition.value,
|
|
180
|
-
},
|
|
181
|
-
),
|
|
292
|
+
layer(tag, {
|
|
293
|
+
...binding,
|
|
294
|
+
key: definition.key,
|
|
295
|
+
value: definition.value,
|
|
296
|
+
}),
|
|
182
297
|
});
|
|
183
298
|
};
|
|
184
299
|
|
|
185
300
|
export const Tag =
|
|
186
|
-
<
|
|
301
|
+
<Self>() =>
|
|
187
302
|
<Id extends string, Key, Value, EncodedValue>(
|
|
188
303
|
id: Id,
|
|
189
304
|
definition: {
|
|
@@ -192,152 +307,9 @@ export const Tag =
|
|
|
192
307
|
},
|
|
193
308
|
) => {
|
|
194
309
|
const kvDefinition = makeDefinition(id, definition);
|
|
310
|
+
const tag = Context.Service<Self, KvClient<Key, Value, EncodedValue>>()(id);
|
|
195
311
|
|
|
196
|
-
|
|
197
|
-
static readonly id = kvDefinition.id;
|
|
198
|
-
static readonly key = kvDefinition.key;
|
|
199
|
-
static readonly value = kvDefinition.value;
|
|
200
|
-
static readonly Binding = kvDefinition.Binding;
|
|
201
|
-
static readonly binding = kvDefinition.binding;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return KvDefinitionClass as (abstract new () => object) & typeof kvDefinition;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Builds a KV service around a Cloudflare KV namespace binding.
|
|
209
|
-
*
|
|
210
|
-
* Returned service includes schema-aware `put`, `get`, `getWithMetadata`,
|
|
211
|
-
* `list`, and `remove` helpers.
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```ts
|
|
215
|
-
* const Sessions = Kv.make("Sessions", {
|
|
216
|
-
* binding: "SESSIONS",
|
|
217
|
-
* key: Schema.String,
|
|
218
|
-
* value: Schema.Struct({ userId: Schema.String }),
|
|
219
|
-
* });
|
|
220
|
-
*
|
|
221
|
-
* const program = Effect.gen(function* () {
|
|
222
|
-
* yield* Sessions.put("abc", { userId: "u_1" });
|
|
223
|
-
* return yield* Sessions.get("abc");
|
|
224
|
-
* });
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
export const Service =
|
|
228
|
-
<Self>() =>
|
|
229
|
-
<Id extends string, Key, Value, EncodedValue>(
|
|
230
|
-
id: Id,
|
|
231
|
-
definition: KvDefinition<Key, Value, EncodedValue>,
|
|
232
|
-
) => {
|
|
233
|
-
const encodeKey = S.encodeEffect(definition.key);
|
|
234
|
-
const decodeKey = S.decodeUnknownEffect(definition.key);
|
|
235
|
-
const encodeValue = S.encodeEffect(S.fromJsonString(S.toCodecJson(definition.value)));
|
|
236
|
-
const decodeValue = S.decodeUnknownEffect(S.fromJsonString(S.toCodecJson(definition.value)));
|
|
237
|
-
|
|
238
|
-
const makeClient = (kv: KVNamespace): KvClient<Key, Value, EncodedValue> => ({
|
|
239
|
-
definition,
|
|
240
|
-
put: Effect.fnUntraced(function* (key: Key, value: Value, options?: KvPutOptions) {
|
|
241
|
-
const keyEncoded = yield* encodeKey(key);
|
|
242
|
-
const valueEncoded = yield* encodeValue(value);
|
|
243
|
-
yield* tryKvPromise(definition.binding, "put", () =>
|
|
244
|
-
kv.put(keyEncoded, valueEncoded, options),
|
|
245
|
-
);
|
|
246
|
-
}),
|
|
247
|
-
get: Effect.fnUntraced(function* (key: Key) {
|
|
248
|
-
const keyEncoded = yield* encodeKey(key);
|
|
249
|
-
const valueEncoded = yield* tryKvPromise(definition.binding, "get", () =>
|
|
250
|
-
kv.get(keyEncoded),
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
if (valueEncoded === null) {
|
|
254
|
-
return Option.none();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return yield* decodeValue(valueEncoded).pipe(Effect.map(Option.some));
|
|
258
|
-
}),
|
|
259
|
-
getWithMetadata: Effect.fnUntraced(function* <Metadata>(
|
|
260
|
-
key: Key,
|
|
261
|
-
metadataSchema: S.Codec<Metadata, unknown>,
|
|
262
|
-
) {
|
|
263
|
-
const keyEncoded = yield* encodeKey(key);
|
|
264
|
-
const result = yield* tryKvPromise(definition.binding, "getWithMetadata", () =>
|
|
265
|
-
kv.getWithMetadata<Metadata>(keyEncoded),
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
if (result.value === null) {
|
|
269
|
-
return Option.none();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const value = yield* decodeValue(result.value);
|
|
273
|
-
const metadata =
|
|
274
|
-
result.metadata === null
|
|
275
|
-
? Option.none<Metadata>()
|
|
276
|
-
: Option.some(yield* S.decodeUnknownEffect(metadataSchema)(result.metadata));
|
|
277
|
-
|
|
278
|
-
return Option.some({
|
|
279
|
-
value,
|
|
280
|
-
metadata,
|
|
281
|
-
cacheStatus: maybeString(result.cacheStatus),
|
|
282
|
-
});
|
|
283
|
-
}),
|
|
284
|
-
list: Effect.fnUntraced(function* <Metadata = unknown>(options?: KvListOptions<Metadata>) {
|
|
285
|
-
const { metadataSchema, ...kvOptions } = options ?? {};
|
|
286
|
-
const result = yield* tryKvPromise(definition.binding, "list", () =>
|
|
287
|
-
kv.list<Metadata>(kvOptions),
|
|
288
|
-
);
|
|
289
|
-
const keys: Array<KvListKey<Key, Metadata>> = [];
|
|
290
|
-
|
|
291
|
-
for (const key of result.keys) {
|
|
292
|
-
const decodedName = yield* decodeKey(key.name);
|
|
293
|
-
const decodedMetadata =
|
|
294
|
-
key.metadata === undefined
|
|
295
|
-
? Option.none<Metadata>()
|
|
296
|
-
: metadataSchema === undefined
|
|
297
|
-
? Option.some(key.metadata as Metadata)
|
|
298
|
-
: Option.some(yield* S.decodeUnknownEffect(metadataSchema)(key.metadata));
|
|
299
|
-
|
|
300
|
-
keys.push({
|
|
301
|
-
name: decodedName,
|
|
302
|
-
expiration: maybeNumber(key.expiration),
|
|
303
|
-
metadata: decodedMetadata,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
keys,
|
|
309
|
-
listComplete: result.list_complete,
|
|
310
|
-
cursor: maybeString("cursor" in result ? result.cursor : undefined),
|
|
311
|
-
cacheStatus: maybeString(result.cacheStatus),
|
|
312
|
-
};
|
|
313
|
-
}),
|
|
314
|
-
remove: Effect.fnUntraced(function* (key: Key) {
|
|
315
|
-
const keyEncoded = yield* encodeKey(key);
|
|
316
|
-
yield* tryKvPromise(definition.binding, "delete", () => kv.delete(keyEncoded));
|
|
317
|
-
}),
|
|
318
|
-
unsafeRaw: Effect.succeed(kv),
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const tag = Binding.Service<Self>()(
|
|
322
|
-
id,
|
|
323
|
-
definition.binding,
|
|
324
|
-
(value): value is KVNamespace => {
|
|
325
|
-
if (typeof value !== "object" || value === null) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const resource = value as Record<string, unknown>;
|
|
330
|
-
|
|
331
|
-
return (
|
|
332
|
-
typeof resource.get === "function" &&
|
|
333
|
-
typeof resource.put === "function" &&
|
|
334
|
-
typeof resource.delete === "function" &&
|
|
335
|
-
typeof resource.getWithMetadata === "function" &&
|
|
336
|
-
typeof resource.list === "function"
|
|
337
|
-
);
|
|
338
|
-
},
|
|
339
|
-
makeClient,
|
|
340
|
-
);
|
|
312
|
+
const makeLayer = (binding: LayerOptions) => kvDefinition.layer(tag, binding);
|
|
341
313
|
|
|
342
314
|
const put = Effect.fnUntraced(function* (key: Key, value: Value, options?: KvPutOptions) {
|
|
343
315
|
const kv = yield* tag;
|
|
@@ -375,13 +347,15 @@ export const Service =
|
|
|
375
347
|
});
|
|
376
348
|
|
|
377
349
|
return Object.assign(tag, {
|
|
378
|
-
|
|
379
|
-
|
|
350
|
+
id: kvDefinition.id,
|
|
351
|
+
keySchema: kvDefinition.key,
|
|
352
|
+
valueSchema: kvDefinition.value,
|
|
353
|
+
layer: makeLayer,
|
|
380
354
|
put,
|
|
381
355
|
get,
|
|
382
356
|
getWithMetadata,
|
|
383
357
|
list,
|
|
384
358
|
remove,
|
|
385
359
|
unsafeRaw,
|
|
386
|
-
})
|
|
360
|
+
}) as TagClass<Self, Id, Key, Value, EncodedValue>;
|
|
387
361
|
};
|
package/src/Queue.ts
CHANGED
|
@@ -91,10 +91,6 @@ export const decodeBatch = <Body>(
|
|
|
91
91
|
return fromMessageBatch(batch, messages);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
export const Tag = QueueDefinition.Tag;
|
|
95
|
-
|
|
96
|
-
export const implement = QueueDefinition.implement;
|
|
97
|
-
|
|
98
94
|
export type Definition<
|
|
99
95
|
Id extends string = string,
|
|
100
96
|
Message extends QueueDefinition.Definition.Any["message"] =
|
|
@@ -105,6 +101,24 @@ export namespace Definition {
|
|
|
105
101
|
export type Any = QueueDefinition.Definition.Any;
|
|
106
102
|
}
|
|
107
103
|
|
|
104
|
+
export type LayerOptions = QueueDefinition.LayerOptions;
|
|
105
|
+
|
|
106
|
+
export type TagClass<
|
|
107
|
+
Self,
|
|
108
|
+
Id extends string,
|
|
109
|
+
Message extends QueueDefinition.Definition.Any["message"],
|
|
110
|
+
> = QueueDefinition.TagClass<Self, Id, Message>;
|
|
111
|
+
|
|
112
|
+
export const Tag: <Self>() => <
|
|
113
|
+
Id extends string,
|
|
114
|
+
Message extends QueueDefinition.Definition.Any["message"],
|
|
115
|
+
>(
|
|
116
|
+
id: Id,
|
|
117
|
+
definition: { readonly message: Message },
|
|
118
|
+
) => TagClass<Self, Id, Message> = QueueDefinition.Tag;
|
|
119
|
+
|
|
120
|
+
export const implement = QueueDefinition.implement;
|
|
121
|
+
|
|
108
122
|
export type Handler<ROut, Self extends QueueDefinition.Definition.Any> = QueueDefinition.Handler<
|
|
109
123
|
ROut,
|
|
110
124
|
Self
|
package/src/QueueBinding.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { Data, Effect, Schema as S } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Schema as S } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type * as RpcDefinition from "./RpcDefinition";
|
|
5
5
|
|
|
6
|
-
const TypeId = "effect-cf/QueueBinding" as const;
|
|
7
|
-
|
|
8
6
|
export type QueueSendOptions = globalThis.QueueSendOptions;
|
|
9
7
|
export type QueueSendResponse = globalThis.QueueSendResponse;
|
|
10
8
|
export type QueueSendBatchOptions = globalThis.QueueSendBatchOptions;
|
|
@@ -32,17 +30,6 @@ export interface QueueBindingClient<Message extends RpcDefinition.ServiceFreeSch
|
|
|
32
30
|
readonly unsafeRaw: Effect.Effect<globalThis.Queue<S.Codec.Encoded<Message>>>;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
declare const QueueServiceTypeId: unique symbol;
|
|
36
|
-
|
|
37
|
-
/** Nominal service marker for Queue services created with {@link make}. */
|
|
38
|
-
export interface QueueService<Id extends string, Message extends RpcDefinition.ServiceFreeSchema> {
|
|
39
|
-
readonly [QueueServiceTypeId]: {
|
|
40
|
-
readonly id: Id;
|
|
41
|
-
readonly message: S.Schema.Type<Message>;
|
|
42
|
-
readonly encodedMessage: S.Codec.Encoded<Message>;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
33
|
export class QueueOperationError extends Data.TaggedError("QueueOperationError")<{
|
|
47
34
|
readonly binding: string;
|
|
48
35
|
readonly operation: string;
|
|
@@ -62,7 +49,7 @@ const tryQueuePromise = <A>(
|
|
|
62
49
|
catch: (cause) => queueError(binding, operation, cause),
|
|
63
50
|
});
|
|
64
51
|
|
|
65
|
-
const isQueue = <Body>(value: unknown): value is globalThis.Queue<Body> => {
|
|
52
|
+
export const isQueue = <Body>(value: unknown): value is globalThis.Queue<Body> => {
|
|
66
53
|
if (typeof value !== "object" || value === null) {
|
|
67
54
|
return false;
|
|
68
55
|
}
|
|
@@ -76,89 +63,50 @@ const isQueue = <Body>(value: unknown): value is globalThis.Queue<Body> => {
|
|
|
76
63
|
);
|
|
77
64
|
};
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
export const make = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
81
|
-
id: Id,
|
|
66
|
+
export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
|
|
82
67
|
definition: QueueBindingDefinition<Message>,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const encodeMessage = S.encodeEffect(definition.message);
|
|
98
|
-
|
|
99
|
-
const makeClient = (queue: globalThis.Queue<EncodedBody>): QueueBindingClient<Message> => ({
|
|
100
|
-
send: Effect.fnUntraced(function* (message: Body, options?: QueueSendOptions) {
|
|
101
|
-
const encoded = yield* encodeMessage(message);
|
|
102
|
-
|
|
103
|
-
yield* tryQueuePromise(definition.binding, "send", () => queue.send(encoded, options));
|
|
104
|
-
}),
|
|
105
|
-
sendBatch: Effect.fnUntraced(function* (
|
|
106
|
-
messages: Iterable<MessageSendRequest<Body>>,
|
|
107
|
-
options?: QueueSendBatchOptions,
|
|
108
|
-
) {
|
|
109
|
-
const encodedMessages: Array<MessageSendRequest<EncodedBody>> = [];
|
|
110
|
-
|
|
111
|
-
for (const message of messages) {
|
|
112
|
-
encodedMessages.push({
|
|
113
|
-
...message,
|
|
114
|
-
body: yield* encodeMessage(message.body),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
yield* tryQueuePromise(definition.binding, "sendBatch", () =>
|
|
119
|
-
queue.sendBatch(encodedMessages, options),
|
|
120
|
-
);
|
|
121
|
-
}),
|
|
122
|
-
metrics: () => tryQueuePromise(definition.binding, "metrics", () => queue.metrics()),
|
|
123
|
-
unsafeRaw: Effect.succeed(queue),
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const tag = Binding.Service<Self>()(
|
|
127
|
-
id,
|
|
128
|
-
definition.binding,
|
|
129
|
-
(value): value is globalThis.Queue<EncodedBody> => isQueue<EncodedBody>(value),
|
|
130
|
-
makeClient,
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const send = Effect.fnUntraced(function* (message: Body, options?: QueueSendOptions) {
|
|
134
|
-
const queue = yield* tag;
|
|
135
|
-
yield* queue.send(message, options);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const sendBatch = Effect.fnUntraced(function* (
|
|
68
|
+
): ((queue: globalThis.Queue<S.Codec.Encoded<Message>>) => QueueBindingClient<Message>) => {
|
|
69
|
+
type Body = S.Schema.Type<Message>;
|
|
70
|
+
type EncodedBody = S.Codec.Encoded<Message>;
|
|
71
|
+
|
|
72
|
+
const encodeMessage = S.encodeEffect(definition.message);
|
|
73
|
+
|
|
74
|
+
return (queue) => ({
|
|
75
|
+
send: Effect.fnUntraced(function* (message: Body, options?: QueueSendOptions) {
|
|
76
|
+
const encoded = yield* encodeMessage(message);
|
|
77
|
+
|
|
78
|
+
yield* tryQueuePromise(definition.binding, "send", () => queue.send(encoded, options));
|
|
79
|
+
}),
|
|
80
|
+
sendBatch: Effect.fnUntraced(function* (
|
|
139
81
|
messages: Iterable<MessageSendRequest<Body>>,
|
|
140
82
|
options?: QueueSendBatchOptions,
|
|
141
83
|
) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
84
|
+
const encodedMessages: Array<MessageSendRequest<EncodedBody>> = [];
|
|
85
|
+
|
|
86
|
+
for (const message of messages) {
|
|
87
|
+
encodedMessages.push({
|
|
88
|
+
...message,
|
|
89
|
+
body: yield* encodeMessage(message.body),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
yield* tryQueuePromise(definition.binding, "sendBatch", () =>
|
|
94
|
+
queue.sendBatch(encodedMessages, options),
|
|
95
|
+
);
|
|
96
|
+
}),
|
|
97
|
+
metrics: () => tryQueuePromise(definition.binding, "metrics", () => queue.metrics()),
|
|
98
|
+
unsafeRaw: Effect.succeed(queue),
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const layer = <Self, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
103
|
+
tag: Context.Service<Self, QueueBindingClient<Message>>,
|
|
104
|
+
definition: QueueBindingDefinition<Message>,
|
|
105
|
+
) =>
|
|
106
|
+
Binding.layer(
|
|
107
|
+
tag,
|
|
108
|
+
definition.binding,
|
|
109
|
+
(value): value is globalThis.Queue<S.Codec.Encoded<Message>> =>
|
|
110
|
+
isQueue<S.Codec.Encoded<Message>>(value),
|
|
111
|
+
makeClient(definition),
|
|
112
|
+
);
|