@livestore/common-cf 0.4.0 → 0.5.0-dev.1
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/dist/.tsbuildinfo +1 -1
- package/dist/declare/cf-declare.d.ts +1 -21
- package/dist/declare/cf-declare.d.ts.map +1 -1
- package/dist/declare/mod.js +1 -1
- package/dist/do-rpc/client.d.ts +8 -0
- package/dist/do-rpc/client.d.ts.map +1 -1
- package/dist/do-rpc/client.js +71 -51
- package/dist/do-rpc/client.js.map +1 -1
- package/dist/do-rpc/client.test.d.ts +2 -0
- package/dist/do-rpc/client.test.d.ts.map +1 -0
- package/dist/do-rpc/client.test.js +189 -0
- package/dist/do-rpc/client.test.js.map +1 -0
- package/dist/do-rpc/mod.d.ts +1 -1
- package/dist/do-rpc/mod.d.ts.map +1 -1
- package/dist/do-rpc/mod.js +2 -2
- package/dist/do-rpc/mod.js.map +1 -1
- package/dist/do-rpc/rpc.test.js +27 -59
- package/dist/do-rpc/rpc.test.js.map +1 -1
- package/dist/do-rpc/server.d.ts +10 -4
- package/dist/do-rpc/server.d.ts.map +1 -1
- package/dist/do-rpc/server.js +107 -74
- package/dist/do-rpc/server.js.map +1 -1
- package/dist/do-rpc/test-fixtures/rpc-schema.d.ts +28 -28
- package/dist/do-rpc/test-fixtures/rpc-schema.js +11 -11
- package/dist/do-rpc/test-fixtures/worker.d.ts.map +1 -1
- package/dist/do-rpc/test-fixtures/worker.js +10 -10
- package/dist/do-rpc/test-fixtures/worker.js.map +1 -1
- package/dist/mod.js +3 -3
- package/dist/ws-rpc/test-fixtures/rpc-schema.d.ts +28 -28
- package/dist/ws-rpc/test-fixtures/rpc-schema.js +9 -9
- package/dist/ws-rpc/test-fixtures/worker.d.ts.map +1 -1
- package/dist/ws-rpc/test-fixtures/worker.js +2 -2
- package/dist/ws-rpc/ws-rpc-server.d.ts +14 -7
- package/dist/ws-rpc/ws-rpc-server.d.ts.map +1 -1
- package/dist/ws-rpc/ws-rpc-server.js +60 -28
- package/dist/ws-rpc/ws-rpc-server.js.map +1 -1
- package/dist/ws-rpc/ws-rpc.test.js +28 -60
- package/dist/ws-rpc/ws-rpc.test.js.map +1 -1
- package/package.json +29 -38
- package/src/declare/cf-declare.ts +1 -1
- package/src/do-rpc/README.md +2 -2
- package/src/do-rpc/client.test.ts +263 -0
- package/src/do-rpc/client.ts +99 -68
- package/src/do-rpc/mod.ts +1 -1
- package/src/do-rpc/rpc.test.ts +30 -60
- package/src/do-rpc/server.ts +168 -86
- package/src/do-rpc/test-fixtures/rpc-schema.ts +11 -11
- package/src/do-rpc/test-fixtures/worker.ts +9 -12
- package/src/ws-rpc/test-fixtures/rpc-schema.ts +9 -9
- package/src/ws-rpc/ws-rpc-server.ts +114 -39
- package/src/ws-rpc/ws-rpc.test.ts +31 -61
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
|
|
2
|
-
declare const TestRpcs_base: RpcGroup.RpcGroup<Rpc.Rpc<"
|
|
3
|
-
|
|
2
|
+
declare const TestRpcs_base: RpcGroup.RpcGroup<Rpc.Rpc<"Add", Schema.Struct<{
|
|
3
|
+
readonly a: Schema.Finite;
|
|
4
|
+
readonly b: Schema.Finite;
|
|
4
5
|
}>, Schema.Struct<{
|
|
5
|
-
|
|
6
|
-
}>,
|
|
7
|
-
|
|
6
|
+
readonly result: Schema.Finite;
|
|
7
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Defect", Schema.Struct<{
|
|
8
|
+
readonly message: Schema.String;
|
|
8
9
|
}>, Schema.Struct<{
|
|
9
|
-
|
|
10
|
-
}>,
|
|
11
|
-
|
|
12
|
-
b: typeof Schema.Number;
|
|
10
|
+
readonly never: Schema.String;
|
|
11
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Echo", Schema.Struct<{
|
|
12
|
+
readonly text: Schema.String;
|
|
13
13
|
}>, Schema.Struct<{
|
|
14
|
-
|
|
15
|
-
}>,
|
|
16
|
-
message:
|
|
14
|
+
readonly echo: Schema.String;
|
|
15
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Fail", Schema.Struct<{
|
|
16
|
+
readonly message: Schema.String;
|
|
17
17
|
}>, Schema.Struct<{
|
|
18
|
-
never:
|
|
19
|
-
}>,
|
|
20
|
-
message:
|
|
18
|
+
readonly never: Schema.String;
|
|
19
|
+
}>, Schema.String, never, never> | Rpc.Rpc<"Ping", Schema.Struct<{
|
|
20
|
+
readonly message: Schema.String;
|
|
21
21
|
}>, Schema.Struct<{
|
|
22
|
-
|
|
23
|
-
}>,
|
|
24
|
-
maybeNumber: Schema.Option<
|
|
25
|
-
}>,
|
|
26
|
-
count:
|
|
27
|
-
|
|
28
|
-
}>, import("
|
|
29
|
-
count:
|
|
30
|
-
|
|
31
|
-
}>, import("
|
|
32
|
-
delay:
|
|
33
|
-
interruptAfterCount:
|
|
34
|
-
}>, import("
|
|
22
|
+
readonly response: Schema.String;
|
|
23
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Stream", Schema.Struct<{}>, import("effect/rpc/RpcSchema").Stream<Schema.Struct<{
|
|
24
|
+
readonly maybeNumber: Schema.Option<Schema.Finite>;
|
|
25
|
+
}>, Schema.Never>, Schema.Never, never, never> | Rpc.Rpc<"StreamBugScenarioDoClient", Schema.Struct<{}>, Schema.Finite, Schema.Never, never, never> | Rpc.Rpc<"StreamBugScenarioDoServer", Schema.Struct<{}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.Never>, Schema.Never, never, never> | Rpc.Rpc<"StreamDefect", Schema.Struct<{
|
|
26
|
+
readonly count: Schema.Finite;
|
|
27
|
+
readonly defectAfter: Schema.Finite;
|
|
28
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.Never>, Schema.Never, never, never> | Rpc.Rpc<"StreamError", Schema.Struct<{
|
|
29
|
+
readonly count: Schema.Finite;
|
|
30
|
+
readonly errorAfter: Schema.Finite;
|
|
31
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.String>, Schema.Never, never, never> | Rpc.Rpc<"StreamInterruptible", Schema.Struct<{
|
|
32
|
+
readonly delay: Schema.Finite;
|
|
33
|
+
readonly interruptAfterCount: Schema.Finite;
|
|
34
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.Never>, Schema.Never, never, never>>;
|
|
35
35
|
export declare class TestRpcs extends TestRpcs_base {
|
|
36
36
|
}
|
|
37
37
|
export type TestRpcsI = RpcGroup.Rpcs<typeof TestRpcs>;
|
|
@@ -6,8 +6,8 @@ export class TestRpcs extends RpcGroup.make(Rpc.make('Ping', {
|
|
|
6
6
|
payload: Schema.Struct({ text: Schema.String }),
|
|
7
7
|
success: Schema.Struct({ echo: Schema.String }),
|
|
8
8
|
}), Rpc.make('Add', {
|
|
9
|
-
payload: Schema.Struct({ a: Schema.
|
|
10
|
-
success: Schema.Struct({ result: Schema.
|
|
9
|
+
payload: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }),
|
|
10
|
+
success: Schema.Struct({ result: Schema.Finite }),
|
|
11
11
|
}), Rpc.make('Defect', {
|
|
12
12
|
payload: Schema.Struct({ message: Schema.String }),
|
|
13
13
|
success: Schema.Struct({ never: Schema.String }),
|
|
@@ -18,29 +18,29 @@ export class TestRpcs extends RpcGroup.make(Rpc.make('Ping', {
|
|
|
18
18
|
}), Rpc.make('Stream', {
|
|
19
19
|
payload: Schema.Struct({}),
|
|
20
20
|
success: Schema.Struct({
|
|
21
|
-
maybeNumber: Schema.Option(Schema.
|
|
21
|
+
maybeNumber: Schema.Option(Schema.Finite),
|
|
22
22
|
}),
|
|
23
23
|
stream: true,
|
|
24
24
|
}), Rpc.make('StreamError', {
|
|
25
|
-
payload: Schema.Struct({ count: Schema.
|
|
26
|
-
success: Schema.
|
|
25
|
+
payload: Schema.Struct({ count: Schema.Finite, errorAfter: Schema.Finite }),
|
|
26
|
+
success: Schema.Finite,
|
|
27
27
|
error: Schema.String,
|
|
28
28
|
stream: true,
|
|
29
29
|
}), Rpc.make('StreamDefect', {
|
|
30
|
-
payload: Schema.Struct({ count: Schema.
|
|
31
|
-
success: Schema.
|
|
30
|
+
payload: Schema.Struct({ count: Schema.Finite, defectAfter: Schema.Finite }),
|
|
31
|
+
success: Schema.Finite,
|
|
32
32
|
stream: true,
|
|
33
33
|
}), Rpc.make('StreamInterruptible', {
|
|
34
|
-
payload: Schema.Struct({ delay: Schema.
|
|
35
|
-
success: Schema.
|
|
34
|
+
payload: Schema.Struct({ delay: Schema.Finite, interruptAfterCount: Schema.Finite }),
|
|
35
|
+
success: Schema.Finite,
|
|
36
36
|
stream: true,
|
|
37
37
|
}), Rpc.make('StreamBugScenarioDoServer', {
|
|
38
38
|
payload: Schema.Struct({}),
|
|
39
|
-
success: Schema.
|
|
39
|
+
success: Schema.Finite,
|
|
40
40
|
stream: true,
|
|
41
41
|
}), Rpc.make('StreamBugScenarioDoClient', {
|
|
42
42
|
payload: Schema.Struct({}),
|
|
43
|
-
success: Schema.
|
|
43
|
+
success: Schema.Finite,
|
|
44
44
|
})) {
|
|
45
45
|
}
|
|
46
46
|
//# sourceMappingURL=rpc-schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/do-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAiBlD,MAAM,WAAW,GAAG;IAClB,WAAW,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAED,qBAAa,oBAAqB,SAAQ,aAAa;IAC5C,sBAAsB,EAA6B,KAAK,CAAA;IAE3D,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/do-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAiBlD,MAAM,WAAW,GAAG;IAClB,WAAW,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAED,qBAAa,oBAAqB,SAAQ,aAAa;IAC5C,sBAAsB,EAA6B,KAAK,CAAA;IAE3D,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA2C5C;CACF;;IAGO,KAAK,UAAU,OAAO,OAAO,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
import { DurableObject } from 'cloudflare:workers';
|
|
3
3
|
import { layerProtocolDurableObject, toDurableObjectHandler } from '@livestore/common-cf';
|
|
4
|
-
import { Effect,
|
|
5
|
-
import { TestRpcs } from
|
|
4
|
+
import { Effect, HttpEffect, Layer, Option, RpcClient, RpcSerialization, RpcServer, Schedule, Stream, } from '@livestore/utils/effect';
|
|
5
|
+
import { TestRpcs } from './rpc-schema.js';
|
|
6
6
|
export class TestRpcDurableObject extends DurableObject {
|
|
7
7
|
__DURABLE_OBJECT_BRAND = 'TestRpcDurableObject';
|
|
8
8
|
async rpc(payload) {
|
|
@@ -32,7 +32,7 @@ export default {
|
|
|
32
32
|
try {
|
|
33
33
|
const url = new URL(request.url);
|
|
34
34
|
// Handle HTTP RPC endpoint
|
|
35
|
-
if (url.pathname === '/rpc') {
|
|
35
|
+
if (url.pathname === '/rpc/') {
|
|
36
36
|
// Get the test server DO instance
|
|
37
37
|
const doId = env.TEST_RPC_DO.idFromName('test-server');
|
|
38
38
|
const serverDO = env.TEST_RPC_DO.get(doId);
|
|
@@ -56,16 +56,16 @@ export default {
|
|
|
56
56
|
StreamBugScenarioDoServer: () => Stream.die('never called'),
|
|
57
57
|
StreamBugScenarioDoClient: (msg) => doRpcClient.StreamBugScenarioDoServer(msg).pipe(Stream.tap(() => Effect.fail('doh')),
|
|
58
58
|
// observed behaviour: `log1` is still logged
|
|
59
|
-
Stream.
|
|
59
|
+
Stream.tapCause((cause) => Effect.log('log1', cause)), Stream.mapError((cause) => cause.toString()),
|
|
60
60
|
// observed behaviour: after this error mapping `log2` is never logged
|
|
61
|
-
Stream.
|
|
61
|
+
Stream.tapCause((cause) => Effect.log('log2', cause)), Stream.tapLogWithLabel('stream'), Stream.runCount, Effect.orDie,
|
|
62
62
|
// observed behaviour: `log3` is also never logged
|
|
63
|
-
Effect.
|
|
64
|
-
}).pipe(Layer.provideMerge(
|
|
65
|
-
// Create the HTTP RPC
|
|
66
|
-
const
|
|
63
|
+
Effect.tapCause((cause) => Effect.log('log3', cause))),
|
|
64
|
+
}).pipe(Layer.provideMerge(RpcSerialization.layerJson));
|
|
65
|
+
// Create the HTTP RPC effect
|
|
66
|
+
const httpEffect = yield* RpcServer.toHttpEffect(TestRpcs).pipe(Effect.provide(handlersLayer));
|
|
67
67
|
// Run the app and convert to web handler
|
|
68
|
-
const webHandler =
|
|
68
|
+
const webHandler = HttpEffect.toWebHandler(httpEffect);
|
|
69
69
|
return yield* Effect.promise(() => webHandler(request));
|
|
70
70
|
}).pipe(Effect.tapCauseLogPretty, Effect.scoped, Effect.withSpan('@livestore/common-cf/do-rpc/test-fixtures/worker:fetch'),
|
|
71
71
|
// Effect.provide(ProtocolLive),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/do-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AACzF,OAAO,EACL,MAAM,EACN,
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/do-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/mod.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './do-rpc/mod.js';
|
|
2
|
+
export * from './helper.js';
|
|
3
|
+
export * from './ws-rpc/ws-rpc-server.js';
|
|
4
4
|
//# sourceMappingURL=mod.js.map
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
|
|
2
|
-
declare const TestRpcs_base: RpcGroup.RpcGroup<Rpc.Rpc<"
|
|
3
|
-
|
|
2
|
+
declare const TestRpcs_base: RpcGroup.RpcGroup<Rpc.Rpc<"Add", Schema.Struct<{
|
|
3
|
+
readonly a: Schema.Finite;
|
|
4
|
+
readonly b: Schema.Finite;
|
|
4
5
|
}>, Schema.Struct<{
|
|
5
|
-
|
|
6
|
-
}>,
|
|
7
|
-
|
|
6
|
+
readonly result: Schema.Finite;
|
|
7
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Defect", Schema.Struct<{
|
|
8
|
+
readonly message: Schema.String;
|
|
8
9
|
}>, Schema.Struct<{
|
|
9
|
-
|
|
10
|
-
}>,
|
|
11
|
-
|
|
12
|
-
b: typeof Schema.Number;
|
|
10
|
+
readonly never: Schema.String;
|
|
11
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Echo", Schema.Struct<{
|
|
12
|
+
readonly text: Schema.String;
|
|
13
13
|
}>, Schema.Struct<{
|
|
14
|
-
|
|
15
|
-
}>,
|
|
16
|
-
message:
|
|
14
|
+
readonly echo: Schema.String;
|
|
15
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Fail", Schema.Struct<{
|
|
16
|
+
readonly message: Schema.String;
|
|
17
17
|
}>, Schema.Struct<{
|
|
18
|
-
never:
|
|
19
|
-
}>,
|
|
20
|
-
message:
|
|
18
|
+
readonly never: Schema.String;
|
|
19
|
+
}>, Schema.String, never, never> | Rpc.Rpc<"Ping", Schema.Struct<{
|
|
20
|
+
readonly message: Schema.String;
|
|
21
21
|
}>, Schema.Struct<{
|
|
22
|
-
|
|
23
|
-
}>,
|
|
24
|
-
maybeNumber: Schema.Option<
|
|
25
|
-
}>,
|
|
26
|
-
count:
|
|
27
|
-
|
|
28
|
-
}>, import("
|
|
29
|
-
count:
|
|
30
|
-
|
|
31
|
-
}>, import("
|
|
32
|
-
delay:
|
|
33
|
-
interruptAfterCount:
|
|
34
|
-
}>, import("
|
|
22
|
+
readonly response: Schema.String;
|
|
23
|
+
}>, Schema.Never, never, never> | Rpc.Rpc<"Stream", Schema.Struct<{}>, import("effect/rpc/RpcSchema").Stream<Schema.Struct<{
|
|
24
|
+
readonly maybeNumber: Schema.Option<Schema.Finite>;
|
|
25
|
+
}>, Schema.Never>, Schema.Never, never, never> | Rpc.Rpc<"StreamDefect", Schema.Struct<{
|
|
26
|
+
readonly count: Schema.Finite;
|
|
27
|
+
readonly defectAfter: Schema.Finite;
|
|
28
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.Never>, Schema.Never, never, never> | Rpc.Rpc<"StreamError", Schema.Struct<{
|
|
29
|
+
readonly count: Schema.Finite;
|
|
30
|
+
readonly errorAfter: Schema.Finite;
|
|
31
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.String>, Schema.Never, never, never> | Rpc.Rpc<"StreamInterruptible", Schema.Struct<{
|
|
32
|
+
readonly delay: Schema.Finite;
|
|
33
|
+
readonly interruptAfterCount: Schema.Finite;
|
|
34
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Finite, Schema.Never>, Schema.Never, never, never>>;
|
|
35
35
|
export declare class TestRpcs extends TestRpcs_base {
|
|
36
36
|
}
|
|
37
37
|
export type TestRpcsI = RpcGroup.Rpcs<typeof TestRpcs>;
|
|
@@ -6,8 +6,8 @@ export class TestRpcs extends RpcGroup.make(Rpc.make('Ping', {
|
|
|
6
6
|
payload: Schema.Struct({ text: Schema.String }),
|
|
7
7
|
success: Schema.Struct({ echo: Schema.String }),
|
|
8
8
|
}), Rpc.make('Add', {
|
|
9
|
-
payload: Schema.Struct({ a: Schema.
|
|
10
|
-
success: Schema.Struct({ result: Schema.
|
|
9
|
+
payload: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }),
|
|
10
|
+
success: Schema.Struct({ result: Schema.Finite }),
|
|
11
11
|
}), Rpc.make('Defect', {
|
|
12
12
|
payload: Schema.Struct({ message: Schema.String }),
|
|
13
13
|
success: Schema.Struct({ never: Schema.String }),
|
|
@@ -18,21 +18,21 @@ export class TestRpcs extends RpcGroup.make(Rpc.make('Ping', {
|
|
|
18
18
|
}), Rpc.make('Stream', {
|
|
19
19
|
payload: Schema.Struct({}),
|
|
20
20
|
success: Schema.Struct({
|
|
21
|
-
maybeNumber: Schema.Option(Schema.
|
|
21
|
+
maybeNumber: Schema.Option(Schema.Finite),
|
|
22
22
|
}),
|
|
23
23
|
stream: true,
|
|
24
24
|
}), Rpc.make('StreamError', {
|
|
25
|
-
payload: Schema.Struct({ count: Schema.
|
|
26
|
-
success: Schema.
|
|
25
|
+
payload: Schema.Struct({ count: Schema.Finite, errorAfter: Schema.Finite }),
|
|
26
|
+
success: Schema.Finite,
|
|
27
27
|
error: Schema.String,
|
|
28
28
|
stream: true,
|
|
29
29
|
}), Rpc.make('StreamDefect', {
|
|
30
|
-
payload: Schema.Struct({ count: Schema.
|
|
31
|
-
success: Schema.
|
|
30
|
+
payload: Schema.Struct({ count: Schema.Finite, defectAfter: Schema.Finite }),
|
|
31
|
+
success: Schema.Finite,
|
|
32
32
|
stream: true,
|
|
33
33
|
}), Rpc.make('StreamInterruptible', {
|
|
34
|
-
payload: Schema.Struct({ delay: Schema.
|
|
35
|
-
success: Schema.
|
|
34
|
+
payload: Schema.Struct({ delay: Schema.Finite, interruptAfterCount: Schema.Finite }),
|
|
35
|
+
success: Schema.Finite,
|
|
36
36
|
stream: true,
|
|
37
37
|
})) {
|
|
38
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/ws-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAQlD,MAAM,WAAW,GAAG;IAClB,WAAW,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAED,qBAAa,oBAAqB,SAAQ,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC;IAC1D,sBAAsB,EAA6B,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/ws-rpc/test-fixtures/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAQlD,MAAM,WAAW,GAAG;IAClB,WAAW,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAA;CAC1D;AAED,qBAAa,oBAAqB,SAAQ,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC;IAC1D,sBAAsB,EAA6B,KAAK,CAAA;IAEjE,YAAY,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EA4C9C;IAEc,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAexD;CACF;;IAGO,KAAK,UAAU,OAAO,OAAO,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
import { DurableObject } from 'cloudflare:workers';
|
|
3
3
|
import { Effect, Layer, Option, RpcServer, Schedule, Stream } from '@livestore/utils/effect';
|
|
4
|
-
import { setupDurableObjectWebSocketRpc } from
|
|
5
|
-
import { TestRpcs } from
|
|
4
|
+
import { setupDurableObjectWebSocketRpc } from '../ws-rpc-server.js';
|
|
5
|
+
import { TestRpcs } from './rpc-schema.js';
|
|
6
6
|
export class TestRpcDurableObject extends DurableObject {
|
|
7
7
|
__DURABLE_OBJECT_BRAND = 'TestRpcDurableObject';
|
|
8
8
|
constructor(state, env) {
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @see {@link https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Cloudflare WebSocket Best Practices}
|
|
16
16
|
*/
|
|
17
|
-
import { Context, Layer,
|
|
17
|
+
import { Context, Layer, Queue, RpcMessage, RpcSerialization, RpcServer, Scope } from '@livestore/utils/effect';
|
|
18
18
|
import type * as CfTypes from '../cf-types.ts';
|
|
19
|
-
declare const WsContext_base: Context.
|
|
19
|
+
declare const WsContext_base: Context.ServiceClass<WsContext, "WsContext", {
|
|
20
20
|
readonly ws: CfTypes.WebSocket;
|
|
21
21
|
}>;
|
|
22
22
|
/**
|
|
@@ -40,7 +40,7 @@ export interface DurableObjectWebSocketRpcConfig {
|
|
|
40
40
|
webSocketMode: 'hibernate' | 'accept';
|
|
41
41
|
/**
|
|
42
42
|
* Effect RPC layer that requires `RpcServer.Protocol` (and `WsContext` if used)
|
|
43
|
-
* and provides the RPC server
|
|
43
|
+
* and provides the RPC server services.
|
|
44
44
|
*
|
|
45
45
|
* This is typically created by:
|
|
46
46
|
* ```typescript
|
|
@@ -56,6 +56,10 @@ export interface DurableObjectWebSocketRpcConfig {
|
|
|
56
56
|
rpcLayer: Layer.Layer<never, never, RpcServer.Protocol | WsContext>;
|
|
57
57
|
/** Function to get access to incoming requests */
|
|
58
58
|
onMessage?: (msg: RpcMessage.FromClientEncoded, ws: CfTypes.WebSocket) => void;
|
|
59
|
+
/** Called after a terminal RPC response has been accepted by the serializer. */
|
|
60
|
+
onRequestExit?: (requestId: string | number, ws: CfTypes.WebSocket) => void;
|
|
61
|
+
/** Called after a connection-level RPC defect has been accepted by the serializer. */
|
|
62
|
+
onProtocolDefect?: (ws: CfTypes.WebSocket) => void;
|
|
59
63
|
mainLayer?: Layer.Layer<never>;
|
|
60
64
|
}
|
|
61
65
|
/**
|
|
@@ -118,7 +122,7 @@ export interface DurableObjectWebSocketRpcConfig {
|
|
|
118
122
|
* @see {@link https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Cloudflare WebSocket Best Practices}
|
|
119
123
|
* @see {@link https://effect-ts.github.io/effect/docs/rpc Effect RPC Documentation}
|
|
120
124
|
*/
|
|
121
|
-
export declare const setupDurableObjectWebSocketRpc: ({ doSelf, rpcLayer, webSocketMode, onMessage, mainLayer, }: DurableObjectWebSocketRpcConfig) => {
|
|
125
|
+
export declare const setupDurableObjectWebSocketRpc: ({ doSelf, rpcLayer, webSocketMode, onMessage, onRequestExit, onProtocolDefect, mainLayer, }: DurableObjectWebSocketRpcConfig) => {
|
|
122
126
|
webSocketMessage: (ws: CfTypes.WebSocket, message: string | ArrayBuffer) => void | Promise<void>;
|
|
123
127
|
webSocketClose: (ws: CfTypes.WebSocket, code: number, reason: string, wasClean: boolean) => void | Promise<void>;
|
|
124
128
|
};
|
|
@@ -127,9 +131,12 @@ export declare const setupDurableObjectWebSocketRpc: ({ doSelf, rpcLayer, webSoc
|
|
|
127
131
|
*/
|
|
128
132
|
export interface WsRpcServerArgs {
|
|
129
133
|
ws: CfTypes.WebSocket;
|
|
134
|
+
scope: Scope.Scope;
|
|
130
135
|
onMessage?: (message: RpcMessage.FromClientEncoded, ws: CfTypes.WebSocket) => void;
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
onRequestExit?: (requestId: string | number, ws: CfTypes.WebSocket) => void;
|
|
137
|
+
onProtocolDefect?: (ws: CfTypes.WebSocket) => void;
|
|
138
|
+
/** Queue for receiving incoming messages from the WebSocket */
|
|
139
|
+
incomingQueue: Queue.Queue<Uint8Array | string>;
|
|
133
140
|
}
|
|
134
141
|
/**
|
|
135
142
|
* Creates an RPC server protocol layer for WebSocket communication.
|
|
@@ -145,6 +152,6 @@ export interface WsRpcServerArgs {
|
|
|
145
152
|
*
|
|
146
153
|
* @internal This is typically used internally by `setupDurableObjectWebSocketRpc`
|
|
147
154
|
*/
|
|
148
|
-
export declare const layerRpcServerWebsocket: (args: WsRpcServerArgs) => Layer.Layer<
|
|
155
|
+
export declare const layerRpcServerWebsocket: (args: WsRpcServerArgs) => Layer.Layer<RpcServer.Protocol | WsContext, never, RpcSerialization.RpcSerialization>;
|
|
149
156
|
export {};
|
|
150
157
|
//# sourceMappingURL=ws-rpc-server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-rpc-server.d.ts","sourceRoot":"","sources":["../../src/ws-rpc/ws-rpc-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,OAAO,EAIP,KAAK,
|
|
1
|
+
{"version":3,"file":"ws-rpc-server.d.ts","sourceRoot":"","sources":["../../src/ws-rpc/ws-rpc-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,OAAO,EAIP,KAAK,EAEL,KAAK,EAEL,UAAU,EACV,gBAAgB,EAChB,SAAS,EAET,KAAK,EAEN,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAA;;iBAO2B,OAAO,CAAC,SAAS;;AAL1F;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAA6E;CAAG;AAE/G;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,+CAA+C;IAC/C,MAAM,EAAE,OAAO,CAAC,aAAa,CAAA;IAC7B;;;;OAIG;IACH,aAAa,EAAE,WAAW,GAAG,QAAQ,CAAA;IACrC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAA;IACnE,kDAAkD;IAClD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,iBAAiB,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAC9E,gFAAgF;IAChF,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAC3E,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAClD,SAAS,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,eAAO,MAAM,8BAA8B,gGAQxC,+BAA+B;;;CA0FjC,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC,SAAS,CAAA;IACrB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAA;IAClB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,iBAAiB,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAClF,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAC3E,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI,CAAA;IAClD,+DAA+D;IAC/D,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,CAAA;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,SAAU,eAAe,0FAI1D,CAAA"}
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* @see {@link https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Cloudflare WebSocket Best Practices}
|
|
16
16
|
*/
|
|
17
17
|
import { notYetImplemented, omitUndefineds } from '@livestore/utils';
|
|
18
|
-
import { Context,
|
|
18
|
+
import { Context, Effect, Function, Exit, Layer, Logger, Queue, References, RpcMessage, RpcSerialization, RpcServer, Schema, Scope, Stream, } from '@livestore/utils/effect';
|
|
19
19
|
/**
|
|
20
20
|
* Context service providing access to the current WebSocket.
|
|
21
21
|
* This is useful for reading WebSocket attachment data (e.g., forwarded headers)
|
|
22
22
|
* inside RPC handlers.
|
|
23
23
|
*/
|
|
24
|
-
export class WsContext extends Context.
|
|
24
|
+
export class WsContext extends Context.Service()('WsContext') {
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Sets up WebSocket RPC functionality on a Cloudflare Durable Object with hibernation support.
|
|
@@ -83,7 +83,7 @@ export class WsContext extends Context.Tag('WsContext')() {
|
|
|
83
83
|
* @see {@link https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Cloudflare WebSocket Best Practices}
|
|
84
84
|
* @see {@link https://effect-ts.github.io/effect/docs/rpc Effect RPC Documentation}
|
|
85
85
|
*/
|
|
86
|
-
export const setupDurableObjectWebSocketRpc = ({ doSelf, rpcLayer, webSocketMode, onMessage, mainLayer, }) => {
|
|
86
|
+
export const setupDurableObjectWebSocketRpc = ({ doSelf, rpcLayer, webSocketMode, onMessage, onRequestExit, onProtocolDefect, mainLayer, }) => {
|
|
87
87
|
if (webSocketMode === 'accept') {
|
|
88
88
|
return notYetImplemented(`WebSocket mode 'accept' is not yet implemented`);
|
|
89
89
|
}
|
|
@@ -94,26 +94,25 @@ export const setupDurableObjectWebSocketRpc = ({ doSelf, rpcLayer, webSocketMode
|
|
|
94
94
|
}
|
|
95
95
|
yield* Effect.logDebug(`Launching WebSocket Effect RPC server`);
|
|
96
96
|
const scope = yield* Scope.make();
|
|
97
|
-
const incomingQueue = yield*
|
|
98
|
-
yield* Scope.addFinalizer(scope,
|
|
97
|
+
const incomingQueue = yield* Queue.unbounded();
|
|
98
|
+
yield* Scope.addFinalizer(scope, Queue.shutdown(incomingQueue).pipe(Effect.asVoid));
|
|
99
99
|
const ProtocolLive = layerRpcServerWebsocket({
|
|
100
100
|
ws,
|
|
101
|
+
scope,
|
|
101
102
|
incomingQueue,
|
|
102
|
-
...omitUndefineds({ onMessage }),
|
|
103
|
+
...omitUndefineds({ onMessage, onRequestExit, onProtocolDefect }),
|
|
103
104
|
}).pipe(Layer.provide(RpcSerialization.layerJson));
|
|
104
105
|
const ServerLive = rpcLayer.pipe(Layer.provide(ProtocolLive));
|
|
105
106
|
yield* Layer.launch(ServerLive).pipe(Effect.tapCauseLogPretty, Effect.forkIn(scope));
|
|
106
|
-
const
|
|
107
|
+
const services = yield* Effect.context();
|
|
107
108
|
const ctx = {
|
|
108
109
|
scope,
|
|
109
|
-
onMessage: (message) => incomingQueue
|
|
110
|
-
.offer(message)
|
|
111
|
-
.pipe(Effect.asVoid, Effect.withSpan('ws-rpc-server/onMessage', { root: true }), Effect.provide(runtime), Effect.runPromise),
|
|
110
|
+
onMessage: (message) => Queue.offer(incomingQueue, message).pipe(Effect.asVoid, Effect.withSpan('ws-rpc-server/onMessage', { root: true }), Effect.runPromiseWith(services)),
|
|
112
111
|
};
|
|
113
112
|
serverCtxMap.set(ws, ctx);
|
|
114
113
|
return ctx;
|
|
115
|
-
}).pipe(Effect.tapCauseLogPretty, Logger.
|
|
116
|
-
|
|
114
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.annotateLogs({ thread: 'ws-rpc-server' }), Effect.provide(Layer.mergeAll(Logger.layer([Logger.consoleStructured]), Layer.succeed(References.MinimumLogLevel, 'Debug'), // Useful for debugging
|
|
115
|
+
mainLayer ?? Layer.empty)), Effect.withSpan('effect-ws-rpc-server'), Effect.runPromise);
|
|
117
116
|
const webSocketMessage = async (ws, message) => {
|
|
118
117
|
// console.log('webSocketMessage', message, serverCtxMap.has(ws))
|
|
119
118
|
const { onMessage } = await launchServer(ws);
|
|
@@ -148,7 +147,7 @@ export const setupDurableObjectWebSocketRpc = ({ doSelf, rpcLayer, webSocketMode
|
|
|
148
147
|
*
|
|
149
148
|
* @internal This is typically used internally by `setupDurableObjectWebSocketRpc`
|
|
150
149
|
*/
|
|
151
|
-
export const layerRpcServerWebsocket = (args) => Layer.mergeAll(Layer.
|
|
150
|
+
export const layerRpcServerWebsocket = (args) => Layer.mergeAll(Layer.effect(RpcServer.Protocol, makeSocketProtocol(args)), Layer.succeed(WsContext, WsContext.of({ ws: args.ws })));
|
|
152
151
|
/**
|
|
153
152
|
* Creates the low-level RPC protocol implementation for WebSocket communication.
|
|
154
153
|
*
|
|
@@ -160,29 +159,53 @@ export const layerRpcServerWebsocket = (args) => Layer.mergeAll(Layer.scoped(Rpc
|
|
|
160
159
|
*
|
|
161
160
|
* @internal Used internally by `layerRpcServerWebsocket`
|
|
162
161
|
*/
|
|
163
|
-
const makeSocketProtocol = ({ incomingQueue, ws, onMessage }) => Effect.gen(function* () {
|
|
162
|
+
const makeSocketProtocol = ({ incomingQueue, scope, ws, onMessage, onRequestExit, onProtocolDefect, }) => Effect.gen(function* () {
|
|
164
163
|
const serialization = yield* RpcSerialization.RpcSerialization;
|
|
165
|
-
const disconnects = yield*
|
|
166
|
-
const
|
|
164
|
+
const disconnects = yield* Queue.unbounded();
|
|
165
|
+
const requestIdsWithSchemas = new Set();
|
|
166
|
+
const interruptedExitEncoded = yield* Schema.encodeUnknownEffect(serialization.codecFor(Schema.Exit(Schema.Void, Schema.Never, Schema.Never)))(Exit.interrupt()).pipe(Effect.orDie);
|
|
167
|
+
const writeRaw = (msg) => Effect.sync(() => ws.send(msg));
|
|
167
168
|
let writeRequest;
|
|
168
|
-
const parser = serialization.
|
|
169
|
+
const parser = serialization.makeUnsafe();
|
|
169
170
|
const id = 0;
|
|
170
|
-
const
|
|
171
|
+
const runLifecycleHook = (name, hook) => Effect.sync(hook).pipe(Effect.catchCause((cause) => Effect.logError(`WebSocket RPC ${name} hook failed`, { cause })));
|
|
172
|
+
const writeDefect = (defect) => {
|
|
173
|
+
try {
|
|
174
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(defect));
|
|
175
|
+
const responseEffect = encoded === undefined ? Effect.void : Effect.orDie(writeRaw(encoded));
|
|
176
|
+
const onAccepted = Effect.sync(() => requestIdsWithSchemas.clear()).pipe(Effect.andThen(onProtocolDefect !== undefined
|
|
177
|
+
? runLifecycleHook('onProtocolDefect', () => onProtocolDefect(ws))
|
|
178
|
+
: Effect.void));
|
|
179
|
+
return Effect.ensuring(responseEffect, onAccepted);
|
|
180
|
+
}
|
|
181
|
+
catch (cause) {
|
|
182
|
+
return Effect.die(cause);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const writeResponse = (response, onAccepted) => {
|
|
171
186
|
try {
|
|
172
187
|
const encoded = parser.encode(response);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
return Effect.orDie(writeRaw(encoded));
|
|
188
|
+
const responseEffect = encoded === undefined ? Effect.void : Effect.orDie(writeRaw(encoded));
|
|
189
|
+
return onAccepted === undefined ? responseEffect : Effect.ensuring(responseEffect, onAccepted);
|
|
177
190
|
}
|
|
178
191
|
catch (cause) {
|
|
179
|
-
return
|
|
192
|
+
return writeDefect(cause);
|
|
180
193
|
}
|
|
181
194
|
};
|
|
195
|
+
const writeExit = (requestId, exit) => writeResponse({ _tag: 'Exit', requestId, exit }, Effect.sync(() => requestIdsWithSchemas.delete(requestId)).pipe(Effect.andThen(onRequestExit !== undefined
|
|
196
|
+
? runLifecycleHook('onRequestExit', () => onRequestExit(requestId, ws))
|
|
197
|
+
: Effect.void)));
|
|
198
|
+
const write = (response) => {
|
|
199
|
+
if (response._tag === 'Exit')
|
|
200
|
+
return writeExit(response.requestId, response.exit);
|
|
201
|
+
if (response._tag === 'Defect')
|
|
202
|
+
return writeDefect(response.defect);
|
|
203
|
+
return writeResponse(response);
|
|
204
|
+
};
|
|
182
205
|
const protocol = yield* RpcServer.Protocol.make((writeRequest_) => {
|
|
183
206
|
writeRequest = writeRequest_;
|
|
184
207
|
// Start processing messages now that writeRequest is available
|
|
185
|
-
const startProcessing =
|
|
208
|
+
const startProcessing = Stream.fromQueue(incomingQueue).pipe(Stream.tap((data) => {
|
|
186
209
|
try {
|
|
187
210
|
const decoded = parser.decode(data);
|
|
188
211
|
if (decoded.length === 0)
|
|
@@ -192,18 +215,25 @@ const makeSocketProtocol = ({ incomingQueue, ws, onMessage }) => Effect.gen(func
|
|
|
192
215
|
while: () => i < decoded.length,
|
|
193
216
|
body: () => {
|
|
194
217
|
const request = decoded[i++];
|
|
195
|
-
if (onMessage !== undefined)
|
|
218
|
+
if (onMessage !== undefined)
|
|
196
219
|
onMessage(request, ws);
|
|
220
|
+
if (request._tag === 'Request') {
|
|
221
|
+
requestIdsWithSchemas.add(request.id);
|
|
222
|
+
}
|
|
223
|
+
else if (request._tag === 'Interrupt' && requestIdsWithSchemas.has(request.requestId) === false) {
|
|
224
|
+
// Effect creates this Exit for an unknown request, but its encoder drops it when hibernation has
|
|
225
|
+
// discarded the request schema.
|
|
226
|
+
return writeExit(request.requestId, interruptedExitEncoded);
|
|
197
227
|
}
|
|
198
228
|
return writeRequest(id, request);
|
|
199
229
|
},
|
|
200
|
-
step: constVoid,
|
|
230
|
+
step: Function.constVoid,
|
|
201
231
|
});
|
|
202
232
|
}
|
|
203
233
|
catch (cause) {
|
|
204
|
-
return
|
|
234
|
+
return writeDefect(cause);
|
|
205
235
|
}
|
|
206
|
-
}), Stream.runDrain, Effect.tapCauseLogPretty, Effect.
|
|
236
|
+
}), Stream.runDrain, Effect.tapCauseLogPretty, Effect.forkIn(scope));
|
|
207
237
|
// Start the message processing
|
|
208
238
|
return Effect.map(startProcessing, () => ({
|
|
209
239
|
disconnects,
|
|
@@ -217,6 +247,8 @@ const makeSocketProtocol = ({ incomingQueue, ws, onMessage }) => Effect.gen(func
|
|
|
217
247
|
supportsAck: true,
|
|
218
248
|
supportsTransferables: false,
|
|
219
249
|
supportsSpanPropagation: true,
|
|
250
|
+
supportsNotifications: true,
|
|
251
|
+
codecFor: serialization.codecFor,
|
|
220
252
|
}));
|
|
221
253
|
});
|
|
222
254
|
return protocol;
|