@livestore/common-cf 0.4.0 → 0.5.0-dev.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/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/do-rpc/client.d.ts.map +1 -1
- package/dist/do-rpc/client.js +17 -27
- 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 +99 -0
- package/dist/do-rpc/client.test.js.map +1 -0
- package/dist/do-rpc/rpc.test.js +25 -57
- package/dist/do-rpc/rpc.test.js.map +1 -1
- package/dist/do-rpc/server.d.ts +9 -4
- package/dist/do-rpc/server.d.ts.map +1 -1
- package/dist/do-rpc/server.js +84 -43
- package/dist/do-rpc/server.js.map +1 -1
- package/dist/do-rpc/test-fixtures/rpc-schema.d.ts +27 -27
- 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 +9 -9
- package/dist/do-rpc/test-fixtures/worker.js.map +1 -1
- package/dist/ws-rpc/test-fixtures/rpc-schema.d.ts +27 -27
- package/dist/ws-rpc/test-fixtures/rpc-schema.js +9 -9
- package/dist/ws-rpc/ws-rpc-server.d.ts +6 -5
- package/dist/ws-rpc/ws-rpc-server.d.ts.map +1 -1
- package/dist/ws-rpc/ws-rpc-server.js +17 -17
- package/dist/ws-rpc/ws-rpc-server.js.map +1 -1
- package/dist/ws-rpc/ws-rpc.test.js +27 -59
- package/dist/ws-rpc/ws-rpc.test.js.map +1 -1
- package/package.json +11 -20
- package/src/declare/cf-declare.ts +1 -1
- package/src/do-rpc/client.test.ts +122 -0
- package/src/do-rpc/client.ts +21 -31
- package/src/do-rpc/rpc.test.ts +29 -59
- package/src/do-rpc/server.ts +119 -54
- 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 +37 -28
- package/src/ws-rpc/ws-rpc.test.ts +31 -61
package/src/do-rpc/client.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Fiber,
|
|
4
4
|
FiberMap,
|
|
5
5
|
Layer,
|
|
6
|
+
Option,
|
|
6
7
|
RpcClient,
|
|
7
8
|
type RpcMessage,
|
|
8
9
|
RpcSerialization,
|
|
@@ -11,13 +12,10 @@ import {
|
|
|
11
12
|
|
|
12
13
|
import type * as CfTypes from '../cf-types.ts'
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* Processes a ReadableStream response from streaming RPCs.
|
|
16
|
-
* Reads chunks from the stream and writes them as RPC responses.
|
|
17
|
-
*/
|
|
15
|
+
/** Decodes a streaming-RPC `ReadableStream`'s msgpack frames, writing each out as it arrives. */
|
|
18
16
|
const processReadableStream = (
|
|
19
17
|
stream: CfTypes.ReadableStream,
|
|
20
|
-
parser: ReturnType<typeof RpcSerialization.msgPack.
|
|
18
|
+
parser: ReturnType<typeof RpcSerialization.msgPack.makeUnsafe>,
|
|
21
19
|
writeResponse: (response: any) => Effect.Effect<void>,
|
|
22
20
|
): Effect.Effect<void> =>
|
|
23
21
|
Effect.gen(function* () {
|
|
@@ -31,21 +29,10 @@ const processReadableStream = (
|
|
|
31
29
|
break
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
//
|
|
32
|
+
// Server encodes `[message]` per enqueue; merged enqueues arrive as
|
|
33
|
+
// `[[msg1], [msg2], ...]`. `flat(1)` normalizes both to `[msg1, ...]`.
|
|
35
34
|
const decoded = parser.decode(value as Uint8Array)
|
|
36
|
-
|
|
37
|
-
// Handle array of messages from server.
|
|
38
|
-
// Server sends `parser.encode([message])` per enqueue, so each decoded value is `[message]`.
|
|
39
|
-
// When CF DO RPC merges enqueues in production, we get `[[msg1], [msg2], ...]`.
|
|
40
|
-
// `flat(1)` normalizes both single and merged cases to `[msg1, msg2, ...]`.
|
|
41
|
-
let messages: any[]
|
|
42
|
-
if (Array.isArray(decoded) === true) {
|
|
43
|
-
messages = decoded.flat(1)
|
|
44
|
-
} else {
|
|
45
|
-
messages = [decoded]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Write each message
|
|
35
|
+
const messages = Array.isArray(decoded) === true ? decoded.flat(1) : [decoded]
|
|
49
36
|
for (const message of messages) {
|
|
50
37
|
yield* writeResponse(message)
|
|
51
38
|
}
|
|
@@ -70,9 +57,8 @@ interface MakeDoRpcProtocolArgs {
|
|
|
70
57
|
* Creates a Protocol layer that uses Cloudflare Durable Object RPC calls.
|
|
71
58
|
* This enables direct RPC communication with Durable Objects using Cloudflare's native RPC.
|
|
72
59
|
*/
|
|
73
|
-
export const layerProtocolDurableObject = (
|
|
74
|
-
args
|
|
75
|
-
): Layer.Layer<RpcClient.Protocol> => Layer.scoped(RpcClient.Protocol, makeProtocolDurableObject(args))
|
|
60
|
+
export const layerProtocolDurableObject = (args: MakeDoRpcProtocolArgs): Layer.Layer<RpcClient.Protocol> =>
|
|
61
|
+
Layer.effect(RpcClient.Protocol, makeProtocolDurableObject(args))
|
|
76
62
|
|
|
77
63
|
/**
|
|
78
64
|
* Implementation of the RPC Protocol interface using Cloudflare Durable Object RPC calls.
|
|
@@ -80,26 +66,30 @@ export const layerProtocolDurableObject = (
|
|
|
80
66
|
*/
|
|
81
67
|
const makeProtocolDurableObject = ({
|
|
82
68
|
callRpc,
|
|
83
|
-
}: MakeDoRpcProtocolArgs): Effect.Effect<RpcClient.Protocol['
|
|
69
|
+
}: MakeDoRpcProtocolArgs): Effect.Effect<RpcClient.Protocol['Service'], never, Scope.Scope> =>
|
|
84
70
|
RpcClient.Protocol.make(
|
|
85
71
|
Effect.fnUntraced(function* (writeResponse) {
|
|
86
|
-
const parser = RpcSerialization.msgPack.unsafeMake()
|
|
87
72
|
// Not using an actual `FiberMap` here because it seems to shutdown to early
|
|
88
|
-
// const fiberMap = new Map<string, Fiber.
|
|
73
|
+
// const fiberMap = new Map<string, Fiber.Fiber<void, never>>()
|
|
89
74
|
const fiberMap = yield* FiberMap.make<string, void, never>()
|
|
90
75
|
|
|
91
|
-
|
|
76
|
+
const send = (clientId: number, message: RpcMessage.FromClientEncoded): Effect.Effect<void> => {
|
|
92
77
|
if (message._tag !== 'Request') {
|
|
93
78
|
if (message._tag === 'Interrupt') {
|
|
94
79
|
return Effect.gen(function* () {
|
|
95
80
|
const fiber = yield* FiberMap.get(fiberMap, message.requestId)
|
|
96
|
-
|
|
81
|
+
if (Option.isSome(fiber) === true) {
|
|
82
|
+
yield* Fiber.interrupt(fiber.value)
|
|
83
|
+
}
|
|
97
84
|
}).pipe(Effect.orDie)
|
|
98
85
|
}
|
|
99
86
|
|
|
100
87
|
return Effect.void
|
|
101
88
|
}
|
|
102
89
|
|
|
90
|
+
// MessagePack parsers buffer incomplete frames, so scope one parser to one DO RPC call.
|
|
91
|
+
const parser = RpcSerialization.msgPack.makeUnsafe()
|
|
92
|
+
|
|
103
93
|
// Wrap single Request in array to match server expected format
|
|
104
94
|
const serializedPayload = parser.encode([message]) as Uint8Array
|
|
105
95
|
|
|
@@ -111,16 +101,16 @@ const makeProtocolDurableObject = ({
|
|
|
111
101
|
const fiber = yield* processReadableStream(
|
|
112
102
|
serializedResponse as CfTypes.ReadableStream,
|
|
113
103
|
parser,
|
|
114
|
-
writeResponse,
|
|
104
|
+
(response) => writeResponse(clientId, response),
|
|
115
105
|
).pipe(
|
|
116
106
|
// Effect.tapCauseLogPretty,
|
|
117
|
-
Effect.
|
|
107
|
+
Effect.forkChild,
|
|
118
108
|
)
|
|
119
109
|
|
|
120
110
|
// fiberMap.set(message.id, fiber)
|
|
121
111
|
yield* FiberMap.set(fiberMap, message.id, fiber)
|
|
122
112
|
|
|
123
|
-
yield* fiber
|
|
113
|
+
yield* Fiber.join(fiber)
|
|
124
114
|
|
|
125
115
|
return
|
|
126
116
|
}
|
|
@@ -138,7 +128,7 @@ const makeProtocolDurableObject = ({
|
|
|
138
128
|
|
|
139
129
|
// Process each response
|
|
140
130
|
for (const response of responseArray) {
|
|
141
|
-
yield* writeResponse(response)
|
|
131
|
+
yield* writeResponse(clientId, response)
|
|
142
132
|
}
|
|
143
133
|
}).pipe(Effect.withSpan('do-rpc-client:send'), Effect.orDie) // Ensure never error type
|
|
144
134
|
}
|
package/src/do-rpc/rpc.test.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { expect } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
-
import {
|
|
4
|
+
import { WranglerDevServer } from '@livestore/utils-dev/wrangler'
|
|
5
5
|
import {
|
|
6
|
-
Chunk,
|
|
7
6
|
Effect,
|
|
8
7
|
FetchHttpClient,
|
|
9
8
|
Layer,
|
|
10
|
-
Logger,
|
|
11
|
-
LogLevel,
|
|
12
9
|
Option,
|
|
10
|
+
References,
|
|
13
11
|
RpcClient,
|
|
14
12
|
RpcSerialization,
|
|
15
13
|
Stream,
|
|
@@ -23,22 +21,26 @@ const testTimeout = 60_000
|
|
|
23
21
|
const withWranglerTest = Vitest.makeWithTestCtx({
|
|
24
22
|
timeout: testTimeout,
|
|
25
23
|
makeLayer: () =>
|
|
26
|
-
|
|
24
|
+
WranglerDevServer.layer({
|
|
27
25
|
cwd: `${import.meta.dirname}/test-fixtures`,
|
|
28
26
|
}).pipe(
|
|
29
27
|
Layer.provide(
|
|
30
|
-
Layer.mergeAll(
|
|
28
|
+
Layer.mergeAll(
|
|
29
|
+
PlatformNode.NodeServices.layer,
|
|
30
|
+
FetchHttpClient.layer,
|
|
31
|
+
Layer.succeed(References.MinimumLogLevel, 'Debug'),
|
|
32
|
+
),
|
|
31
33
|
),
|
|
32
34
|
),
|
|
33
35
|
})
|
|
34
36
|
|
|
35
37
|
const ProtocolLive = Layer.suspend(() =>
|
|
36
38
|
Effect.gen(function* () {
|
|
37
|
-
const server = yield*
|
|
39
|
+
const server = yield* WranglerDevServer.WranglerDevServer
|
|
38
40
|
return RpcClient.layerProtocolHttp({
|
|
39
41
|
url: `${server.url}/rpc`,
|
|
40
42
|
}).pipe(Layer.provide([FetchHttpClient.layer, RpcSerialization.layerJson]))
|
|
41
|
-
}).pipe(Layer.
|
|
43
|
+
}).pipe(Layer.unwrap),
|
|
42
44
|
)
|
|
43
45
|
|
|
44
46
|
/**
|
|
@@ -61,7 +63,7 @@ const ProtocolLive = Layer.suspend(() =>
|
|
|
61
63
|
|
|
62
64
|
Vitest.describe('Durable Object RPC', { timeout: testTimeout }, () => {
|
|
63
65
|
// Direct HTTP RPC client tests
|
|
64
|
-
Vitest.
|
|
66
|
+
Vitest.live('should call ping method', (test) =>
|
|
65
67
|
Effect.gen(function* () {
|
|
66
68
|
const client = yield* RpcClient.make(TestRpcs)
|
|
67
69
|
const result = yield* client.Ping({ message: 'Hello HTTP RPC' })
|
|
@@ -69,7 +71,7 @@ Vitest.describe('Durable Object RPC', { timeout: testTimeout }, () => {
|
|
|
69
71
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
70
72
|
)
|
|
71
73
|
|
|
72
|
-
Vitest.
|
|
74
|
+
Vitest.live('should call echo method', (test) =>
|
|
73
75
|
Effect.gen(function* () {
|
|
74
76
|
const client = yield* RpcClient.make(TestRpcs)
|
|
75
77
|
const result = yield* client.Echo({ text: 'Echo' })
|
|
@@ -77,7 +79,7 @@ Vitest.describe('Durable Object RPC', { timeout: testTimeout }, () => {
|
|
|
77
79
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
78
80
|
)
|
|
79
81
|
|
|
80
|
-
Vitest.
|
|
82
|
+
Vitest.live('should call add method', (test) =>
|
|
81
83
|
Effect.gen(function* () {
|
|
82
84
|
const client = yield* RpcClient.make(TestRpcs)
|
|
83
85
|
const result = yield* client.Add({ a: 15, b: 25 })
|
|
@@ -85,103 +87,71 @@ Vitest.describe('Durable Object RPC', { timeout: testTimeout }, () => {
|
|
|
85
87
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
86
88
|
)
|
|
87
89
|
|
|
88
|
-
Vitest.
|
|
90
|
+
Vitest.live('should handle RPC fail method', (test) =>
|
|
89
91
|
Effect.gen(function* () {
|
|
90
92
|
const client = yield* RpcClient.make(TestRpcs)
|
|
91
93
|
const error = yield* client.Fail({ message: 'test http failure' }).pipe(Effect.exit)
|
|
92
94
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
93
|
-
"
|
|
94
|
-
"_id": "Exit",
|
|
95
|
-
"_tag": "Failure",
|
|
96
|
-
"cause": {
|
|
97
|
-
"_id": "Cause",
|
|
98
|
-
"_tag": "Die",
|
|
99
|
-
"defect": "RPC failure: test http failure"
|
|
100
|
-
}
|
|
101
|
-
}"
|
|
95
|
+
"Failure(Cause([Die("RPC failure: test http failure")]))"
|
|
102
96
|
`)
|
|
103
97
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
104
98
|
)
|
|
105
99
|
|
|
106
|
-
Vitest.
|
|
100
|
+
Vitest.live('should handle defect method', (test) =>
|
|
107
101
|
Effect.gen(function* () {
|
|
108
102
|
const client = yield* RpcClient.make(TestRpcs)
|
|
109
103
|
const error = yield* client.Defect({ message: 'test http defect' }).pipe(Effect.exit)
|
|
110
104
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
111
|
-
"
|
|
112
|
-
"_id": "Exit",
|
|
113
|
-
"_tag": "Failure",
|
|
114
|
-
"cause": {
|
|
115
|
-
"_id": "Cause",
|
|
116
|
-
"_tag": "Die",
|
|
117
|
-
"defect": "some defect: test http defect"
|
|
118
|
-
}
|
|
119
|
-
}"
|
|
105
|
+
"Failure(Cause([Die("some defect: test http defect")]))"
|
|
120
106
|
`)
|
|
121
107
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
122
108
|
)
|
|
123
109
|
|
|
124
|
-
Vitest.
|
|
110
|
+
Vitest.live('should handle streaming RPC via HTTP', (test) =>
|
|
125
111
|
Effect.gen(function* () {
|
|
126
112
|
const client = yield* RpcClient.make(TestRpcs)
|
|
127
113
|
const stream = client.Stream({}).pipe(
|
|
128
114
|
Stream.take(4),
|
|
129
115
|
Stream.map((c) => c.maybeNumber.pipe(Option.getOrUndefined)),
|
|
130
116
|
)
|
|
131
|
-
const
|
|
132
|
-
expect(
|
|
117
|
+
const result = yield* Stream.runCollect(stream)
|
|
118
|
+
expect(result).toEqual([1, 4, 9, 16]) // squares of 1,2,3,4
|
|
133
119
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
134
120
|
)
|
|
135
121
|
|
|
136
|
-
Vitest.
|
|
122
|
+
Vitest.live('should handle streaming RPC with error via HTTP', (test) =>
|
|
137
123
|
Effect.gen(function* () {
|
|
138
124
|
const client = yield* RpcClient.make(TestRpcs)
|
|
139
125
|
const stream = client.StreamError({ count: 5, errorAfter: 4 })
|
|
140
126
|
const error = yield* Stream.runCollect(stream).pipe(Effect.exit)
|
|
141
127
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
142
|
-
"
|
|
143
|
-
"_id": "Exit",
|
|
144
|
-
"_tag": "Failure",
|
|
145
|
-
"cause": {
|
|
146
|
-
"_id": "Cause",
|
|
147
|
-
"_tag": "Fail",
|
|
148
|
-
"failure": "Stream error after 4: got 9"
|
|
149
|
-
}
|
|
150
|
-
}"
|
|
128
|
+
"Failure(Cause([Fail("Stream error after 4: got 9")]))"
|
|
151
129
|
`)
|
|
152
130
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
153
131
|
)
|
|
154
132
|
|
|
155
|
-
Vitest.
|
|
133
|
+
Vitest.live('should handle streaming RPC with defect via HTTP', (test) =>
|
|
156
134
|
Effect.gen(function* () {
|
|
157
135
|
const client = yield* RpcClient.make(TestRpcs)
|
|
158
136
|
const stream = client.StreamDefect({ count: 4, defectAfter: 1 })
|
|
159
137
|
const error = yield* Stream.runCollect(stream).pipe(Effect.exit)
|
|
160
138
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
161
|
-
"
|
|
162
|
-
"_id": "Exit",
|
|
163
|
-
"_tag": "Failure",
|
|
164
|
-
"cause": {
|
|
165
|
-
"_id": "Cause",
|
|
166
|
-
"_tag": "Die",
|
|
167
|
-
"defect": "Stream defect after 1: got 4"
|
|
168
|
-
}
|
|
169
|
-
}"
|
|
139
|
+
"Failure(Cause([Die("Stream defect after 1: got 4")]))"
|
|
170
140
|
`)
|
|
171
141
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
172
142
|
)
|
|
173
143
|
|
|
174
|
-
Vitest.
|
|
144
|
+
Vitest.live('should handle stream interruption via HTTP', (test) =>
|
|
175
145
|
Effect.gen(function* () {
|
|
176
146
|
const client = yield* RpcClient.make(TestRpcs)
|
|
177
147
|
const stream = client.StreamInterruptible({ delay: 50, interruptAfterCount: 3 }).pipe(Stream.take(3))
|
|
178
|
-
const
|
|
179
|
-
expect(
|
|
148
|
+
const result = yield* Stream.runCollect(stream)
|
|
149
|
+
expect(result).toEqual([1, 2, 3])
|
|
180
150
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
181
151
|
)
|
|
182
152
|
|
|
183
153
|
// TODO @IMax153
|
|
184
|
-
Vitest.
|
|
154
|
+
Vitest.live.skip('should handle streaming RPC bug scenario', (test) =>
|
|
185
155
|
Effect.gen(function* () {
|
|
186
156
|
const client = yield* RpcClient.make(TestRpcs)
|
|
187
157
|
yield* client.StreamBugScenarioDoClient({})
|
|
@@ -225,7 +195,7 @@ Vitest.describe('Durable Object RPC', { timeout: testTimeout }, () => {
|
|
|
225
195
|
errorSchema: [Function: Never],
|
|
226
196
|
annotations: { _id: 'Context', services: [] },
|
|
227
197
|
middlewares: Set(0) {},
|
|
228
|
-
key: '
|
|
198
|
+
key: 'effect/unstable/rpc/Rpc/StreamBugScenarioDoServer'
|
|
229
199
|
} {}
|
|
230
200
|
|
|
231
201
|
Killing wrangler process...
|