@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
package/src/do-rpc/client.ts
CHANGED
|
@@ -1,59 +1,79 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Data,
|
|
2
3
|
Effect,
|
|
3
4
|
Fiber,
|
|
4
5
|
FiberMap,
|
|
5
6
|
Layer,
|
|
7
|
+
Option,
|
|
6
8
|
RpcClient,
|
|
7
|
-
|
|
9
|
+
RpcClientError,
|
|
10
|
+
RpcMessage,
|
|
8
11
|
RpcSerialization,
|
|
12
|
+
Schema,
|
|
9
13
|
type Scope,
|
|
10
14
|
} from '@livestore/utils/effect'
|
|
11
15
|
|
|
12
16
|
import type * as CfTypes from '../cf-types.ts'
|
|
13
17
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
/** A transferred stream can lose Cloudflare's RPC error flags when its connection breaks. */
|
|
19
|
+
export class DoRpcReadError extends Data.TaggedError('DoRpcReadError')<{ cause: unknown }> {}
|
|
20
|
+
|
|
21
|
+
const isEncodedRpcMessage = Schema.is(RpcMessage.EncodedSchema)
|
|
22
|
+
|
|
23
|
+
const isFromServerEncoded = (message: unknown): message is RpcMessage.FromServerEncoded => {
|
|
24
|
+
if (isEncodedRpcMessage(message) === false) return false
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
message._tag === 'Chunk' ||
|
|
28
|
+
message._tag === 'Exit' ||
|
|
29
|
+
message._tag === 'Defect' ||
|
|
30
|
+
message._tag === 'Pong' ||
|
|
31
|
+
message._tag === 'Request'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Decodes a streaming-RPC `ReadableStream`'s binary frames, writing each out as it arrives. */
|
|
18
36
|
const processReadableStream = (
|
|
19
37
|
stream: CfTypes.ReadableStream,
|
|
20
|
-
parser:
|
|
21
|
-
writeResponse: (response:
|
|
22
|
-
): Effect.Effect<void> =>
|
|
38
|
+
parser: RpcSerialization.Parser,
|
|
39
|
+
writeResponse: (response: RpcMessage.FromServerEncoded) => Effect.Effect<void>,
|
|
40
|
+
): Effect.Effect<void, RpcClientError.RpcClientError> =>
|
|
23
41
|
Effect.gen(function* () {
|
|
24
42
|
const reader = stream.getReader()
|
|
25
43
|
|
|
26
44
|
yield* Effect.gen(function* () {
|
|
27
45
|
while (true) {
|
|
28
|
-
const { done, value } = yield* Effect.tryPromise(
|
|
46
|
+
const { done, value } = yield* Effect.tryPromise({
|
|
47
|
+
try: () => reader.read(),
|
|
48
|
+
catch: (cause) => transportFailure(new DoRpcReadError({ cause })),
|
|
49
|
+
})
|
|
29
50
|
|
|
30
51
|
if (done === true) {
|
|
31
|
-
|
|
52
|
+
return yield* transportFailure(new DoRpcReadError({ cause: new Error('RPC stream ended before its exit') }))
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
|
|
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]
|
|
55
|
+
if (value instanceof Uint8Array === false) {
|
|
56
|
+
return yield* Effect.die('Received a non-binary RPC response')
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
for (const message of parser.decode(value)) {
|
|
60
|
+
if (isFromServerEncoded(message) === false) {
|
|
61
|
+
return yield* Effect.die('Received an invalid RPC response')
|
|
62
|
+
}
|
|
50
63
|
yield* writeResponse(message)
|
|
64
|
+
// The RPC exit completes the request; do not wait for the transferred stream to close too.
|
|
65
|
+
if (message._tag === 'Exit') return
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
}).pipe(
|
|
54
69
|
Effect.withSpan('do-rpc-client:processReadableStream'),
|
|
70
|
+
// Cancelling an errored stream rejects with that same error; ignore it so the original failure surfaces.
|
|
55
71
|
Effect.ensuring(
|
|
56
|
-
Effect.
|
|
72
|
+
Effect.sync(() => {
|
|
73
|
+
// Cancellation is best effort: a disconnected remote producer may never acknowledge it.
|
|
74
|
+
void reader.cancel().catch(() => {})
|
|
75
|
+
reader.releaseLock()
|
|
76
|
+
}),
|
|
57
77
|
),
|
|
58
78
|
)
|
|
59
79
|
})
|
|
@@ -70,9 +90,10 @@ interface MakeDoRpcProtocolArgs {
|
|
|
70
90
|
* Creates a Protocol layer that uses Cloudflare Durable Object RPC calls.
|
|
71
91
|
* This enables direct RPC communication with Durable Objects using Cloudflare's native RPC.
|
|
72
92
|
*/
|
|
73
|
-
export const layerProtocolDurableObject = (
|
|
74
|
-
args
|
|
75
|
-
|
|
93
|
+
export const layerProtocolDurableObject = (args: MakeDoRpcProtocolArgs): Layer.Layer<RpcClient.Protocol> =>
|
|
94
|
+
Layer.effect(RpcClient.Protocol, makeProtocolDurableObject(args)).pipe(
|
|
95
|
+
Layer.provide(RpcSerialization.layerSchemaBinary()),
|
|
96
|
+
)
|
|
76
97
|
|
|
77
98
|
/**
|
|
78
99
|
* Implementation of the RPC Protocol interface using Cloudflare Durable Object RPC calls.
|
|
@@ -80,73 +101,83 @@ export const layerProtocolDurableObject = (
|
|
|
80
101
|
*/
|
|
81
102
|
const makeProtocolDurableObject = ({
|
|
82
103
|
callRpc,
|
|
83
|
-
}: MakeDoRpcProtocolArgs): Effect.Effect<
|
|
104
|
+
}: MakeDoRpcProtocolArgs): Effect.Effect<
|
|
105
|
+
RpcClient.Protocol['Service'],
|
|
106
|
+
never,
|
|
107
|
+
Scope.Scope | RpcSerialization.RpcSerialization
|
|
108
|
+
> =>
|
|
84
109
|
RpcClient.Protocol.make(
|
|
85
110
|
Effect.fnUntraced(function* (writeResponse) {
|
|
86
|
-
const
|
|
111
|
+
const serialization = yield* RpcSerialization.RpcSerialization
|
|
87
112
|
// Not using an actual `FiberMap` here because it seems to shutdown to early
|
|
88
|
-
// const fiberMap = new Map<string, Fiber.
|
|
89
|
-
const fiberMap = yield* FiberMap.make<string, void,
|
|
113
|
+
// const fiberMap = new Map<string, Fiber.Fiber<void, never>>()
|
|
114
|
+
const fiberMap = yield* FiberMap.make<string, void, RpcClientError.RpcClientError>()
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
const send = (
|
|
117
|
+
clientId: number,
|
|
118
|
+
message: RpcMessage.FromClientEncoded,
|
|
119
|
+
): Effect.Effect<void, RpcClientError.RpcClientError> => {
|
|
92
120
|
if (message._tag !== 'Request') {
|
|
93
121
|
if (message._tag === 'Interrupt') {
|
|
94
122
|
return Effect.gen(function* () {
|
|
95
123
|
const fiber = yield* FiberMap.get(fiberMap, message.requestId)
|
|
96
|
-
|
|
124
|
+
if (Option.isSome(fiber) === true) {
|
|
125
|
+
yield* Fiber.interrupt(fiber.value)
|
|
126
|
+
}
|
|
97
127
|
}).pipe(Effect.orDie)
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
return Effect.void
|
|
101
131
|
}
|
|
102
132
|
|
|
133
|
+
// Binary parsers hold stream framing state, so scope one parser to one DO RPC call.
|
|
134
|
+
const parser = serialization.makeUnsafe()
|
|
135
|
+
|
|
103
136
|
// Wrap single Request in array to match server expected format
|
|
104
|
-
const serializedPayload = parser.encode([message])
|
|
137
|
+
const serializedPayload = parser.encode([message])
|
|
138
|
+
if (serializedPayload instanceof Uint8Array === false) {
|
|
139
|
+
return Effect.die('SchemaBinary RPC serialization did not produce bytes')
|
|
140
|
+
}
|
|
105
141
|
|
|
106
142
|
return Effect.gen(function* () {
|
|
107
|
-
const serializedResponse = yield* Effect.tryPromise(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// fiberMap.set(message.id, fiber)
|
|
121
|
-
yield* FiberMap.set(fiberMap, message.id, fiber)
|
|
122
|
-
|
|
123
|
-
yield* fiber
|
|
124
|
-
|
|
143
|
+
const serializedResponse = yield* Effect.tryPromise({
|
|
144
|
+
try: () => callRpc(serializedPayload),
|
|
145
|
+
catch: transportFailure,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
if (serializedResponse instanceof Uint8Array) {
|
|
149
|
+
for (const response of parser.decode(serializedResponse)) {
|
|
150
|
+
if (isFromServerEncoded(response) === false) {
|
|
151
|
+
return yield* Effect.die('Received an invalid RPC response')
|
|
152
|
+
}
|
|
153
|
+
yield* writeResponse(clientId, response)
|
|
154
|
+
}
|
|
125
155
|
return
|
|
126
156
|
}
|
|
127
157
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Normalize nested arrays from server serialization (same as streaming path)
|
|
132
|
-
let responseArray: any[]
|
|
133
|
-
if (Array.isArray(decoded) === true) {
|
|
134
|
-
responseArray = decoded.flat(1)
|
|
135
|
-
} else {
|
|
136
|
-
responseArray = [decoded]
|
|
137
|
-
}
|
|
158
|
+
const fiber = yield* processReadableStream(serializedResponse, parser, (response) =>
|
|
159
|
+
writeResponse(clientId, response),
|
|
160
|
+
).pipe(Effect.forkChild)
|
|
138
161
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
}).pipe(Effect.withSpan('do-rpc-client:send'), Effect.orDie) // Ensure never error type
|
|
162
|
+
yield* FiberMap.set(fiberMap, message.id, fiber)
|
|
163
|
+
yield* Fiber.join(fiber)
|
|
164
|
+
}).pipe(Effect.withSpan('do-rpc-client:send'))
|
|
144
165
|
}
|
|
145
166
|
|
|
146
167
|
return {
|
|
147
168
|
send,
|
|
148
169
|
supportsAck: false, // DO RPC doesn't support ack mechanism like WebSockets
|
|
149
170
|
supportsTransferables: false, // DO RPC doesn't support transferables yet
|
|
171
|
+
codecFor: serialization.codecFor,
|
|
150
172
|
}
|
|
151
173
|
}),
|
|
152
174
|
)
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A rejected DO call or stream read fails only the request that made it (Effect RPC routes `send` failures per
|
|
178
|
+
* request). `RpcClientError` reasons are a closed union, so the original rejection rides as `RpcClientDefect.cause`.
|
|
179
|
+
*/
|
|
180
|
+
const transportFailure = (cause: unknown) =>
|
|
181
|
+
new RpcClientError.RpcClientError({
|
|
182
|
+
reason: new RpcClientError.RpcClientDefect({ message: 'Durable Object RPC call failed', cause }),
|
|
183
|
+
})
|
package/src/do-rpc/mod.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Re-export client and server implementations for backward compatibility
|
|
2
|
-
export { layerProtocolDurableObject } from './client.ts'
|
|
2
|
+
export { DoRpcReadError, layerProtocolDurableObject } from './client.ts'
|
|
3
3
|
export { type ClientDoWithRpcCallback, emitStreamResponse, toDurableObjectHandler } from './server.ts'
|
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,110 +87,78 @@ 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({})
|
|
188
158
|
/*
|
|
189
159
|
|
|
190
160
|
|
|
191
|
-
In
|
|
161
|
+
In the installed `@effect/rpc` `RpcClient.js`:
|
|
192
162
|
add the console log
|
|
193
163
|
```
|
|
194
164
|
yield* Scope.addFinalizerExit(scope, exit => {
|
|
@@ -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...
|