liminal 0.17.13 → 0.17.15

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 (94) hide show
  1. package/Actor.ts +12 -13
  2. package/ActorTransport.ts +6 -4
  3. package/Audition.ts +87 -40
  4. package/CHANGELOG.md +16 -0
  5. package/Client.ts +260 -134
  6. package/ClientDirectory.ts +50 -36
  7. package/ClientHandleEncoders.ts +15 -0
  8. package/Fn.ts +55 -0
  9. package/Method.ts +11 -21
  10. package/Protocol.ts +44 -36
  11. package/Reducer.ts +22 -0
  12. package/Tracing.ts +37 -0
  13. package/browser/BrowserActorNamespace.ts +65 -30
  14. package/dist/Actor.d.ts +1 -1
  15. package/dist/Actor.js +6 -6
  16. package/dist/Actor.js.map +1 -1
  17. package/dist/ActorTransport.d.ts +5 -4
  18. package/dist/Audition.d.ts +16 -9
  19. package/dist/Audition.js +25 -9
  20. package/dist/Audition.js.map +1 -1
  21. package/dist/Client.d.ts +21 -14
  22. package/dist/Client.js +147 -100
  23. package/dist/Client.js.map +1 -1
  24. package/dist/ClientDirectory.d.ts +14 -6
  25. package/dist/ClientDirectory.js +25 -22
  26. package/dist/ClientDirectory.js.map +1 -1
  27. package/dist/ClientHandleEncoders.d.ts +7 -0
  28. package/dist/ClientHandleEncoders.js +2 -0
  29. package/dist/ClientHandleEncoders.js.map +1 -0
  30. package/dist/Fn.d.ts +16 -0
  31. package/dist/Fn.js +2 -0
  32. package/dist/Fn.js.map +1 -0
  33. package/dist/Method.d.ts +9 -14
  34. package/dist/Method.js +0 -1
  35. package/dist/Method.js.map +1 -1
  36. package/dist/Protocol.d.ts +19 -22
  37. package/dist/Protocol.js +20 -15
  38. package/dist/Protocol.js.map +1 -1
  39. package/dist/Reducer.d.ts +11 -0
  40. package/dist/Reducer.js +2 -0
  41. package/dist/Reducer.js.map +1 -0
  42. package/dist/Tracing.d.ts +37 -0
  43. package/dist/Tracing.js +29 -0
  44. package/dist/Tracing.js.map +1 -0
  45. package/dist/browser/BrowserActorNamespace.d.ts +5 -5
  46. package/dist/browser/BrowserActorNamespace.js +41 -20
  47. package/dist/browser/BrowserActorNamespace.js.map +1 -1
  48. package/dist/errors.d.ts +0 -4
  49. package/dist/errors.js.map +1 -1
  50. package/dist/experimental/TaggedTemplateFunction.js +1 -1
  51. package/dist/experimental/TaggedTemplateFunction.js.map +1 -1
  52. package/dist/index.d.ts +3 -3
  53. package/dist/index.js +3 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/package.json +16 -21
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/dist/workerd/ActorHandle.d.ts +9 -0
  58. package/dist/workerd/ActorHandle.js +4 -0
  59. package/dist/workerd/ActorHandle.js.map +1 -0
  60. package/dist/workerd/WorkerdActorNamespace.d.ts +18 -18
  61. package/dist/workerd/WorkerdActorNamespace.js +43 -141
  62. package/dist/workerd/WorkerdActorNamespace.js.map +1 -1
  63. package/dist/workerd/WorkerdActorRuntime.d.ts +19 -0
  64. package/dist/workerd/WorkerdActorRuntime.js +204 -0
  65. package/dist/workerd/WorkerdActorRuntime.js.map +1 -0
  66. package/dist/workerd/index.d.ts +2 -0
  67. package/dist/workerd/index.js +2 -0
  68. package/dist/workerd/index.js.map +1 -1
  69. package/errors.ts +0 -6
  70. package/experimental/TaggedTemplateFunction.ts +1 -1
  71. package/index.ts +3 -3
  72. package/package.json +10 -25
  73. package/tsconfig.json +1 -1
  74. package/vitest.config.ts +7 -0
  75. package/workerd/ActorHandle.ts +29 -0
  76. package/workerd/WorkerdActorNamespace.ts +86 -273
  77. package/workerd/WorkerdActorRuntime.ts +422 -0
  78. package/workerd/index.ts +2 -0
  79. package/Accumulator.ts +0 -103
  80. package/F.ts +0 -10
  81. package/_diagnostic.ts +0 -3
  82. package/_util/Mutex.ts +0 -13
  83. package/dist/Accumulator.d.ts +0 -22
  84. package/dist/Accumulator.js +0 -37
  85. package/dist/Accumulator.js.map +0 -1
  86. package/dist/F.d.ts +0 -4
  87. package/dist/F.js +0 -2
  88. package/dist/F.js.map +0 -1
  89. package/dist/_diagnostic.d.ts +0 -4
  90. package/dist/_diagnostic.js +0 -3
  91. package/dist/_diagnostic.js.map +0 -1
  92. package/dist/_util/Mutex.d.ts +0 -7
  93. package/dist/_util/Mutex.js +0 -9
  94. package/dist/_util/Mutex.js.map +0 -1
