@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.
Files changed (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/declare/cf-declare.d.ts +1 -21
  3. package/dist/declare/cf-declare.d.ts.map +1 -1
  4. package/dist/do-rpc/client.d.ts.map +1 -1
  5. package/dist/do-rpc/client.js +17 -27
  6. package/dist/do-rpc/client.js.map +1 -1
  7. package/dist/do-rpc/client.test.d.ts +2 -0
  8. package/dist/do-rpc/client.test.d.ts.map +1 -0
  9. package/dist/do-rpc/client.test.js +99 -0
  10. package/dist/do-rpc/client.test.js.map +1 -0
  11. package/dist/do-rpc/rpc.test.js +25 -57
  12. package/dist/do-rpc/rpc.test.js.map +1 -1
  13. package/dist/do-rpc/server.d.ts +9 -4
  14. package/dist/do-rpc/server.d.ts.map +1 -1
  15. package/dist/do-rpc/server.js +84 -43
  16. package/dist/do-rpc/server.js.map +1 -1
  17. package/dist/do-rpc/test-fixtures/rpc-schema.d.ts +27 -27
  18. package/dist/do-rpc/test-fixtures/rpc-schema.js +11 -11
  19. package/dist/do-rpc/test-fixtures/worker.d.ts.map +1 -1
  20. package/dist/do-rpc/test-fixtures/worker.js +9 -9
  21. package/dist/do-rpc/test-fixtures/worker.js.map +1 -1
  22. package/dist/ws-rpc/test-fixtures/rpc-schema.d.ts +27 -27
  23. package/dist/ws-rpc/test-fixtures/rpc-schema.js +9 -9
  24. package/dist/ws-rpc/ws-rpc-server.d.ts +6 -5
  25. package/dist/ws-rpc/ws-rpc-server.d.ts.map +1 -1
  26. package/dist/ws-rpc/ws-rpc-server.js +17 -17
  27. package/dist/ws-rpc/ws-rpc-server.js.map +1 -1
  28. package/dist/ws-rpc/ws-rpc.test.js +27 -59
  29. package/dist/ws-rpc/ws-rpc.test.js.map +1 -1
  30. package/package.json +11 -20
  31. package/src/declare/cf-declare.ts +1 -1
  32. package/src/do-rpc/client.test.ts +122 -0
  33. package/src/do-rpc/client.ts +21 -31
  34. package/src/do-rpc/rpc.test.ts +29 -59
  35. package/src/do-rpc/server.ts +119 -54
  36. package/src/do-rpc/test-fixtures/rpc-schema.ts +11 -11
  37. package/src/do-rpc/test-fixtures/worker.ts +9 -12
  38. package/src/ws-rpc/test-fixtures/rpc-schema.ts +9 -9
  39. package/src/ws-rpc/ws-rpc-server.ts +37 -28
  40. package/src/ws-rpc/ws-rpc.test.ts +31 -61
@@ -1,26 +1,43 @@
1
1
  import {
2
- Chunk,
3
2
  Effect,
4
3
  Exit,
5
4
  Headers,
6
5
  type Layer,
7
- type NonEmptyArray,
8
6
  Option,
7
+ type ReadonlyArray,
9
8
  Rpc,
10
9
  type RpcGroup,
11
- type RpcMessage,
10
+ RpcMessage,
12
11
  RpcSchema,
13
12
  RpcSerialization,
13
+ Result,
14
14
  Schema,
15
+ SchemaIssue,
15
16
  type Scope,
16
17
  Stream,
17
18
  } from '@livestore/utils/effect'
18
19
 
19
20
  import type * as CfTypes from '../cf-types.ts'
20
21
 
22
+ /**
23
+ * The erased dynamic RPC schemas (`Rpc.exitSchema(...) as Schema.Top`, `rpc.payloadSchema`) are
24
+ * plain data — they need NO decoding/encoding services. `Schema.Top` types those service channels
25
+ * as `unknown`, which surfaces as `unknown` in the requirements channel of `encode/decodeUnknownEffect`
26
+ * (anyUnknownInErrorContext / TS377030). Assert the true `never` services in ONE place.
27
+ *
28
+ * TODO(effect): revisit if a cleaner/idiomatic API lands — https://github.com/Effect-TS/effect/issues/6489
29
+ */
30
+ const erasedJsonCodec = (schema: Schema.Top) =>
31
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- erased RPC data schemas require no codec services
32
+ Schema.toCodecJson(schema as Schema.Codec<unknown, unknown, never, never>)
33
+
21
34
  export interface ClientDoWithRpcCallback {
22
35
  __DURABLE_OBJECT_BRAND: never
23
- syncUpdateRpc: (payload: RpcMessage.ResponseChunkEncoded) => Promise<void>
36
+ /**
37
+ * The sync backend calls this to deliver a live update; `storeId` lets a rebuilt DO reload its
38
+ * store before delivering. See the Cloudflare Durable Object adapter docs for the recovery options.
39
+ */
40
+ syncUpdateRpc: (payload: Uint8Array<ArrayBuffer>, storeId: string) => Promise<void>
24
41
  }
25
42
 
26
43
  /**
@@ -41,13 +58,13 @@ export const toDurableObjectHandler =
41
58
  ) => Effect.Effect<Uint8Array<ArrayBuffer> | CfTypes.ReadableStream>) =>
42
59
  (serializedPayload) =>
43
60
  Effect.gen(function* () {
44
- const parser = RpcSerialization.msgPack.unsafeMake()
61
+ const parser = RpcSerialization.msgPack.makeUnsafe()
45
62
 
46
63
  // Decode incoming requests - client sends array of requests
47
64
  const decoded = parser.decode(serializedPayload)
48
65
 
49
66
  // Handle potential nested array from client serialization
50
- let requests: RpcMessage.FromClient<Rpcs>[]
67
+ let requests: RpcMessage.FromClientEncoded[]
51
68
  if (Array.isArray(decoded) === true && decoded.length === 1 && Array.isArray(decoded[0]) === true) {
52
69
  // Double-wrapped array [[{...}]] -> [{...}]
53
70
  requests = decoded[0]
@@ -68,57 +85,83 @@ export const toDurableObjectHandler =
68
85
  if (request._tag !== 'Request') {
69
86
  continue
70
87
  }
88
+ const requestId = RpcMessage.RequestId(request.id)
71
89
 
72
90
  // Find the RPC handler
73
91
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- RpcGroup.requests map returns Rpc.Any; narrowing to AnyWithProps for property access
74
92
  const rpc = group.requests.get(request.tag)! as unknown as Rpc.AnyWithProps
75
- // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- context.unsafeMap dynamic lookup; type safety ensured by RpcGroup registration
76
- const entry = context.unsafeMap.get(rpc.key) as Rpc.Handler<Rpcs['_tag']>
93
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- context.mapUnsafe dynamic lookup; type safety ensured by RpcGroup registration
94
+ const entry = context.mapUnsafe.get(rpc.key) as Rpc.Handler<Rpcs['_tag']>
77
95
 
78
96
  if (rpc == null || entry == null) {
79
97
  responses.push({
80
98
  _tag: 'Exit',
81
- requestId: request.id,
99
+ requestId,
82
100
  exit: Exit.die(`Unknown request tag: ${request.tag}`),
83
101
  })
84
102
  continue
85
103
  }
86
104
 
105
+ const payloadResult = yield* Schema.decodeUnknownEffect(erasedJsonCodec(rpc.payloadSchema))(
106
+ request.payload,
107
+ ).pipe(Effect.provideContext(entry.context), Effect.result)
108
+
109
+ if (Result.isFailure(payloadResult) === true) {
110
+ // Request payloads are encoded with the JSON codec by Effect's RPC client. Decode them
111
+ // before dispatch so JSON-only representations such as `null` become their schema values.
112
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.exitSchema requires AnyWithProps; type narrowing already done above
113
+ const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Top
114
+ const rawExit = Exit.die(SchemaIssue.makeFormatterDefault()(payloadResult.failure.issue))
115
+ const encodedExit = yield* Schema.encodeUnknownEffect(erasedJsonCodec(exitSchema))(rawExit).pipe(
116
+ Effect.provideContext(entry.context),
117
+ )
118
+ responses.push({
119
+ _tag: 'Exit',
120
+ requestId,
121
+ exit: encodedExit,
122
+ })
123
+ continue
124
+ }
125
+
126
+ const payload = payloadResult.success
127
+
87
128
  // Check if this is a streaming RPC
88
- // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.Handler doesn't expose successSchema publicly; see https://github.com/Effect-TS/effect/issues/6064
89
- const isStream = RpcSchema.isStreamSchema((rpc as any).successSchema)
129
+ const isStream = RpcSchema.isStreamSchema(rpc.successSchema)
90
130
 
91
131
  // For streaming RPCs with only one request, return ReadableStream directly
92
132
  if (isStream === true && requests.length === 1) {
93
- return yield* createStreamingResponse(rpc, entry, request, parser, options.layer)
133
+ return yield* createStreamingResponse(rpc, entry, requestId, payload, parser, options.layer)
94
134
  }
95
135
 
96
136
  // Execute the handler
97
137
  const result = yield* Effect.gen(function* () {
98
- const handlerResult = entry.handler(request.payload, {
99
- clientId: 0, // TODO: add proper clientId if needed
138
+ const handlerResult = entry.handler(payload, {
139
+ client: new Rpc.ServerClient(0), // TODO: add proper clientId if needed
140
+ requestId,
100
141
  headers: Headers.fromInput({
101
- 'x-rpc-request-id': request.id.toString(),
142
+ 'x-rpc-request-id': requestId.toString(),
102
143
  }),
144
+ rpc,
103
145
  })
146
+ const effectOrStream = Rpc.isWrapper(handlerResult) === true ? handlerResult.value : handlerResult
104
147
 
105
148
  let value: any
106
- if (Effect.isEffect(handlerResult) === true) {
149
+ if (Effect.isEffect(effectOrStream) === true) {
107
150
  // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `Rpc.Handler.handler` returns `Effect<any, any>` due to dynamic dispatch
108
- value = yield* handlerResult
151
+ value = yield* effectOrStream
109
152
  } else {
110
- value = handlerResult
153
+ value = effectOrStream
111
154
  }
112
155
 
113
156
  // Get the exit schema for this RPC
114
157
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.exitSchema requires AnyWithProps; type narrowing already done above
115
- const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Schema<any>
158
+ const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Top
116
159
 
117
160
  let encodedExit: any
118
161
  if (exitSchema !== undefined) {
119
162
  // Use schema encoding for proper serialization
120
163
  const rawExit = Exit.succeed(value)
121
- encodedExit = yield* Schema.encodeUnknown(exitSchema)(rawExit)
164
+ encodedExit = yield* Schema.encodeUnknownEffect(erasedJsonCodec(exitSchema))(rawExit)
122
165
  } else {
123
166
  // Fallback to direct exit
124
167
  encodedExit = Exit.succeed(value)
@@ -126,21 +169,21 @@ export const toDurableObjectHandler =
126
169
 
127
170
  return {
128
171
  _tag: 'Exit' as const,
129
- requestId: request.id,
172
+ requestId,
130
173
  exit: encodedExit,
131
174
  }
132
175
  }).pipe(
133
- Effect.catchAllCause((cause) => {
176
+ Effect.catchCause((cause) => {
134
177
  // Get the exit schema for this RPC
135
178
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.exitSchema requires AnyWithProps; type narrowing already done above
136
- const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Schema<any>
179
+ const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Top
137
180
 
138
181
  return Effect.gen(function* () {
139
182
  let encodedExit: any
140
183
  if (exitSchema !== undefined) {
141
184
  // Use schema encoding for proper serialization
142
185
  const rawExit = Exit.failCause(cause)
143
- encodedExit = yield* Schema.encodeUnknown(exitSchema)(rawExit)
186
+ encodedExit = yield* Schema.encodeUnknownEffect(erasedJsonCodec(exitSchema))(rawExit)
144
187
  } else {
145
188
  // Fallback to direct exit
146
189
  encodedExit = Exit.failCause(cause)
@@ -148,7 +191,7 @@ export const toDurableObjectHandler =
148
191
 
149
192
  return {
150
193
  _tag: 'Exit' as const,
151
- requestId: request.id,
194
+ requestId,
152
195
  exit: encodedExit,
153
196
  }
154
197
  })
@@ -161,19 +204,23 @@ export const toDurableObjectHandler =
161
204
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- msgPack parser.encode returns unknown; cast to expected wire format
162
205
  const encoded = parser.encode(responses) as Uint8Array<ArrayBuffer>
163
206
  return encoded
164
- }).pipe(Effect.provide(options.layer), Effect.scoped, Effect.orDie)
207
+ }).pipe(Effect.provide(options.layer), Effect.scoped, Effect.orDie) as Effect.Effect<
208
+ Uint8Array<ArrayBuffer> | CfTypes.ReadableStream
209
+ >
165
210
 
166
211
  /** Out-of-band RPC stream response emission back to the caller DO */
167
212
  export const emitStreamResponse = Effect.fn('do-rpc/emitStreamResponse')(function* ({
168
213
  callerContext,
169
214
  env,
170
215
  requestId,
216
+ storeId,
171
217
  values,
172
218
  }: {
173
219
  env: Record<string, any>
174
220
  callerContext: { bindingName: string; durableObjectId: string }
175
221
  requestId: string
176
- values: NonEmptyArray<any>
222
+ storeId: string
223
+ values: ReadonlyArray.NonEmptyReadonlyArray<any>
177
224
  }) {
178
225
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- CF worker env bindings are typed as Record<string, any>; narrowing to known DO namespace
179
226
  const clientDoNamespace = env[callerContext.bindingName] as
@@ -187,8 +234,13 @@ export const emitStreamResponse = Effect.fn('do-rpc/emitStreamResponse')(functio
187
234
  const clientDo = clientDoNamespace.get(clientDoNamespace.idFromString(callerContext.durableObjectId))
188
235
 
189
236
  const res: RpcMessage.ResponseChunkEncoded = { _tag: 'Chunk', requestId, values }
237
+ const parser = RpcSerialization.msgPack.makeUnsafe()
238
+ // Native Cloudflare RPC rejects schema values with custom prototypes. Keep the callback
239
+ // boundary clone-safe by sending the already-encoded Effect RPC message as bytes.
240
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- msgPack parser.encode returns unknown; the encoded result is a byte payload
241
+ const serializedRes = parser.encode(res) as Uint8Array<ArrayBuffer>
190
242
 
191
- yield* Effect.tryPromise(() => clientDo.syncUpdateRpc(res))
243
+ yield* Effect.tryPromise(() => clientDo.syncUpdateRpc(serializedRes, storeId))
192
244
  })
193
245
 
194
246
  /**
@@ -198,32 +250,40 @@ export const emitStreamResponse = Effect.fn('do-rpc/emitStreamResponse')(functio
198
250
  const createStreamingResponse = <Rpcs extends Rpc.Any, LE>(
199
251
  rpc: Rpc.AnyWithProps,
200
252
  entry: Rpc.Handler<Rpcs['_tag']>,
201
- request: any,
202
- parser: ReturnType<typeof RpcSerialization.msgPack.unsafeMake>,
253
+ requestId: RpcMessage.RequestId,
254
+ payload: unknown,
255
+ parser: ReturnType<typeof RpcSerialization.msgPack.makeUnsafe>,
203
256
  layer: Layer.Layer<Rpc.ToHandler<Rpcs> | Rpc.Middleware<Rpcs>, LE>,
204
257
  ): Effect.Effect<CfTypes.ReadableStream, never, Scope.Scope> =>
205
258
  Effect.gen(function* () {
206
259
  // Execute the handler to get the stream
207
- const handlerResult = entry.handler(request.payload, {
208
- clientId: 0, // TODO: add proper clientId if needed
260
+ const handlerResult = entry.handler(payload, {
261
+ client: new Rpc.ServerClient(0), // TODO: add proper clientId if needed
262
+ requestId,
209
263
  headers: Headers.fromInput({
210
- 'x-rpc-request-id': request.id.toString(),
264
+ 'x-rpc-request-id': requestId.toString(),
211
265
  }),
266
+ rpc,
212
267
  })
268
+ const effectOrStream = Rpc.isWrapper(handlerResult) === true ? handlerResult.value : handlerResult
213
269
 
214
- // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `Rpc.Handler.handler` returns `Effect<any, any>` due to dynamic dispatch; orDie converts the error to a defect handled by the downstream catchAllCause
215
270
  const stream: Stream.Stream<any, any> =
216
- Effect.isEffect(handlerResult) === true ? yield* Effect.orDie(handlerResult) : handlerResult
271
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `Rpc.Handler.handler` returns `Effect<any, any>` due to dynamic dispatch; orDie converts the error to a defect handled by the downstream catchCause
272
+ Effect.isEffect(effectOrStream) === true ? yield* Effect.orDie(effectOrStream) : effectOrStream
217
273
 
218
274
  // Get the stream schemas for proper chunk-level encoding
219
275
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.Handler doesn't expose successSchema publicly; see https://github.com/Effect-TS/effect/issues/6064
220
- const streamSchemas = RpcSchema.getStreamSchemas((rpc as any).successSchema.ast)
221
- const chunkEncoder =
276
+ const streamSchemas =
277
+ RpcSchema.isStreamSchema(rpc.successSchema) === true
278
+ ? Option.some({
279
+ success: rpc.successSchema.success,
280
+ error: rpc.successSchema.error,
281
+ })
282
+ : Option.none()
283
+ const arrayEncoder =
222
284
  Option.isSome(streamSchemas) === true
223
- ? // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- stream schema success type is inferred as unknown; cast needed for encodeUnknown
224
- Schema.encodeUnknown(Schema.Array(streamSchemas.value.success as Schema.Schema<any>))
225
- : // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Schema.Any needs explicit cast for Schema.Array compatibility
226
- Schema.encodeUnknown(Schema.Array(Schema.Any as Schema.Schema<any>))
285
+ ? Schema.encodeUnknownEffect(Schema.toCodecJson(Schema.Array(streamSchemas.value.success)))
286
+ : Schema.encodeUnknownEffect(Schema.toCodecJson(Schema.Array(Schema.Any)))
227
287
 
228
288
  // Convert stream to ReadableStream
229
289
  const readableStream = new ReadableStream({
@@ -231,17 +291,16 @@ const createStreamingResponse = <Rpcs extends Rpc.Any, LE>(
231
291
  // Run the stream and send chunks + final exit
232
292
  const runStream = Effect.gen(function* () {
233
293
  // Process stream chunks - let chunk encoder handle Effect objects properly
234
- yield* Stream.runForEachChunk(stream, (chunk) =>
294
+ yield* Stream.runForEachArray(stream, (array) =>
235
295
  Effect.gen(function* () {
236
- const chunkArray = Chunk.toReadonlyArray(chunk)
237
- if (chunkArray.length === 0) return
296
+ if (array.length === 0) return
238
297
 
239
298
  // Encode the chunk using the proper chunk encoder (like official RPC)
240
- const encodedValues = yield* chunkEncoder(chunkArray)
299
+ const encodedValues = yield* arrayEncoder(array)
241
300
 
242
301
  const chunkMessage = {
243
302
  _tag: 'Chunk' as const,
244
- requestId: request.id,
303
+ requestId,
245
304
  values: encodedValues,
246
305
  }
247
306
 
@@ -254,12 +313,12 @@ const createStreamingResponse = <Rpcs extends Rpc.Any, LE>(
254
313
  // Send final exit message with proper schema encoding
255
314
  const rawExit = Exit.void
256
315
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.exitSchema requires AnyWithProps; type narrowing already done above
257
- const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Schema<any>
258
- const encodedExit = yield* Schema.encodeUnknown(exitSchema)(rawExit)
316
+ const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Top
317
+ const encodedExit = yield* Schema.encodeUnknownEffect(erasedJsonCodec(exitSchema))(rawExit)
259
318
 
260
319
  const exitMessage = {
261
320
  _tag: 'Exit' as const,
262
- requestId: request.id,
321
+ requestId,
263
322
  exit: encodedExit,
264
323
  }
265
324
 
@@ -268,17 +327,17 @@ const createStreamingResponse = <Rpcs extends Rpc.Any, LE>(
268
327
  controller.enqueue(exitSerialized)
269
328
  controller.close()
270
329
  }).pipe(
271
- Effect.catchAllCause((cause) =>
330
+ Effect.catchCause((cause) =>
272
331
  Effect.gen(function* () {
273
332
  // Send error exit with proper schema encoding
274
333
  const rawExit = Exit.failCause(cause)
275
334
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Rpc.exitSchema requires AnyWithProps; type narrowing already done above
276
- const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Schema<any>
277
- const encodedExit = yield* Schema.encodeUnknown(exitSchema)(rawExit)
335
+ const exitSchema = Rpc.exitSchema(rpc as any) as Schema.Top
336
+ const encodedExit = yield* Schema.encodeUnknownEffect(erasedJsonCodec(exitSchema))(rawExit)
278
337
 
279
338
  const exitMessage = {
280
339
  _tag: 'Exit' as const,
281
- requestId: request.id,
340
+ requestId,
282
341
  exit: encodedExit,
283
342
  }
284
343
 
@@ -291,7 +350,13 @@ const createStreamingResponse = <Rpcs extends Rpc.Any, LE>(
291
350
  )
292
351
 
293
352
  // Run the stream processing
294
- runStream.pipe(Effect.provide(layer), Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
353
+ runStream.pipe(
354
+ Effect.provide(layer),
355
+ Effect.scoped,
356
+ Effect.tapCauseLogPretty,
357
+ (_) => _ as Effect.Effect<void>,
358
+ Effect.runPromise,
359
+ )
295
360
  },
296
361
  // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- bridging standard Web API ReadableStream to Cloudflare Worker ReadableStream type
297
362
  }) as any as CfTypes.ReadableStream
@@ -10,8 +10,8 @@ export class TestRpcs extends RpcGroup.make(
10
10
  success: Schema.Struct({ echo: Schema.String }),
11
11
  }),
12
12
  Rpc.make('Add', {
13
- payload: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
14
- success: Schema.Struct({ result: Schema.Number }),
13
+ payload: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }),
14
+ success: Schema.Struct({ result: Schema.Finite }),
15
15
  }),
16
16
  Rpc.make('Defect', {
17
17
  payload: Schema.Struct({ message: Schema.String }),
@@ -25,34 +25,34 @@ export class TestRpcs extends RpcGroup.make(
25
25
  Rpc.make('Stream', {
26
26
  payload: Schema.Struct({}),
27
27
  success: Schema.Struct({
28
- maybeNumber: Schema.Option(Schema.Number),
28
+ maybeNumber: Schema.Option(Schema.Finite),
29
29
  }),
30
30
  stream: true,
31
31
  }),
32
32
  Rpc.make('StreamError', {
33
- payload: Schema.Struct({ count: Schema.Number, errorAfter: Schema.Number }),
34
- success: Schema.Number,
33
+ payload: Schema.Struct({ count: Schema.Finite, errorAfter: Schema.Finite }),
34
+ success: Schema.Finite,
35
35
  error: Schema.String,
36
36
  stream: true,
37
37
  }),
38
38
  Rpc.make('StreamDefect', {
39
- payload: Schema.Struct({ count: Schema.Number, defectAfter: Schema.Number }),
40
- success: Schema.Number,
39
+ payload: Schema.Struct({ count: Schema.Finite, defectAfter: Schema.Finite }),
40
+ success: Schema.Finite,
41
41
  stream: true,
42
42
  }),
43
43
  Rpc.make('StreamInterruptible', {
44
- payload: Schema.Struct({ delay: Schema.Number, interruptAfterCount: Schema.Number }),
45
- success: Schema.Number,
44
+ payload: Schema.Struct({ delay: Schema.Finite, interruptAfterCount: Schema.Finite }),
45
+ success: Schema.Finite,
46
46
  stream: true,
47
47
  }),
48
48
  Rpc.make('StreamBugScenarioDoServer', {
49
49
  payload: Schema.Struct({}),
50
- success: Schema.Number,
50
+ success: Schema.Finite,
51
51
  stream: true,
52
52
  }),
53
53
  Rpc.make('StreamBugScenarioDoClient', {
54
54
  payload: Schema.Struct({}),
55
- success: Schema.Number,
55
+ success: Schema.Finite,
56
56
  }),
57
57
  ) {}
58
58
  export type TestRpcsI = RpcGroup.Rpcs<typeof TestRpcs>
@@ -5,7 +5,7 @@ import { DurableObject } from 'cloudflare:workers'
5
5
  import { layerProtocolDurableObject, toDurableObjectHandler } from '@livestore/common-cf'
6
6
  import {
7
7
  Effect,
8
- HttpApp,
8
+ HttpEffect,
9
9
  Layer,
10
10
  Option,
11
11
  RpcClient,
@@ -76,7 +76,7 @@ export default {
76
76
  const url = new URL(request.url)
77
77
 
78
78
  // Handle HTTP RPC endpoint
79
- if (url.pathname === '/rpc') {
79
+ if (url.pathname === '/rpc/') {
80
80
  // Get the test server DO instance
81
81
  const doId = env.TEST_RPC_DO.idFromName('test-server')
82
82
  const serverDO = env.TEST_RPC_DO.get(doId)
@@ -106,26 +106,23 @@ export default {
106
106
  doRpcClient.StreamBugScenarioDoServer(msg).pipe(
107
107
  Stream.tap(() => Effect.fail('doh')),
108
108
  // observed behaviour: `log1` is still logged
109
- Stream.tapErrorCause((cause) => Effect.log('log1', cause)),
109
+ Stream.tapCause((cause) => Effect.log('log1', cause)),
110
110
  Stream.mapError((cause) => cause.toString()),
111
111
  // observed behaviour: after this error mapping `log2` is never logged
112
- Stream.tapErrorCause((cause) => Effect.log('log2', cause)),
112
+ Stream.tapCause((cause) => Effect.log('log2', cause)),
113
113
  Stream.tapLogWithLabel('stream'),
114
114
  Stream.runCount,
115
115
  Effect.orDie,
116
116
  // observed behaviour: `log3` is also never logged
117
- Effect.tapErrorCause((cause) => Effect.log('log3', cause)),
117
+ Effect.tapCause((cause) => Effect.log('log3', cause)),
118
118
  ),
119
- }).pipe(
120
- Layer.provideMerge(RpcServer.layerProtocolHttp({ path: '/rpc' })),
121
- Layer.provideMerge(RpcSerialization.layerJson),
122
- )
119
+ }).pipe(Layer.provideMerge(RpcSerialization.layerJson))
123
120
 
124
- // Create the HTTP RPC app
125
- const httpApp = RpcServer.toHttpApp(TestRpcs).pipe(Effect.provide(handlersLayer))
121
+ // Create the HTTP RPC effect
122
+ const httpEffect = yield* RpcServer.toHttpEffect(TestRpcs).pipe(Effect.provide(handlersLayer))
126
123
 
127
124
  // Run the app and convert to web handler
128
- const webHandler = yield* httpApp.pipe(Effect.map(HttpApp.toWebHandler))
125
+ const webHandler = HttpEffect.toWebHandler(httpEffect)
129
126
 
130
127
  return yield* Effect.promise(() => webHandler(request))
131
128
  }).pipe(
@@ -10,8 +10,8 @@ export class TestRpcs extends RpcGroup.make(
10
10
  success: Schema.Struct({ echo: Schema.String }),
11
11
  }),
12
12
  Rpc.make('Add', {
13
- payload: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
14
- success: Schema.Struct({ result: Schema.Number }),
13
+ payload: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }),
14
+ success: Schema.Struct({ result: Schema.Finite }),
15
15
  }),
16
16
  Rpc.make('Defect', {
17
17
  payload: Schema.Struct({ message: Schema.String }),
@@ -25,24 +25,24 @@ export class TestRpcs extends RpcGroup.make(
25
25
  Rpc.make('Stream', {
26
26
  payload: Schema.Struct({}),
27
27
  success: Schema.Struct({
28
- maybeNumber: Schema.Option(Schema.Number),
28
+ maybeNumber: Schema.Option(Schema.Finite),
29
29
  }),
30
30
  stream: true,
31
31
  }),
32
32
  Rpc.make('StreamError', {
33
- payload: Schema.Struct({ count: Schema.Number, errorAfter: Schema.Number }),
34
- success: Schema.Number,
33
+ payload: Schema.Struct({ count: Schema.Finite, errorAfter: Schema.Finite }),
34
+ success: Schema.Finite,
35
35
  error: Schema.String,
36
36
  stream: true,
37
37
  }),
38
38
  Rpc.make('StreamDefect', {
39
- payload: Schema.Struct({ count: Schema.Number, defectAfter: Schema.Number }),
40
- success: Schema.Number,
39
+ payload: Schema.Struct({ count: Schema.Finite, defectAfter: Schema.Finite }),
40
+ success: Schema.Finite,
41
41
  stream: true,
42
42
  }),
43
43
  Rpc.make('StreamInterruptible', {
44
- payload: Schema.Struct({ delay: Schema.Number, interruptAfterCount: Schema.Number }),
45
- success: Schema.Number,
44
+ payload: Schema.Struct({ delay: Schema.Finite, interruptAfterCount: Schema.Finite }),
45
+ success: Schema.Finite,
46
46
  stream: true,
47
47
  }),
48
48
  ) {}