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
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Data, Effect, type Context, type Scope } from "effect";
|
|
1
|
+
import { Data, Effect, type Context, type Layer, type Scope } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
4
5
|
import * as CloudflareRpc from "./Rpc";
|
|
5
6
|
import type * as DurableObjectDefinition from "./DurableObjectDefinition";
|
|
6
7
|
import * as RpcDefinition from "./RpcDefinition";
|
|
8
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
7
9
|
import * as RpcInvocation from "./internal/RpcInvocation";
|
|
8
10
|
|
|
9
11
|
const expectedDurableObjectNamespace =
|
|
@@ -86,7 +88,11 @@ export class DurableObjectRpcError extends Data.TaggedError("DurableObjectRpcErr
|
|
|
86
88
|
readonly binding: string;
|
|
87
89
|
readonly method: string;
|
|
88
90
|
readonly cause: unknown;
|
|
89
|
-
}> {
|
|
91
|
+
}> {
|
|
92
|
+
override get message(): string {
|
|
93
|
+
return `Durable Object RPC method "${this.method}" failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
90
96
|
|
|
91
97
|
/**
|
|
92
98
|
* Failure raised when forwarding a request to a Durable Object stub.
|
|
@@ -94,7 +100,11 @@ export class DurableObjectRpcError extends Data.TaggedError("DurableObjectRpcErr
|
|
|
94
100
|
export class DurableObjectFetchError extends Data.TaggedError("DurableObjectFetchError")<{
|
|
95
101
|
readonly binding: string;
|
|
96
102
|
readonly cause: unknown;
|
|
97
|
-
}> {
|
|
103
|
+
}> {
|
|
104
|
+
override get message(): string {
|
|
105
|
+
return `Durable Object fetch failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
type StubMethodKey<Api> = RpcInvocation.AsyncMethodKey<Api>;
|
|
100
110
|
type StubMethodArgs<Api, Method extends keyof Api> = RpcInvocation.AsyncMethodArgs<Api, Method>;
|
|
@@ -316,7 +326,7 @@ export type DurableObjectNamespaceEffectClient<
|
|
|
316
326
|
* Prefer the typed helpers above unless Cloudflare exposes a platform feature
|
|
317
327
|
* that is not wrapped by effect-cf yet.
|
|
318
328
|
*/
|
|
319
|
-
readonly
|
|
329
|
+
readonly rawUnsafe: Effect.Effect<DurableObjectNamespaceClient<Api>>;
|
|
320
330
|
};
|
|
321
331
|
|
|
322
332
|
export type DurableObjectNamespaceStaticClient<
|
|
@@ -360,7 +370,7 @@ export type DurableObjectNamespaceStaticClient<
|
|
|
360
370
|
method: Method,
|
|
361
371
|
...args: StubMethodArgs<Api, Method>
|
|
362
372
|
) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
|
|
363
|
-
readonly
|
|
373
|
+
readonly rawUnsafe: () => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
|
|
364
374
|
};
|
|
365
375
|
|
|
366
376
|
const hasFunction = (value: object, key: string): boolean =>
|
|
@@ -416,104 +426,93 @@ export const makeClient = <
|
|
|
416
426
|
catch: (cause) => new DurableObjectFetchError({ binding: definition.binding, cause }),
|
|
417
427
|
});
|
|
418
428
|
|
|
419
|
-
const rpc =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
429
|
+
const rpc = Effect.fn("DurableObjectNamespace.rpc")(function* <
|
|
430
|
+
Method extends StubMethodKey<Api>,
|
|
431
|
+
>(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
|
|
432
|
+
const methodName = String(method);
|
|
433
|
+
const encodedArgs =
|
|
434
|
+
definition.definition === undefined
|
|
435
|
+
? args
|
|
436
|
+
: yield* RpcDefinition.encodeArgs(
|
|
437
|
+
definition.definition,
|
|
438
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
439
|
+
args as never,
|
|
440
|
+
).pipe(
|
|
441
|
+
Effect.mapError(
|
|
442
|
+
(cause) =>
|
|
443
|
+
new DurableObjectRpcError({
|
|
444
|
+
binding: definition.binding,
|
|
445
|
+
method: methodName,
|
|
446
|
+
cause,
|
|
447
|
+
}),
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return yield* RpcInvocation.invokeRpcMethod(
|
|
452
|
+
stub,
|
|
453
|
+
method,
|
|
454
|
+
encodedArgs as StubMethodArgs<Api, Method>,
|
|
455
|
+
(cause) =>
|
|
456
|
+
new DurableObjectRpcError({
|
|
457
|
+
binding: definition.binding,
|
|
458
|
+
method: methodName,
|
|
459
|
+
cause,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const decodeSuccess = Effect.fn("DurableObjectNamespace.decodeSuccess")(function* <
|
|
465
|
+
Method extends StubMethodKey<Api>,
|
|
466
|
+
>(methodName: string, value: Awaited<StubMethodCloudflareReturn<Api, Method>>) {
|
|
467
|
+
if (definition.definition === undefined) {
|
|
468
|
+
return value as StubMethodSuccess<Api, Method>;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
472
|
+
definition.definition,
|
|
473
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
474
|
+
value,
|
|
475
|
+
).pipe(
|
|
476
|
+
Effect.mapError(
|
|
448
477
|
(cause) =>
|
|
449
478
|
new DurableObjectRpcError({
|
|
450
479
|
binding: definition.binding,
|
|
451
480
|
method: methodName,
|
|
452
481
|
cause,
|
|
453
482
|
}),
|
|
454
|
-
)
|
|
455
|
-
|
|
483
|
+
),
|
|
484
|
+
);
|
|
456
485
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
value: Awaited<StubMethodCloudflareReturn<Api, Method>>,
|
|
460
|
-
) =>
|
|
461
|
-
Effect.gen(function* () {
|
|
462
|
-
if (definition.definition === undefined) {
|
|
463
|
-
return value as StubMethodSuccess<Api, Method>;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
467
|
-
definition.definition,
|
|
468
|
-
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
469
|
-
value,
|
|
470
|
-
).pipe(
|
|
471
|
-
Effect.mapError(
|
|
472
|
-
(cause) =>
|
|
473
|
-
new DurableObjectRpcError({
|
|
474
|
-
binding: definition.binding,
|
|
475
|
-
method: methodName,
|
|
476
|
-
cause,
|
|
477
|
-
}),
|
|
478
|
-
),
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
return decoded as StubMethodSuccess<Api, Method>;
|
|
482
|
-
});
|
|
486
|
+
return decoded as StubMethodSuccess<Api, Method>;
|
|
487
|
+
});
|
|
483
488
|
|
|
484
|
-
const call =
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}),
|
|
499
|
-
),
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
return yield* decodeSuccess<Method>(methodName, value);
|
|
503
|
-
});
|
|
489
|
+
const call = Effect.fn("DurableObjectNamespace.call")(function* <
|
|
490
|
+
Method extends StubMethodKey<Api>,
|
|
491
|
+
>(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
|
|
492
|
+
const methodName = String(method);
|
|
493
|
+
const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
|
|
494
|
+
Effect.mapError(
|
|
495
|
+
(cause) =>
|
|
496
|
+
new DurableObjectRpcError({
|
|
497
|
+
binding: definition.binding,
|
|
498
|
+
method: methodName,
|
|
499
|
+
cause,
|
|
500
|
+
}),
|
|
501
|
+
),
|
|
502
|
+
);
|
|
504
503
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
504
|
+
return yield* decodeSuccess<Method>(methodName, value);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const scopedCall = Effect.fn("DurableObjectNamespace.scopedCall")(function* <
|
|
508
|
+
Method extends StubMethodKey<Api>,
|
|
509
|
+
>(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
|
|
510
|
+
const methodName = String(method);
|
|
511
|
+
const result = yield* rpc(stub, method, ...args);
|
|
512
|
+
const value = yield* CloudflareRpc.scoped(result);
|
|
513
|
+
|
|
514
|
+
return yield* decodeSuccess<Method>(methodName, value);
|
|
515
|
+
});
|
|
517
516
|
|
|
518
517
|
const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, {
|
|
519
518
|
call,
|
|
@@ -533,7 +532,7 @@ export const makeClient = <
|
|
|
533
532
|
rpc,
|
|
534
533
|
call,
|
|
535
534
|
scopedCall,
|
|
536
|
-
|
|
535
|
+
rawUnsafe: Effect.succeed(namespace),
|
|
537
536
|
}) as DurableObjectNamespaceEffectClient<Api, Definition>;
|
|
538
537
|
};
|
|
539
538
|
};
|
|
@@ -545,7 +544,11 @@ export const layer = <
|
|
|
545
544
|
>(
|
|
546
545
|
tag: Context.Service<Self, DurableObjectNamespaceEffectClient<Api, Definition>>,
|
|
547
546
|
definition: DurableObjectNamespaceBindingDefinition<Definition>,
|
|
548
|
-
)
|
|
547
|
+
): Layer.Layer<
|
|
548
|
+
Self,
|
|
549
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
550
|
+
WorkerEnvironment
|
|
551
|
+
> =>
|
|
549
552
|
Binding.layer(
|
|
550
553
|
tag,
|
|
551
554
|
definition.binding,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Effect, Layer,
|
|
1
|
+
import { Context, Effect, Layer, Predicate, Queue } from "effect";
|
|
2
2
|
import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
|
3
3
|
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
4
4
|
import * as RpcServer from "effect/unstable/rpc/RpcServer";
|
|
@@ -52,7 +52,13 @@ export class DurableObjectRpcWebSocket extends Context.Service<
|
|
|
52
52
|
/**
|
|
53
53
|
* Builds a layer that bridges Durable Object websocket events to `RpcServer.Protocol`.
|
|
54
54
|
*/
|
|
55
|
-
export const layer = (
|
|
55
|
+
export const layer = (
|
|
56
|
+
options: LayerOptions = {},
|
|
57
|
+
): Layer.Layer<
|
|
58
|
+
RpcServer.Protocol | DurableObjectRpcWebSocket,
|
|
59
|
+
never,
|
|
60
|
+
DurableObjectState | RpcSerialization.RpcSerialization
|
|
61
|
+
> =>
|
|
56
62
|
Layer.effectContext(
|
|
57
63
|
Effect.gen(function* () {
|
|
58
64
|
const tag = options.tag ?? defaultTag;
|
|
@@ -156,7 +162,7 @@ export const layer = (options: LayerOptions = {}) =>
|
|
|
156
162
|
connection?.socket.raw.close();
|
|
157
163
|
}),
|
|
158
164
|
clientIds: Effect.sync(() => clientIds),
|
|
159
|
-
initialMessage: Effect.
|
|
165
|
+
initialMessage: Effect.succeedNone,
|
|
160
166
|
supportsAck: true,
|
|
161
167
|
supportsTransferables: false,
|
|
162
168
|
supportsSpanPropagation: true,
|
|
@@ -174,7 +180,7 @@ export const layer = (options: LayerOptions = {}) =>
|
|
|
174
180
|
const connection = register(socket);
|
|
175
181
|
const decoded = yield* Effect.try({
|
|
176
182
|
try: () => connection.parser.decode(normalizeMessage(message)),
|
|
177
|
-
catch:
|
|
183
|
+
catch: (cause) => cause,
|
|
178
184
|
});
|
|
179
185
|
|
|
180
186
|
const run = writeRequest;
|
|
@@ -189,10 +195,12 @@ export const layer = (options: LayerOptions = {}) =>
|
|
|
189
195
|
yield* run(connection.id, current as RpcMessage.FromClientEncoded);
|
|
190
196
|
}
|
|
191
197
|
}).pipe(
|
|
192
|
-
Effect.catch((
|
|
198
|
+
Effect.catch((cause) => {
|
|
193
199
|
const connection = register(socket);
|
|
194
200
|
|
|
195
|
-
return
|
|
201
|
+
return Predicate.isTagged(cause, "MaxBufferSizeExceeded")
|
|
202
|
+
? Effect.ignore(connection.socket.close(1009, String(cause)))
|
|
203
|
+
: send(connection, RpcMessage.ResponseDefectEncoded(cause));
|
|
196
204
|
}),
|
|
197
205
|
),
|
|
198
206
|
close: unregister,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
2
2
|
import { Effect, Layer } from "effect";
|
|
3
|
+
import type { SqlClient } from "effect/unstable/sql";
|
|
3
4
|
|
|
4
5
|
import { DurableObjectState } from "./DurableObjectState";
|
|
5
6
|
|
|
6
7
|
/** Options forwarded to `@effect/sql-sqlite-do` when building a SQL client layer. */
|
|
7
|
-
export type SqliteLayerOptions = Omit<SqliteClient.SqliteClientConfig, "db">;
|
|
8
|
+
export type SqliteLayerOptions = Omit<SqliteClient.SqliteClientConfig, "db" | "storage">;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Provides `effect/unstable/sql` from the current Durable Object SQLite storage.
|
|
@@ -17,11 +18,13 @@ export type SqliteLayerOptions = Omit<SqliteClient.SqliteClientConfig, "db">;
|
|
|
17
18
|
* );
|
|
18
19
|
* ```
|
|
19
20
|
*/
|
|
20
|
-
export const layer = (
|
|
21
|
+
export const layer = (
|
|
22
|
+
options?: SqliteLayerOptions,
|
|
23
|
+
): Layer.Layer<SqliteClient.SqliteClient | SqlClient.SqlClient, never, DurableObjectState> =>
|
|
21
24
|
Layer.unwrap(
|
|
22
25
|
Effect.gen(function* () {
|
|
23
26
|
const state = yield* DurableObjectState;
|
|
24
27
|
|
|
25
|
-
return SqliteClient.layer({ ...options,
|
|
28
|
+
return SqliteClient.layer({ ...options, storage: state.raw.storage });
|
|
26
29
|
}),
|
|
27
30
|
);
|
|
@@ -2,6 +2,8 @@ import { Cause, Context, Effect, Exit } from "effect";
|
|
|
2
2
|
|
|
3
3
|
import { fromDurableObjectStorage, type DurableObjectStorage } from "./DurableObjectStorage";
|
|
4
4
|
import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
5
|
+
import type { WorkerContextWaitUntilOptions } from "./Worker";
|
|
6
|
+
import { makeWaitUntilScheduler } from "./internal/WorkerContext";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Effect-friendly wrapper around Cloudflare `DurableObjectState`.
|
|
@@ -15,6 +17,16 @@ export interface DurableObjectStateService {
|
|
|
15
17
|
readonly storage: DurableObjectStorage;
|
|
16
18
|
/** Registers background work with Cloudflare's lifecycle. */
|
|
17
19
|
waitUntil(promise: Promise<unknown>): Effect.Effect<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Runs a background Effect via Cloudflare's `waitUntil` with the caller's
|
|
22
|
+
* Effect context, mirroring `WorkerContext.waitUntil` failure modes:
|
|
23
|
+
* `"observe"` (default) logs or routes failures to `onFailure`, while
|
|
24
|
+
* `"propagate"` also rejects the native `waitUntil` promise.
|
|
25
|
+
*/
|
|
26
|
+
waitUntil<A, E, R, R2 = never>(
|
|
27
|
+
effect: Effect.Effect<A, E, R>,
|
|
28
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
29
|
+
): Effect.Effect<void, never, R | R2>;
|
|
18
30
|
/**
|
|
19
31
|
* Runs an Effect inside Cloudflare's `blockConcurrencyWhile` gate.
|
|
20
32
|
*
|
|
@@ -72,54 +84,66 @@ export class DurableObjectState extends Context.Service<
|
|
|
72
84
|
*/
|
|
73
85
|
export const fromDurableObjectState = (
|
|
74
86
|
state: globalThis.DurableObjectState,
|
|
75
|
-
): DurableObjectStateService =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
waitUntil
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
): DurableObjectStateService => {
|
|
88
|
+
const scheduleWaitUntil = makeWaitUntilScheduler("DurableObjectState.waitUntil", (promise) =>
|
|
89
|
+
state.waitUntil(promise),
|
|
90
|
+
);
|
|
91
|
+
const waitUntil = (<A, E, R, R2 = never>(
|
|
92
|
+
input: Promise<unknown> | Effect.Effect<A, E, R>,
|
|
93
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
94
|
+
) =>
|
|
95
|
+
Effect.isEffect(input)
|
|
96
|
+
? scheduleWaitUntil(input, options, options?.mode ?? "observe")
|
|
97
|
+
: Effect.sync(() => state.waitUntil(input))) as DurableObjectStateService["waitUntil"];
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
raw: state,
|
|
101
|
+
id: state.id,
|
|
102
|
+
storage: fromDurableObjectStorage(state.storage),
|
|
103
|
+
waitUntil,
|
|
104
|
+
blockConcurrencyWhile: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
105
|
+
Effect.context<R>().pipe(
|
|
106
|
+
Effect.flatMap((context) =>
|
|
107
|
+
Effect.promise(() =>
|
|
108
|
+
state.blockConcurrencyWhile(() =>
|
|
109
|
+
runPromiseExitPreservingTypedFailures(context, effect),
|
|
110
|
+
),
|
|
86
111
|
),
|
|
87
112
|
),
|
|
113
|
+
Effect.flatten,
|
|
88
114
|
),
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Effect.promise(() =>
|
|
95
|
-
state.blockConcurrencyWhile(() =>
|
|
96
|
-
Effect.runPromise(Effect.provideContext(effect, context)),
|
|
115
|
+
blockConcurrencyWhileOrReset: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
116
|
+
Effect.context<R>().pipe(
|
|
117
|
+
Effect.flatMap((context) =>
|
|
118
|
+
Effect.promise(() =>
|
|
119
|
+
state.blockConcurrencyWhile(() => Effect.runPromiseWith(context)(effect)),
|
|
97
120
|
),
|
|
98
121
|
),
|
|
99
122
|
),
|
|
123
|
+
acceptWebSocket: (ws: DurableWebSocket, tags?: Array<string>) =>
|
|
124
|
+
Effect.sync(() => state.acceptWebSocket(ws.raw, tags)),
|
|
125
|
+
getWebSockets: (tag?: string) =>
|
|
126
|
+
Effect.sync(() => state.getWebSockets(tag).map((socket) => fromWebSocket(socket))),
|
|
127
|
+
setWebSocketAutoResponse: (pair?: WebSocketRequestResponsePair) =>
|
|
128
|
+
Effect.sync(() => state.setWebSocketAutoResponse(pair)),
|
|
129
|
+
getWebSocketAutoResponse: Effect.sync(() => state.getWebSocketAutoResponse()),
|
|
130
|
+
getWebSocketAutoResponseTimestamp: (ws: DurableWebSocket) =>
|
|
131
|
+
Effect.sync(() => state.getWebSocketAutoResponseTimestamp(ws.raw)),
|
|
132
|
+
setHibernatableWebSocketEventTimeout: (timeoutMs?: number) =>
|
|
133
|
+
Effect.sync(() => state.setHibernatableWebSocketEventTimeout(timeoutMs)),
|
|
134
|
+
getHibernatableWebSocketEventTimeout: Effect.sync(() =>
|
|
135
|
+
state.getHibernatableWebSocketEventTimeout(),
|
|
100
136
|
),
|
|
101
|
-
|
|
102
|
-
Effect.sync(() => state.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
setWebSocketAutoResponse: (pair?: WebSocketRequestResponsePair) =>
|
|
106
|
-
Effect.sync(() => state.setWebSocketAutoResponse(pair)),
|
|
107
|
-
getWebSocketAutoResponse: Effect.sync(() => state.getWebSocketAutoResponse()),
|
|
108
|
-
getWebSocketAutoResponseTimestamp: (ws: DurableWebSocket) =>
|
|
109
|
-
Effect.sync(() => state.getWebSocketAutoResponseTimestamp(ws.raw)),
|
|
110
|
-
setHibernatableWebSocketEventTimeout: (timeoutMs?: number) =>
|
|
111
|
-
Effect.sync(() => state.setHibernatableWebSocketEventTimeout(timeoutMs)),
|
|
112
|
-
getHibernatableWebSocketEventTimeout: Effect.sync(() =>
|
|
113
|
-
state.getHibernatableWebSocketEventTimeout(),
|
|
114
|
-
),
|
|
115
|
-
getTags: (ws: DurableWebSocket) => Effect.sync(() => state.getTags(ws.raw)),
|
|
116
|
-
abort: (reason?: string) => Effect.sync(() => state.abort(reason)),
|
|
117
|
-
});
|
|
137
|
+
getTags: (ws: DurableWebSocket) => Effect.sync(() => state.getTags(ws.raw)),
|
|
138
|
+
abort: (reason?: string) => Effect.sync(() => state.abort(reason)),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
118
141
|
|
|
119
|
-
const runPromiseExitPreservingTypedFailures = async <A, E>(
|
|
120
|
-
|
|
142
|
+
const runPromiseExitPreservingTypedFailures = async <A, E, R>(
|
|
143
|
+
context: Context.Context<R>,
|
|
144
|
+
effect: Effect.Effect<A, E, R>,
|
|
121
145
|
): Promise<Exit.Exit<A, E>> => {
|
|
122
|
-
const exit = await Effect.
|
|
146
|
+
const exit = await Effect.runPromiseExitWith(context)(effect);
|
|
123
147
|
|
|
124
148
|
if (Exit.isFailure(exit) && (Cause.hasDies(exit.cause) || Cause.hasInterrupts(exit.cause))) {
|
|
125
149
|
throw Cause.squash(exit.cause);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Data, Effect, Exit, Option, Schema as S } from "effect";
|
|
2
2
|
|
|
3
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
4
|
+
|
|
3
5
|
/** Supported primitive value types for Durable Object SQL APIs. */
|
|
4
6
|
export type SqlStorageValue = globalThis.SqlStorageValue;
|
|
5
7
|
|
|
@@ -7,7 +9,11 @@ export type SqlStorageValue = globalThis.SqlStorageValue;
|
|
|
7
9
|
export class StorageOperationError extends Data.TaggedError("StorageOperationError")<{
|
|
8
10
|
readonly operation: string;
|
|
9
11
|
readonly cause: unknown;
|
|
10
|
-
}> {
|
|
12
|
+
}> {
|
|
13
|
+
override get message(): string {
|
|
14
|
+
return `Durable Object storage ${this.operation} failed: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
type StorageEffect<A> = Effect.Effect<A, StorageOperationError>;
|
|
13
19
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Data, Effect, Option, Schema as S } from "effect";
|
|
1
|
+
import { Data, Effect, Option, Result, Schema as S } from "effect";
|
|
2
2
|
|
|
3
3
|
import { DurableObjectState } from "./DurableObjectState";
|
|
4
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
4
5
|
|
|
5
6
|
/** Data supported by Cloudflare websocket `send`. */
|
|
6
7
|
export type DurableWebSocketSendData = string | ArrayBuffer | ArrayBufferView;
|
|
@@ -8,12 +9,20 @@ export type DurableWebSocketSendData = string | ArrayBuffer | ArrayBufferView;
|
|
|
8
9
|
/** Error raised when sending on a Durable Object websocket fails. */
|
|
9
10
|
export class DurableWebSocketSendError extends Data.TaggedError("DurableWebSocketSendError")<{
|
|
10
11
|
readonly cause: unknown;
|
|
11
|
-
}> {
|
|
12
|
+
}> {
|
|
13
|
+
override get message(): string {
|
|
14
|
+
return `Durable Object websocket send failed: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
/** Error raised when closing a Durable Object websocket fails. */
|
|
14
19
|
export class DurableWebSocketCloseError extends Data.TaggedError("DurableWebSocketCloseError")<{
|
|
15
20
|
readonly cause: unknown;
|
|
16
|
-
}> {
|
|
21
|
+
}> {
|
|
22
|
+
override get message(): string {
|
|
23
|
+
return `Durable Object websocket close failed: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
17
26
|
|
|
18
27
|
/** Error raised when serializing or deserializing a websocket attachment fails. */
|
|
19
28
|
export class DurableWebSocketAttachmentError extends Data.TaggedError(
|
|
@@ -21,7 +30,11 @@ export class DurableWebSocketAttachmentError extends Data.TaggedError(
|
|
|
21
30
|
)<{
|
|
22
31
|
readonly operation: "serialize" | "deserialize";
|
|
23
32
|
readonly cause: unknown;
|
|
24
|
-
}> {
|
|
33
|
+
}> {
|
|
34
|
+
override get message(): string {
|
|
35
|
+
return `Durable Object websocket attachment ${this.operation} failed: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
25
38
|
|
|
26
39
|
/** Effect-native wrapper around a hibernatable Durable Object websocket. */
|
|
27
40
|
export interface DurableWebSocket<Attachment = unknown> {
|
|
@@ -110,33 +123,35 @@ export interface AcceptedUpgrade<Attachment = unknown> {
|
|
|
110
123
|
* return response.response;
|
|
111
124
|
* ```
|
|
112
125
|
*/
|
|
113
|
-
export const acceptUpgrade = <
|
|
126
|
+
export const acceptUpgrade = Effect.fn("DurableObjectWebSocket.acceptUpgrade")(function* <
|
|
127
|
+
Attachment = unknown,
|
|
128
|
+
>(
|
|
114
129
|
options: AcceptUpgradeOptions<Attachment> = {},
|
|
115
|
-
): Effect.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
yield*
|
|
127
|
-
|
|
128
|
-
options.tags === undefined ? undefined : [...options.tags],
|
|
129
|
-
);
|
|
130
|
+
): Effect.fn.Return<
|
|
131
|
+
AcceptedUpgrade<Attachment>,
|
|
132
|
+
DurableWebSocketAttachmentError,
|
|
133
|
+
DurableObjectState
|
|
134
|
+
> {
|
|
135
|
+
const state = yield* DurableObjectState;
|
|
136
|
+
const pair = new WebSocketPair();
|
|
137
|
+
const client = pair[0];
|
|
138
|
+
const server = fromWebSocket<Attachment>(pair[1]);
|
|
139
|
+
|
|
140
|
+
if (options.attachment !== undefined) {
|
|
141
|
+
yield* server.serializeAttachment(options.attachment);
|
|
142
|
+
}
|
|
130
143
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
yield* state.acceptWebSocket(server, options.tags === undefined ? undefined : [...options.tags]);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
client,
|
|
148
|
+
server,
|
|
149
|
+
response: new Response(null, {
|
|
150
|
+
status: 101,
|
|
151
|
+
webSocket: client,
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
});
|
|
140
155
|
|
|
141
156
|
export type AttachmentInvalidPolicy = "ignore" | "ignore-and-close" | "fail";
|
|
142
157
|
|
|
@@ -213,23 +228,18 @@ export const attachment = <const AttachmentSchema extends S.Codec<any, any, neve
|
|
|
213
228
|
const onInvalid = options.onInvalid ?? "ignore";
|
|
214
229
|
|
|
215
230
|
for (const socket of sockets) {
|
|
216
|
-
const decoded = yield* deserialize(socket)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
if (decoded._tag === "Success" && Option.isSome(decoded.value)) {
|
|
224
|
-
restored.push({ socket, attachment: decoded.value.value });
|
|
231
|
+
const decoded = yield* Effect.result(deserialize(socket));
|
|
232
|
+
|
|
233
|
+
if (Result.isSuccess(decoded) && Option.isSome(decoded.success)) {
|
|
234
|
+
restored.push({ socket, attachment: decoded.success.value });
|
|
225
235
|
continue;
|
|
226
236
|
}
|
|
227
237
|
|
|
228
|
-
if (decoded
|
|
229
|
-
return yield* Effect.fail(decoded.
|
|
238
|
+
if (Result.isFailure(decoded) && onInvalid === "fail") {
|
|
239
|
+
return yield* Effect.fail(decoded.failure);
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
if (decoded
|
|
242
|
+
if (Result.isFailure(decoded) && onInvalid === "ignore-and-close") {
|
|
233
243
|
yield* socket.close(1008, "invalid websocket attachment").pipe(Effect.ignore);
|
|
234
244
|
}
|
|
235
245
|
}
|