package/Client.ts CHANGED
@@ -15,23 +15,27 @@ import {
15
15
  Take,
16
16
  Schema as S,
17
17
  Array,
18
- Struct,
19
18
  Fiber,
20
19
  Exit,
21
20
  Cause,
22
21
  Result,
23
22
  flow,
23
+ Tracer,
24
+ identity,
25
+ Semaphore,
24
26
  } from "effect"
25
27
  import { Socket } from "effect/unstable/socket"
26
28
  import { Worker } from "effect/unstable/workers"
29
+ import * as Spanner from "liminal-util/Spanner"
27
30
 
28
- import { diagnostic } from "./_diagnostic.ts"
29
31
  import { decodeJsonString, encodeJsonString } from "./_util/schema.ts"
30
- import { type ClientError, AuditionError, ConnectionError, type FError, UnresolvedError } from "./errors.ts"
31
- import { type F } from "./F.ts"
32
+ import { type ClientError, AuditionError, ConnectionError, UnresolvedError } from "./errors.ts"
33
+ import type { Fn, FnError } from "./Fn.ts"
32
34
  import { Protocol, type ProtocolDefinition } from "./Protocol.ts"
35
+ import * as Reducer from "./Reducer.ts"
36
+ import * as Tracing from "./Tracing.ts"
33
37
 
34
- const { debug, span } = diagnostic("Client")
38
+ const span = Spanner.make(import.meta.url)
35
39
 
36
40
  export const TypeId = "~liminal/Client" as const
37
41
 
@@ -47,15 +51,21 @@ interface EventTake<A, E> {
47
51
  readonly take: Take.Take<A, E>
48
52
  }
49
53
 
50
- export interface Session<Self, D extends ProtocolDefinition> {
51
- readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>
54
+ export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<
55
+ {
56
+ readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError>
52
57
 
53
- readonly f: F<Self, D>
58
+ readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>
54
59
 
55
- readonly end: Effect.Effect<void>
56
- }
60
+ readonly fnRaw: <K extends keyof D["external"], M extends D["external"][K]>(
61
+ tag: K,
62
+ payload: M["payload"]["Type"],
63
+ ) => Effect.Effect<M["success"]["Type"], M["failure"]["Type"], ClientSelf>
57
64
 
58
- export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<Session<ClientSelf, D>, ClientError>
65
+ readonly end: Effect.Effect<void>
66
+ },
67
+ ClientError
68
+ >
59
69
 
60
70
  export interface Client<Self, ClientId extends string, D extends ProtocolDefinition> extends Context.Service<
61
71
  Self,
@@ -69,15 +79,23 @@ export interface Client<Self, ClientId extends string, D extends ProtocolDefinit
69
79
 
70
80
  readonly protocol: Protocol<D>
71
81
 
82
+ readonly state: Stream.Stream<
83
+ S.Struct<D["state"]>["Type"],
84
+ ClientError | S.SchemaError,
85
+ Self | S.Struct<D["state"]>["DecodingServices"]
86
+ >
87
+
72
88
  readonly events: Stream.Stream<
73
89
  ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"],
74
90
  ClientError | S.SchemaError,
75
91
  Self
76
92
  >
77
93
 
