@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
|
@@ -18,16 +18,17 @@
|
|
|
18
18
|
import { notYetImplemented, omitUndefineds } from '@livestore/utils'
|
|
19
19
|
import {
|
|
20
20
|
Context,
|
|
21
|
-
constVoid,
|
|
22
21
|
Effect,
|
|
22
|
+
Function,
|
|
23
23
|
Exit,
|
|
24
24
|
Layer,
|
|
25
25
|
Logger,
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
Queue,
|
|
27
|
+
References,
|
|
28
28
|
RpcMessage,
|
|
29
29
|
RpcSerialization,
|
|
30
30
|
RpcServer,
|
|
31
|
+
Schema,
|
|
31
32
|
Scope,
|
|
32
33
|
Stream,
|
|
33
34
|
} from '@livestore/utils/effect'
|
|
@@ -39,7 +40,7 @@ import type * as CfTypes from '../cf-types.ts'
|
|
|
39
40
|
* This is useful for reading WebSocket attachment data (e.g., forwarded headers)
|
|
40
41
|
* inside RPC handlers.
|
|
41
42
|
*/
|
|
42
|
-
export class WsContext extends Context.
|
|
43
|
+
export class WsContext extends Context.Service<WsContext, { readonly ws: CfTypes.WebSocket }>()('WsContext') {}
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Configuration options for setting up WebSocket RPC on a Durable Object.
|
|
@@ -55,7 +56,7 @@ export interface DurableObjectWebSocketRpcConfig {
|
|
|
55
56
|
webSocketMode: 'hibernate' | 'accept'
|
|
56
57
|
/**
|
|
57
58
|
* Effect RPC layer that requires `RpcServer.Protocol` (and `WsContext` if used)
|
|
58
|
-
* and provides the RPC server
|
|
59
|
+
* and provides the RPC server services.
|
|
59
60
|
*
|
|
60
61
|
* This is typically created by:
|
|
61
62
|
* ```typescript
|
|
@@ -71,6 +72,10 @@ export interface DurableObjectWebSocketRpcConfig {
|
|
|
71
72
|
rpcLayer: Layer.Layer<never, never, RpcServer.Protocol | WsContext>
|
|
72
73
|
/** Function to get access to incoming requests */
|
|
73
74
|
onMessage?: (msg: RpcMessage.FromClientEncoded, ws: CfTypes.WebSocket) => void
|
|
75
|
+
/** Called after a terminal RPC response has been accepted by the serializer. */
|
|
76
|
+
onRequestExit?: (requestId: string | number, ws: CfTypes.WebSocket) => void
|
|
77
|
+
/** Called after a connection-level RPC defect has been accepted by the serializer. */
|
|
78
|
+
onProtocolDefect?: (ws: CfTypes.WebSocket) => void
|
|
74
79
|
mainLayer?: Layer.Layer<never>
|
|
75
80
|
}
|
|
76
81
|
|
|
@@ -139,6 +144,8 @@ export const setupDurableObjectWebSocketRpc = ({
|
|
|
139
144
|
rpcLayer,
|
|
140
145
|
webSocketMode,
|
|
141
146
|
onMessage,
|
|
147
|
+
onRequestExit,
|
|
148
|
+
onProtocolDefect,
|
|
142
149
|
mainLayer,
|
|
143
150
|
}: DurableObjectWebSocketRpcConfig) => {
|
|
144
151
|
if (webSocketMode === 'accept') {
|
|
@@ -148,7 +155,7 @@ export const setupDurableObjectWebSocketRpc = ({
|
|
|
148
155
|
const serverCtxMap = new Map<
|
|
149
156
|
CfTypes.WebSocket,
|
|
150
157
|
{
|
|
151
|
-
scope: Scope.
|
|
158
|
+
scope: Scope.Closeable
|
|
152
159
|
onMessage: (message: string | ArrayBuffer) => Promise<void>
|
|
153
160
|
}
|
|
154
161
|
>()
|
|
@@ -163,33 +170,31 @@ export const setupDurableObjectWebSocketRpc = ({
|
|
|
163
170
|
|
|
164
171
|
const scope = yield* Scope.make()
|
|
165
172
|
|
|
166
|
-
const incomingQueue = yield*
|
|
173
|
+
const incomingQueue = yield* Queue.unbounded<Uint8Array | string>()
|
|
167
174
|
|
|
168
|
-
yield* Scope.addFinalizer(scope,
|
|
175
|
+
yield* Scope.addFinalizer(scope, Queue.shutdown(incomingQueue).pipe(Effect.asVoid))
|
|
169
176
|
|
|
170
177
|
const ProtocolLive = layerRpcServerWebsocket({
|
|
171
178
|
ws,
|
|
179
|
+
scope,
|
|
172
180
|
incomingQueue,
|
|
173
|
-
...omitUndefineds({ onMessage }),
|
|
181
|
+
...omitUndefineds({ onMessage, onRequestExit, onProtocolDefect }),
|
|
174
182
|
}).pipe(Layer.provide(RpcSerialization.layerJson))
|
|
175
183
|
|
|
176
184
|
const ServerLive = rpcLayer.pipe(Layer.provide(ProtocolLive))
|
|
177
185
|
|
|
178
186
|
yield* Layer.launch(ServerLive).pipe(Effect.tapCauseLogPretty, Effect.forkIn(scope))
|
|
179
187
|
|
|
180
|
-
const
|
|
188
|
+
const services = yield* Effect.context()
|
|
181
189
|
|
|
182
190
|
const ctx = {
|
|
183
191
|
scope,
|
|
184
192
|
onMessage: (message: string | ArrayBuffer) =>
|
|
185
|
-
incomingQueue
|
|
186
|
-
.
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
Effect.provide(runtime),
|
|
191
|
-
Effect.runPromise,
|
|
192
|
-
),
|
|
193
|
+
Queue.offer(incomingQueue, message as Uint8Array | string).pipe(
|
|
194
|
+
Effect.asVoid,
|
|
195
|
+
Effect.withSpan('ws-rpc-server/onMessage', { root: true }),
|
|
196
|
+
Effect.runPromiseWith(services),
|
|
197
|
+
),
|
|
193
198
|
}
|
|
194
199
|
|
|
195
200
|
serverCtxMap.set(ws, ctx)
|
|
@@ -197,8 +202,14 @@ export const setupDurableObjectWebSocketRpc = ({
|
|
|
197
202
|
return ctx
|
|
198
203
|
}).pipe(
|
|
199
204
|
Effect.tapCauseLogPretty,
|
|
200
|
-
|
|
201
|
-
Effect.provide(
|
|
205
|
+
Effect.annotateLogs({ thread: 'ws-rpc-server' }),
|
|
206
|
+
Effect.provide(
|
|
207
|
+
Layer.mergeAll(
|
|
208
|
+
Logger.layer([Logger.consoleStructured]),
|
|
209
|
+
Layer.succeed(References.MinimumLogLevel, 'Debug'), // Useful for debugging
|
|
210
|
+
mainLayer ?? Layer.empty,
|
|
211
|
+
),
|
|
212
|
+
),
|
|
202
213
|
Effect.withSpan('effect-ws-rpc-server'),
|
|
203
214
|
Effect.runPromise,
|
|
204
215
|
)
|
|
@@ -233,9 +244,12 @@ export const setupDurableObjectWebSocketRpc = ({
|
|
|
233
244
|
*/
|
|
234
245
|
export interface WsRpcServerArgs {
|
|
235
246
|
ws: CfTypes.WebSocket
|
|
247
|
+
scope: Scope.Scope
|
|
236
248
|
onMessage?: (message: RpcMessage.FromClientEncoded, ws: CfTypes.WebSocket) => void
|
|
237
|
-
|
|
238
|
-
|
|
249
|
+
onRequestExit?: (requestId: string | number, ws: CfTypes.WebSocket) => void
|
|
250
|
+
onProtocolDefect?: (ws: CfTypes.WebSocket) => void
|
|
251
|
+
/** Queue for receiving incoming messages from the WebSocket */
|
|
252
|
+
incomingQueue: Queue.Queue<Uint8Array | string>
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
/**
|
|
@@ -253,7 +267,10 @@ export interface WsRpcServerArgs {
|
|
|
253
267
|
* @internal This is typically used internally by `setupDurableObjectWebSocketRpc`
|
|
254
268
|
*/
|
|
255
269
|
export const layerRpcServerWebsocket = (args: WsRpcServerArgs) =>
|
|
256
|
-
Layer.mergeAll(
|
|
270
|
+
Layer.mergeAll(
|
|
271
|
+
Layer.effect(RpcServer.Protocol, makeSocketProtocol(args)),
|
|
272
|
+
Layer.succeed(WsContext, WsContext.of({ ws: args.ws })),
|
|
273
|
+
)
|
|
257
274
|
|
|
258
275
|
/**
|
|
259
276
|
* Creates the low-level RPC protocol implementation for WebSocket communication.
|
|
@@ -266,35 +283,84 @@ export const layerRpcServerWebsocket = (args: WsRpcServerArgs) =>
|
|
|
266
283
|
*
|
|
267
284
|
* @internal Used internally by `layerRpcServerWebsocket`
|
|
268
285
|
*/
|
|
269
|
-
const makeSocketProtocol = ({
|
|
286
|
+
const makeSocketProtocol = ({
|
|
287
|
+
incomingQueue,
|
|
288
|
+
scope,
|
|
289
|
+
ws,
|
|
290
|
+
onMessage,
|
|
291
|
+
onRequestExit,
|
|
292
|
+
onProtocolDefect,
|
|
293
|
+
}: WsRpcServerArgs) =>
|
|
270
294
|
Effect.gen(function* () {
|
|
271
295
|
const serialization = yield* RpcSerialization.RpcSerialization
|
|
272
|
-
const disconnects = yield*
|
|
296
|
+
const disconnects = yield* Queue.unbounded<number>()
|
|
297
|
+
const requestIdsWithSchemas = new Set<string | number>()
|
|
298
|
+
const interruptedExitEncoded = yield* Schema.encodeUnknownEffect(
|
|
299
|
+
serialization.codecFor(Schema.Exit(Schema.Void, Schema.Never, Schema.Never)),
|
|
300
|
+
)(Exit.interrupt()).pipe(Effect.orDie)
|
|
273
301
|
|
|
274
|
-
const writeRaw = (msg: Uint8Array | string) => Effect.
|
|
302
|
+
const writeRaw = (msg: Uint8Array | string) => Effect.sync(() => ws.send(msg))
|
|
275
303
|
|
|
276
304
|
let writeRequest!: (clientId: number, message: RpcMessage.FromClientEncoded) => Effect.Effect<void>
|
|
277
305
|
|
|
278
|
-
const parser = serialization.
|
|
306
|
+
const parser = serialization.makeUnsafe()
|
|
279
307
|
const id = 0
|
|
280
308
|
|
|
281
|
-
const
|
|
309
|
+
const runLifecycleHook = (name: string, hook: () => void) =>
|
|
310
|
+
Effect.sync(hook).pipe(
|
|
311
|
+
Effect.catchCause((cause) => Effect.logError(`WebSocket RPC ${name} hook failed`, { cause })),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
const writeDefect = (defect: unknown) => {
|
|
315
|
+
try {
|
|
316
|
+
const encoded = parser.encode(RpcMessage.ResponseDefectEncoded(defect))
|
|
317
|
+
const responseEffect = encoded === undefined ? Effect.void : Effect.orDie(writeRaw(encoded))
|
|
318
|
+
const onAccepted = Effect.sync(() => requestIdsWithSchemas.clear()).pipe(
|
|
319
|
+
Effect.andThen(
|
|
320
|
+
onProtocolDefect !== undefined
|
|
321
|
+
? runLifecycleHook('onProtocolDefect', () => onProtocolDefect(ws))
|
|
322
|
+
: Effect.void,
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
return Effect.ensuring(responseEffect, onAccepted)
|
|
326
|
+
} catch (cause) {
|
|
327
|
+
return Effect.die(cause)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const writeResponse = (response: unknown, onAccepted?: Effect.Effect<void>) => {
|
|
282
332
|
try {
|
|
283
333
|
const encoded = parser.encode(response)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
return Effect.orDie(writeRaw(encoded))
|
|
334
|
+
const responseEffect = encoded === undefined ? Effect.void : Effect.orDie(writeRaw(encoded))
|
|
335
|
+
return onAccepted === undefined ? responseEffect : Effect.ensuring(responseEffect, onAccepted)
|
|
288
336
|
} catch (cause) {
|
|
289
|
-
return
|
|
337
|
+
return writeDefect(cause)
|
|
290
338
|
}
|
|
291
339
|
}
|
|
292
340
|
|
|
341
|
+
const writeExit = (requestId: string | number, exit: unknown) =>
|
|
342
|
+
writeResponse(
|
|
343
|
+
{ _tag: 'Exit', requestId, exit },
|
|
344
|
+
Effect.sync(() => requestIdsWithSchemas.delete(requestId)).pipe(
|
|
345
|
+
Effect.andThen(
|
|
346
|
+
onRequestExit !== undefined
|
|
347
|
+
? runLifecycleHook('onRequestExit', () => onRequestExit(requestId, ws))
|
|
348
|
+
: Effect.void,
|
|
349
|
+
),
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
const write = (response: RpcMessage.FromServerEncoded) => {
|
|
354
|
+
if (response._tag === 'Exit') return writeExit(response.requestId, response.exit)
|
|
355
|
+
if (response._tag === 'Defect') return writeDefect(response.defect)
|
|
356
|
+
return writeResponse(response)
|
|
357
|
+
}
|
|
358
|
+
|
|
293
359
|
const protocol = yield* RpcServer.Protocol.make((writeRequest_) => {
|
|
294
360
|
writeRequest = writeRequest_
|
|
295
361
|
|
|
296
362
|
// Start processing messages now that writeRequest is available
|
|
297
|
-
const startProcessing =
|
|
363
|
+
const startProcessing = Stream.fromQueue(incomingQueue).pipe(
|
|
298
364
|
Stream.tap((data) => {
|
|
299
365
|
try {
|
|
300
366
|
const decoded = parser.decode(data) as ReadonlyArray<RpcMessage.FromClientEncoded>
|
|
@@ -304,20 +370,27 @@ const makeSocketProtocol = ({ incomingQueue, ws, onMessage }: WsRpcServerArgs) =
|
|
|
304
370
|
while: () => i < decoded.length,
|
|
305
371
|
body: () => {
|
|
306
372
|
const request = decoded[i++]!
|
|
307
|
-
if (onMessage !== undefined)
|
|
308
|
-
|
|
373
|
+
if (onMessage !== undefined) onMessage(request, ws)
|
|
374
|
+
|
|
375
|
+
if (request._tag === 'Request') {
|
|
376
|
+
requestIdsWithSchemas.add(request.id)
|
|
377
|
+
} else if (request._tag === 'Interrupt' && requestIdsWithSchemas.has(request.requestId) === false) {
|
|
378
|
+
// Effect creates this Exit for an unknown request, but its encoder drops it when hibernation has
|
|
379
|
+
// discarded the request schema.
|
|
380
|
+
return writeExit(request.requestId, interruptedExitEncoded)
|
|
309
381
|
}
|
|
382
|
+
|
|
310
383
|
return writeRequest(id, request)
|
|
311
384
|
},
|
|
312
|
-
step: constVoid,
|
|
385
|
+
step: Function.constVoid,
|
|
313
386
|
})
|
|
314
387
|
} catch (cause) {
|
|
315
|
-
return
|
|
388
|
+
return writeDefect(cause)
|
|
316
389
|
}
|
|
317
390
|
}),
|
|
318
391
|
Stream.runDrain,
|
|
319
392
|
Effect.tapCauseLogPretty,
|
|
320
|
-
Effect.
|
|
393
|
+
Effect.forkIn(scope),
|
|
321
394
|
)
|
|
322
395
|
|
|
323
396
|
// Start the message processing
|
|
@@ -333,6 +406,8 @@ const makeSocketProtocol = ({ incomingQueue, ws, onMessage }: WsRpcServerArgs) =
|
|
|
333
406
|
supportsAck: true,
|
|
334
407
|
supportsTransferables: false,
|
|
335
408
|
supportsSpanPropagation: true,
|
|
409
|
+
supportsNotifications: true,
|
|
410
|
+
codecFor: serialization.codecFor,
|
|
336
411
|
}))
|
|
337
412
|
})
|
|
338
413
|
|
|
@@ -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
|
Socket,
|
|
@@ -24,29 +22,33 @@ const testTimeout = 60_000
|
|
|
24
22
|
const withWranglerTest = Vitest.makeWithTestCtx({
|
|
25
23
|
timeout: testTimeout,
|
|
26
24
|
makeLayer: () =>
|
|
27
|
-
|
|
25
|
+
WranglerDevServer.layer({
|
|
28
26
|
cwd: `${import.meta.dirname}/test-fixtures`,
|
|
29
27
|
}).pipe(
|
|
30
28
|
Layer.provide(
|
|
31
|
-
Layer.mergeAll(
|
|
29
|
+
Layer.mergeAll(
|
|
30
|
+
PlatformNode.NodeServices.layer,
|
|
31
|
+
FetchHttpClient.layer,
|
|
32
|
+
Layer.succeed(References.MinimumLogLevel, 'Debug'),
|
|
33
|
+
),
|
|
32
34
|
),
|
|
33
35
|
),
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
const ProtocolLive = Layer.suspend(() =>
|
|
37
39
|
Effect.gen(function* () {
|
|
38
|
-
const server = yield*
|
|
40
|
+
const server = yield* WranglerDevServer.WranglerDevServer
|
|
39
41
|
return RpcClient.layerProtocolSocket().pipe(
|
|
40
42
|
Layer.provide(Socket.layerWebSocket(`ws://localhost:${server.port}`)),
|
|
41
43
|
Layer.provide(Socket.layerWebSocketConstructorGlobal),
|
|
42
44
|
Layer.provide(RpcSerialization.layerJson),
|
|
43
45
|
)
|
|
44
|
-
}).pipe(Layer.
|
|
46
|
+
}).pipe(Layer.unwrap),
|
|
45
47
|
)
|
|
46
48
|
|
|
47
49
|
Vitest.describe('Durable Object WebSocket RPC', { timeout: testTimeout }, () => {
|
|
48
50
|
// Direct HTTP RPC client tests
|
|
49
|
-
Vitest.
|
|
51
|
+
Vitest.live('should call ping method', (test) =>
|
|
50
52
|
Effect.gen(function* () {
|
|
51
53
|
const client = yield* RpcClient.make(TestRpcs)
|
|
52
54
|
const result = yield* client.Ping({ message: 'Hello HTTP RPC' })
|
|
@@ -54,7 +56,7 @@ Vitest.describe('Durable Object WebSocket RPC', { timeout: testTimeout }, () =>
|
|
|
54
56
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
55
57
|
)
|
|
56
58
|
|
|
57
|
-
Vitest.
|
|
59
|
+
Vitest.live('should call echo method', (test) =>
|
|
58
60
|
Effect.gen(function* () {
|
|
59
61
|
const client = yield* RpcClient.make(TestRpcs)
|
|
60
62
|
const result = yield* client.Echo({ text: 'Echo' })
|
|
@@ -62,7 +64,7 @@ Vitest.describe('Durable Object WebSocket RPC', { timeout: testTimeout }, () =>
|
|
|
62
64
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
63
65
|
)
|
|
64
66
|
|
|
65
|
-
Vitest.
|
|
67
|
+
Vitest.live('should call add method', (test) =>
|
|
66
68
|
Effect.gen(function* () {
|
|
67
69
|
const client = yield* RpcClient.make(TestRpcs)
|
|
68
70
|
const result = yield* client.Add({ a: 15, b: 25 })
|
|
@@ -70,104 +72,72 @@ Vitest.describe('Durable Object WebSocket RPC', { timeout: testTimeout }, () =>
|
|
|
70
72
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
71
73
|
)
|
|
72
74
|
|
|
73
|
-
Vitest.
|
|
75
|
+
Vitest.live('should handle RPC fail method', (test) =>
|
|
74
76
|
Effect.gen(function* () {
|
|
75
77
|
const client = yield* RpcClient.make(TestRpcs)
|
|
76
78
|
const error = yield* client.Fail({ message: 'test http failure' }).pipe(Effect.exit)
|
|
77
79
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
78
|
-
"
|
|
79
|
-
"_id": "Exit",
|
|
80
|
-
"_tag": "Failure",
|
|
81
|
-
"cause": {
|
|
82
|
-
"_id": "Cause",
|
|
83
|
-
"_tag": "Fail",
|
|
84
|
-
"failure": "RPC failure: test http failure"
|
|
85
|
-
}
|
|
86
|
-
}"
|
|
80
|
+
"Failure(Cause([Fail("RPC failure: test http failure")]))"
|
|
87
81
|
`)
|
|
88
82
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
89
83
|
)
|
|
90
84
|
|
|
91
|
-
Vitest.
|
|
85
|
+
Vitest.live('should handle defect method', (test) =>
|
|
92
86
|
Effect.gen(function* () {
|
|
93
87
|
const client = yield* RpcClient.make(TestRpcs)
|
|
94
88
|
const error = yield* client.Defect({ message: 'test http defect' }).pipe(Effect.exit)
|
|
95
89
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
96
|
-
"
|
|
97
|
-
"_id": "Exit",
|
|
98
|
-
"_tag": "Failure",
|
|
99
|
-
"cause": {
|
|
100
|
-
"_id": "Cause",
|
|
101
|
-
"_tag": "Die",
|
|
102
|
-
"defect": "some defect: test http defect"
|
|
103
|
-
}
|
|
104
|
-
}"
|
|
90
|
+
"Failure(Cause([Die("some defect: test http defect")]))"
|
|
105
91
|
`)
|
|
106
92
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
107
93
|
)
|
|
108
94
|
|
|
109
|
-
Vitest.
|
|
95
|
+
Vitest.live('should handle streaming RPC via HTTP', (test) =>
|
|
110
96
|
Effect.gen(function* () {
|
|
111
97
|
const client = yield* RpcClient.make(TestRpcs)
|
|
112
98
|
const stream = client.Stream({}).pipe(
|
|
113
99
|
Stream.take(4),
|
|
114
100
|
Stream.map((c) => c.maybeNumber.pipe(Option.getOrUndefined)),
|
|
115
101
|
)
|
|
116
|
-
const
|
|
117
|
-
expect(
|
|
102
|
+
const result = yield* Stream.runCollect(stream)
|
|
103
|
+
expect(result).toEqual([1, 4, 9, 16]) // squares of 1,2,3,4
|
|
118
104
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
119
105
|
)
|
|
120
106
|
|
|
121
|
-
Vitest.
|
|
107
|
+
Vitest.live('should handle streaming RPC with error via HTTP', (test) =>
|
|
122
108
|
Effect.gen(function* () {
|
|
123
109
|
const client = yield* RpcClient.make(TestRpcs)
|
|
124
110
|
const stream = client.StreamError({ count: 5, errorAfter: 4 })
|
|
125
111
|
const error = yield* Stream.runCollect(stream).pipe(Effect.exit)
|
|
126
112
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
127
|
-
"
|
|
128
|
-
"_id": "Exit",
|
|
129
|
-
"_tag": "Failure",
|
|
130
|
-
"cause": {
|
|
131
|
-
"_id": "Cause",
|
|
132
|
-
"_tag": "Fail",
|
|
133
|
-
"failure": "Stream error after 4: got 9"
|
|
134
|
-
}
|
|
135
|
-
}"
|
|
113
|
+
"Failure(Cause([Fail("Stream error after 4: got 9")]))"
|
|
136
114
|
`)
|
|
137
115
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
138
116
|
)
|
|
139
117
|
|
|
140
|
-
Vitest.
|
|
118
|
+
Vitest.live('should handle streaming RPC with defect via HTTP', (test) =>
|
|
141
119
|
Effect.gen(function* () {
|
|
142
120
|
const client = yield* RpcClient.make(TestRpcs)
|
|
143
121
|
const stream = client.StreamDefect({ count: 4, defectAfter: 1 })
|
|
144
122
|
const error = yield* Stream.runCollect(stream).pipe(Effect.exit)
|
|
145
123
|
expect(error.toString()).toMatchInlineSnapshot(`
|
|
146
|
-
"
|
|
147
|
-
"_id": "Exit",
|
|
148
|
-
"_tag": "Failure",
|
|
149
|
-
"cause": {
|
|
150
|
-
"_id": "Cause",
|
|
151
|
-
"_tag": "Die",
|
|
152
|
-
"defect": "Stream defect after 1: got 4"
|
|
153
|
-
}
|
|
154
|
-
}"
|
|
124
|
+
"Failure(Cause([Die("Stream defect after 1: got 4")]))"
|
|
155
125
|
`)
|
|
156
126
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
157
127
|
)
|
|
158
128
|
|
|
159
|
-
Vitest.
|
|
129
|
+
Vitest.live('should handle stream interruption via HTTP', (test) =>
|
|
160
130
|
Effect.gen(function* () {
|
|
161
131
|
const client = yield* RpcClient.make(TestRpcs)
|
|
162
132
|
const stream = client.StreamInterruptible({ delay: 50, interruptAfterCount: 3 }).pipe(Stream.take(3))
|
|
163
|
-
const
|
|
164
|
-
expect(
|
|
133
|
+
const result = yield* Stream.runCollect(stream)
|
|
134
|
+
expect(result).toEqual([1, 2, 3])
|
|
165
135
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
166
136
|
)
|
|
167
137
|
})
|
|
168
138
|
|
|
169
139
|
Vitest.describe('Hibernation Tests', { timeout: 25000 }, () => {
|
|
170
|
-
Vitest.
|
|
140
|
+
Vitest.live('should maintain RPC functionality after hibernation', (test) =>
|
|
171
141
|
Effect.gen(function* () {
|
|
172
142
|
console.log('๐งช Testing RPC server persistence across hibernation...')
|
|
173
143
|
|
|
@@ -222,15 +192,15 @@ Vitest.describe('Hibernation Tests', { timeout: 25000 }, () => {
|
|
|
222
192
|
Stream.take(3),
|
|
223
193
|
Stream.map((c) => c.maybeNumber.pipe(Option.getOrUndefined)),
|
|
224
194
|
)
|
|
225
|
-
const
|
|
226
|
-
expect(
|
|
195
|
+
const result = yield* Stream.runCollect(stream)
|
|
196
|
+
expect(result).toEqual([1, 4, 9]) // squares of 1,2,3
|
|
227
197
|
console.log('โ
Streaming after hibernation successful')
|
|
228
198
|
|
|
229
199
|
console.log('๐ All RPC operations successful after hibernation!')
|
|
230
200
|
}).pipe(Effect.provide(ProtocolLive), withWranglerTest(test)),
|
|
231
201
|
)
|
|
232
202
|
|
|
233
|
-
Vitest.
|
|
203
|
+
Vitest.live('should handle rapid operations after hibernation', (test) =>
|
|
234
204
|
Effect.gen(function* () {
|
|
235
205
|
console.log('๐งช Testing rapid operations after hibernation...')
|
|
236
206
|
|