78
- readonly f: F<Self, D>
94
+ readonly fn: Fn<Self, D["external"]>
79
95
 
80
96
  readonly invalidate: Effect.Effect<void, never, Self>
97
+
98
+ readonly reducer: <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_tag: K, f: R) => R
81
99
  }
82
100
 
83
101
  export const Service =
@@ -87,13 +105,27 @@ export const Service =
87
105
 
88
106
  const protocol = Protocol(definition)
89
107
 
90
- const events = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(Struct.get("events")), Stream.unwrap)
108
+ const state = tag.asEffect().pipe(
109
+ Effect.flatMap(RcRef.get),
110
+ Effect.map(({ state }) => state),
111
+ Stream.unwrap,
112
+ )
113
+
114
+ const events = tag.asEffect().pipe(
115
+ Effect.flatMap(RcRef.get),
116
+ Effect.map(({ events }) => events),
117
+ Stream.unwrap,
118
+ )
91
119
 
92
- const f: F<Self, D> = (_tag) =>
93
- Effect.fnUntraced(function* (value) {
94
- const { f } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get))
95
- return yield* f(_tag)(value)
96
- }, Effect.scoped)
120
+ const fn = ((_tag: keyof D["external"], ...f: Array<any>) =>
121
+ Effect.fnUntraced(
122
+ function* (payload: any) {
123
+ const { fnRaw: fn } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get))
124
+ return yield* fn(_tag, payload)
125
+ },
126
+ Effect.scoped,
127
+ ...(f as [any]),
128
+ )) as Fn<Self, D["external"]>
97
129
 
98
130
  const invalidate = tag.asEffect().pipe(
99
131
  Effect.flatMap((rc) =>
@@ -106,47 +138,69 @@ export const Service =
106
138
  Effect.ignore,
107
139
  )
108
140
 
141
+ const reducer = <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_event: K, f: R) => f
142
+
109
143
  return Object.assign(tag, {
110
144
  [TypeId]: TypeId,
111
145
  definition,
112
146
  protocol,
147
+ state,
113
148
  events,
114
- f,
149
+ fn,
115
150
  invalidate,
151
+ reducer,
116
152
  })
117
153
  }
118
154
 
119
- export interface ClientTransport<D extends ProtocolDefinition> {
155
+ export interface ClientTransport<D extends ProtocolDefinition, R> {
120
156
  readonly listen: (
121
- publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError>,
122
- ) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"]>
157
+ publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError, R>,
158
+ ) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"] | R>
123
159
 
124
160
  readonly send: (
125
161
  message: Protocol<D>["F"]["Payload"]["Type"],
126
162
  ) => Effect.Effect<void, ClientError | S.SchemaError, Protocol<D>["F"]["Payload"]["EncodingServices"]>
127
163
  }
128
164
 
129
- const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
130
- client: Client<Self, Id, D>,
131
- build: Effect.Effect<ClientTransport<D>, ClientError, R | Scope.Scope>,
132
- replay?: ReplayConfig | undefined,
133
- ) =>
165
+ const make = <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, R, CR>({
166
+ build,
167
+ client,
168
+ reducers,
169
+ onConnect,
170
+ replay,
171
+ }: {
172
+ readonly client: Client<Self, Id, D>
173
+ readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
174
+ readonly reducers: Reducers
175
+ readonly build: Effect.Effect<
176
+ ClientTransport<D, Reducer.Reducers.Services<Self, Reducers> | CR>,
177
+ ClientError,
178
+ R | Scope.Scope
179
+ >
180
+ readonly replay?: ReplayConfig | undefined
181
+ }) =>
134
182
  Effect.gen(function* () {
135
- const rcr: RcRef.RcRef<Session<Self, D>, ClientError> = yield* RcRef.make({
183
+ const rcr: Service<Self, D> = yield* RcRef.make({
136
184
  acquire: Effect.gen(function* () {
137
185
  type _ = typeof client.protocol
138
186
  type Event = ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"]
139
187
 
140
- yield* debug("AcquisitionStarted")
141
-
142
188
  const { listen, send } = yield* build
143
189
 
144
190
  const audition = yield* Deferred.make<void>()
191
+ const stateDeferred = yield* Deferred.make<Ref.Ref<S.Struct<D["state"]>["Type"]>>()
145
192
 
146
- const inflights: Record<string, Deferred.Deferred<_["F"]["Success"]["Type"], FError<D>>> = {}
193
+ const inflights: Record<
194
+ string,
195
+ {
196
+ readonly deferred: Deferred.Deferred<_["F"]["Success"]["Type"], FnError<D["external"], keyof D["external"]>>
197
+ readonly span?: Tracer.AnySpan | undefined
198
+ }
199
+ > = {}
147
200
  let callId = 0
148
201
  let takeCount = 0
149
- const pubsub = yield* PubSub.unbounded<EventTake<Event, ClientError>>()
202
+ const eventsPubsub = yield* PubSub.unbounded<EventTake<Event, ClientError>>()
203
+ const statePubsub = yield* PubSub.unbounded<S.Struct<D["state"]>["Type"]>({ replay: 1 })
150
204
 
151
205
  const replayState = yield* Ref.make<{
152
206
  readonly startupOpen: boolean
@@ -175,57 +229,85 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
175
229
  return { startupOpen, buffer }
176
230
  })
177
231
  }
178
- yield* PubSub.publish(pubsub, eventTake)
232
+ yield* PubSub.publish(eventsPubsub, eventTake)
179
233
  })
180
234
 
181
235
  const outer = yield* Scope.Scope
182
236
  const scope = yield* Scope.fork(outer, "sequential")
183
237
  const end = Scope.close(scope, Exit.void)
238
+ const reduceMutex = yield* Semaphore.make(1)
239
+ const reduceTask = Semaphore.withPermits(reduceMutex, 1)
184
240
 
185
241
  const fiber = yield* listen(
186
242
  Effect.fnUntraced(function* (message) {
187
243
  switch (message._tag) {
188
244
  case "Audition.Success": {
189
- yield* debug("Audition.Succeeded")
245
+ const { initial } = message
246
+ yield* PubSub.publish(statePubsub, initial)
247
+ const state = yield* Ref.make(initial)
248
+ yield* Deferred.succeed(stateDeferred, state)
190
249
  yield* Deferred.succeed(audition, void 0)
250
+ yield* onConnect?.(initial) ?? Effect.void
191
251
  return
192
252
  }
193
253
  case "Audition.Failure": {
194
254
  const { expected, actual } = message
195
- yield* debug("Audition.Failed", { expected, actual })
196
255
  return yield* new AuditionError({ value: { expected, actual } })
197
256
  }
198
257
  case "Event": {
199
258
  const { event } = message
200
- yield* debug("Event.Emitted", { event })
201
- yield* publishTake([event as never], true)
259
+ const { _tag } = event as never
260
+ const reducer = reducers[_tag]!
261
+ const state = yield* Deferred.await(stateDeferred)
262
+ yield* Effect.gen(function* () {
263
+ const current = yield* Ref.get(state)
264
+ const reduced = yield* reducer(event as never)(current).pipe(
265
+ Effect.provideService(client, rcr),
266
+ ) as Effect.Effect<
267
+ S.Struct<D["state"]>["Type"] | undefined,
268
+ never,
269
+ Reducer.Reducers.Services<Self, Reducers>
270
+ >
271
+ if (reduced) {
272
+ yield* PubSub.publish(statePubsub, reduced)
273
+ yield* Ref.set(state, reduced)
274
+ }
275
+ }).pipe(reduceTask)
276
+ const parent = message.trace ? Tracer.externalSpan(message.trace) : undefined
277
+ yield* publishTake([event], true).pipe(
278
+ span("enqueue-event", {
279
+ attributes: { _tag },
280
+ kind: "consumer",
281
+ parent,
282
+ }),
283
+ )
202
284
  return
203
285
  }
204
286
  case "F.Success":
205
287
  case "F.Failure": {
206
288
  const { id } = message
207
- const deferred = inflights[id]
208
- if (deferred) {
289
+ const inflight = inflights[id]
290
+ if (inflight) {
209
291
  delete inflights[id]
210
- switch (message._tag) {
211
- case "F.Success": {
212
- const { _tag, value } = message.success as never
213
- yield* debug("Call.Succeeded", { id, _tag, value })
214
- yield* Deferred.succeed(deferred, value)
215
- return
292
+ return yield* Effect.gen(function* () {
293
+ switch (message._tag) {
294
+ case "F.Success": {
295
+ const { value } = message.success as never
296
+ yield* Deferred.succeed(inflight.deferred, value)
297
+ return
298
+ }
299
+ case "F.Failure": {
300
+ const { _tag, value } = message.failure as never
301
+ yield* Effect.annotateLogs(Effect.logDebug("Call.Failed"), { id, _tag })
302
+ yield* Deferred.fail(inflight.deferred, value)
303
+ return
304
+ }
216
305
  }
217
- case "F.Failure": {
218
- const { _tag, value } = message.failure as never
219
- yield* debug("Call.Failed", { id, _tag, value })
220
- yield* Deferred.fail(deferred, value)
221
- return
222
- }
223
- }
306
+ }).pipe(inflight.span ? Effect.withParentSpan(inflight.span, { captureStackTrace: false }) : identity)
224
307
  }
225
308
  return
226
309
  }
227
310
  case "Disconnect": {
228
- yield* debug("Disconnected")
229
311
  return
230
312
  }
231
313
  }
@@ -234,7 +316,13 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
234
316
  Effect.ensuring(
235
317
  Effect.all(
236
318
  [
237
- debug("Client.Closed", { unresolved: Record.keys(inflights).length }),
319
+ Effect.sync(() => Record.keys(inflights).length).pipe(
320
+ Effect.flatMap((unresolved) =>
321
+ unresolved === 0
322
+ ? Effect.void
323
+ : Effect.annotateLogs(Effect.logDebug("Client.Closed"), { unresolved }),
324
+ ),
325
+ ),
238
326
  Deferred.succeed(audition, void 0),
239
327
  RcRef.invalidate(rcr),
240
328
  ],
@@ -245,8 +333,25 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
245
333
  Effect.provideService(Scope.Scope, scope),
246
334
  )
247
335
 
336
+ const interrupt = Stream.interruptWhen(
337
+ Fiber.await(fiber).pipe(
338
+ Effect.flatMap(
339
+ Exit.match({
340
+ onSuccess: () => Effect.void,
341
+ onFailure: flow(
342
+ Cause.findError,
343
+ Result.match({
344
+ onSuccess: Effect.fail,
345
+ onFailure: () => Effect.void,
346
+ }),
347
+ ),
348
+ }),
349
+ ),
350
+ ),
351
+ )
352
+
248
353
  const events = Effect.gen(function* () {
249
- const queue = yield* PubSub.subscribe(pubsub)
354
+ const queue = yield* PubSub.subscribe(eventsPubsub)
250
355
  const live = (replayCount: number) =>
251
356
  Stream.fromSubscription(queue).pipe(
252
357
  Stream.filter((entry) => entry.seq > replayCount),
@@ -283,109 +388,115 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
283
388
  ),
284
389
  live(replayCount),
285
390
  )
286
- }).pipe(
287
- Stream.unwrap,
288
- Stream.interruptWhen(
289
- Fiber.await(fiber).pipe(
290
- Effect.flatMap(
291
- Exit.match({
292
- onSuccess: () => Effect.void,
293
- onFailure: flow(
294
- Cause.findError,
295
- Result.match({
296
- onSuccess: Effect.fail,
297
- onFailure: () => Effect.void,
298
- }),
299
- ),
300
- }),
301
- ),
302
- ),
303
- ),
304
- )
391
+ }).pipe(Stream.unwrap, interrupt)
305
392
 
306
- yield* Deferred.await(audition)
393
+ const state = Stream.fromPubSub(statePubsub).pipe(interrupt)
307
394
 
308
395
  const encodingServices = yield* Effect.context<_["F"]["Payload"]["EncodingServices"]>()
309
396
 
310
- const f: F<Self, D> = (_tag) =>
311
- Effect.fnUntraced(
312
- function* (value) {
313
- const exit = fiber.pollUnsafe()
314
- if (exit) {
315
- return yield* Exit.match(exit, {
316
- onSuccess: () => new UnresolvedError(),
317
- onFailure: flow(
318
- Cause.findError,
319
- Result.match({
320
- onSuccess: Effect.fail,
321
- onFailure: () => new UnresolvedError(),
322
- }),
323
- ),
324
- })
325
- }
326
- const id = callId++
327
- const inflight = yield* Deferred.make<_["F"]["Success"]["Type"], FError<D>>()
328
- inflights[id] = inflight
329
- yield* send({
330
- _tag: "F.Payload",
331
- id,
332
- payload: { _tag, value } as never,
397
+ yield* Deferred.await(audition)
398
+
399
+ const fnRaw = <K extends keyof D["external"]>(_tag: K, value: D["external"][K]["payload"]["Type"]) =>
400
+ Effect.gen(function* () {
401
+ const exit = fiber.pollUnsafe()
402
+ if (exit) {
403
+ return yield* Exit.match(exit, {
404
+ onSuccess: () => new UnresolvedError(),
405
+ onFailure: flow(
406
+ Cause.findError,
407
+ Result.match({
408
+ onSuccess: Effect.fail,
409
+ onFailure: () => new UnresolvedError(),
410
+ }),
411
+ ),
333
412
  })
334
- return yield* Effect.raceFirst(
335
- Deferred.await(inflight),
336
- Fiber.await(fiber).pipe(
337
- Effect.flatMap(
338
- (exit): Effect.Effect<never, ClientError | UnresolvedError | S.SchemaError> =>
339
- Exit.match(exit, {
340
- onSuccess: () => new UnresolvedError().asEffect(),
341
- onFailure: flow(
342
- Cause.findError,
343
- Result.match({
344
- onSuccess: Effect.fail,
345
- onFailure: () => new UnresolvedError().asEffect(),
346
- }),
347
- ),
348
- }),
349
- ),
413
+ }
414
+ const id = callId++
415
+ const deferred = yield* Deferred.make<
416
+ _["F"]["Success"]["Type"],
417
+ FnError<D["external"], keyof D["external"]>
418
+ >()
419
+ const span = yield* Tracing.current
420
+ const trace = span ? Tracing.toTraceEnvelope(span) : undefined
421
+ inflights[id] = { deferred, span }
422
+ yield* send({
423
+ _tag: "F.Payload",
424
+ id,
425
+ payload: { _tag, value } as never,
426
+ ...(trace && { trace }),
427
+ })
428
+ return yield* Effect.raceFirst(
429
+ Deferred.await(deferred),
430
+ Fiber.await(fiber).pipe(
431
+ Effect.flatMap(
432
+ (exit): Effect.Effect<never, ClientError | UnresolvedError | S.SchemaError> =>
433
+ Exit.match(exit, {
434
+ onSuccess: () => new UnresolvedError().asEffect(),
435
+ onFailure: flow(
436
+ Cause.findError,
437
+ Result.match({
438
+ onSuccess: Effect.fail,
439
+ onFailure: () => new UnresolvedError().asEffect(),
440
+ }),
441
+ ),
442
+ }),
350
443
  ),
351
- )
352
- },
353
- span("f"),
354
- Effect.scoped,
444
+ ),
445
+ )
446
+ }).pipe(
447
+ span("fn", {
448
+ kind: "client",
449
+ attributes: { _tag },
450
+ }),
355
451
  Effect.provide(encodingServices),
356
452
  )
357
453
 
358
- return { events, f, end }
454
+ return { state, events, fnRaw, end }
359
455
  }).pipe(span("acquire", { attributes: { client: client.key } }), Effect.annotateLogs("client", client.key)),
360
456
  })
361
457
 
362
458
  return rcr
363
459
  }).pipe(Layer.effect(client))
364
460
 
365
- export const layerSocket = <Self, Id extends string, D extends ProtocolDefinition>({
461
+ export const layerSocket = <
462
+ Self,
463
+ Id extends string,
464
+ D extends ProtocolDefinition,
465
+ Reducers extends Reducer.Reducers<D>,
466
+ CR = never,
467
+ >({
366
468
  client,
469
+ reducers,
367
470
  url,
368
471
  protocols,
369
472
  replay,
473
+ onConnect,
370
474
  }: {
371
475
  readonly client: Client<Self, Id, D>
372
- readonly url?: string | undefined
373
- readonly protocols?: string | Array<string> | undefined
476
+ readonly reducers: Reducers
374
477
  readonly replay?: ReplayConfig | undefined
478
+ readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
479
+ readonly protocols?: string | Array<string> | undefined
480
+ readonly url?: string | undefined
375
481
  }): Layer.Layer<
376
482
  Self,
377
483
  never,
378
484
  | Socket.WebSocketConstructor
379
485
  | Protocol<D>["Actor"]["DecodingServices"]
380
486
  | Protocol<D>["F"]["Payload"]["EncodingServices"]
487
+ | Reducer.Reducers.Services<Self, Reducers>
488
+ | CR
381
489
  > => {
382
490
  const { F, Actor } = client.protocol
383
491
  const encodeFPayload = encodeJsonString(F.Payload)
384
492
  const decodeActor = decodeJsonString(Actor)
385
493
 
386
- return make<Self, Id, D, Socket.WebSocketConstructor>(
494
+ return make<Self, Id, D, Reducers, Socket.WebSocketConstructor, CR>({
387
495
  client,
388
- Effect.gen(function* () {
496
+ reducers,
497
+ onConnect,
498
+ replay,
499
+ build: Effect.gen(function* () {
389
500
  const socket = yield* Socket.makeWebSocket(url ?? "/", {
390
501
  protocols: ["liminal", Encoding.encodeBase64Url(client.key), ...(protocols ? Array.ensure(protocols) : [])],
391
502
  })
@@ -405,10 +516,9 @@ export const layerSocket = <Self, Id extends string, D extends ProtocolDefinitio
405
516
  Effect.fnUntraced(function* (cause) {
406
517
  const { reason } = cause
407
518
  if (reason._tag === "SocketCloseError" && reason.code === 1000) {
408
- yield* debug("Socket.Disconnected")
409
519
  return yield* publish({ _tag: "Disconnect" })
410
520
  }
411
- yield* debug(`SocketErrored.${reason._tag}`, { cause })
521
+ yield* Effect.annotateLogs(Effect.logDebug(`SocketErrored.${reason._tag}`), { cause })
412
522
  return yield* new ConnectionError({ cause })
413
523
  }),
414
524
  ),
@@ -427,24 +537,41 @@ export const layerSocket = <Self, Id extends string, D extends ProtocolDefinitio
427
537
  ),
428
538
  }
429
539
  }),
430
- replay,
431
- )
540
+ })
432
541
  }
433
542
 
434
- export const layerWorker = <Self, Id extends string, D extends ProtocolDefinition, T extends Protocol<D>>({
543
+ export const layerWorker = <
544
+ Self,
545
+ Id extends string,
546
+ D extends ProtocolDefinition,
547
+ Reducers extends Reducer.Reducers<D>,
548
+ T extends Protocol<D>,
549
+ CR = never,
550
+ >({
435
551
  client,
552
+ reducers,
436
553
  replay,
554
+ onConnect,
437
555
  }: {
438
556
  readonly client: Client<Self, Id, D>
557
+ readonly reducers: Reducers
439
558
  readonly replay?: ReplayConfig | undefined
559
+ readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
440
560
  }): Layer.Layer<
441
561
  Self,
442
562
  never,
443
- Worker.WorkerPlatform | Worker.Spawner | T["Actor"]["DecodingServices"] | T["F"]["Payload"]["EncodingServices"]
563
+ | Worker.WorkerPlatform
564
+ | Worker.Spawner
565
+ | T["Actor"]["DecodingServices"]
566
+ | T["F"]["Payload"]["EncodingServices"]
567
+ | Reducer.Reducers.Services<Self, Reducers>
444
568
  > =>
445
- make<Self, Id, D, Worker.WorkerPlatform | Worker.Spawner>(
569
+ make<Self, Id, D, Reducers, Worker.WorkerPlatform | Worker.Spawner, CR>({
446
570
  client,
447
- Effect.gen(function* () {
571
+ reducers,
572
+ onConnect,
573
+ replay,
574
+ build: Effect.gen(function* () {
448
575
  const platform = yield* Worker.WorkerPlatform
449
576
  const backing = yield* platform
450
577
  .spawn<T["Actor"]["Type"], T["Client"]["Type"]>(0)
@@ -484,5 +611,4 @@ export const layerWorker = <Self, Id extends string, D extends ProtocolDefinitio
484
611
  send,
485
612
  }
486
613
  }),
487
- replay,
488
- )
614
+ })