liminal 0.17.5 → 0.17.7

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 (51) hide show
  1. package/Actor.ts +29 -45
  2. package/ActorTransport.ts +17 -0
  3. package/Audition.ts +27 -48
  4. package/CHANGELOG.md +12 -0
  5. package/Client.ts +104 -173
  6. package/ClientDirectory.ts +104 -0
  7. package/ClientHandle.ts +6 -13
  8. package/F.ts +5 -7
  9. package/Method.ts +5 -13
  10. package/Protocol.ts +85 -68
  11. package/Send.ts +5 -9
  12. package/_util/schema.ts +7 -0
  13. package/dist/Actor.d.ts +15 -17
  14. package/dist/Actor.js +1 -3
  15. package/dist/Actor.js.map +1 -1
  16. package/dist/ActorTransport.d.ts +7 -0
  17. package/dist/ActorTransport.js +2 -0
  18. package/dist/ActorTransport.js.map +1 -0
  19. package/dist/Audition.d.ts +7 -7
  20. package/dist/Audition.js.map +1 -1
  21. package/dist/Client.d.ts +23 -28
  22. package/dist/Client.js +63 -67
  23. package/dist/Client.js.map +1 -1
  24. package/dist/ClientDirectory.d.ts +21 -0
  25. package/dist/ClientDirectory.js +41 -0
  26. package/dist/ClientDirectory.js.map +1 -0
  27. package/dist/ClientHandle.d.ts +6 -5
  28. package/dist/ClientHandle.js.map +1 -1
  29. package/dist/F.d.ts +2 -2
  30. package/dist/F.js +1 -1
  31. package/dist/F.js.map +1 -1
  32. package/dist/Method.d.ts +5 -10
  33. package/dist/Method.js +1 -1
  34. package/dist/Method.js.map +1 -1
  35. package/dist/Protocol.d.ts +51 -34
  36. package/dist/Protocol.js +20 -22
  37. package/dist/Protocol.js.map +1 -1
  38. package/dist/Send.d.ts +2 -1
  39. package/dist/_util/schema.d.ts +4 -0
  40. package/dist/_util/schema.js +5 -0
  41. package/dist/_util/schema.js.map +1 -0
  42. package/dist/errors.d.ts +5 -5
  43. package/dist/errors.js +2 -2
  44. package/dist/errors.js.map +1 -1
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.js +2 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/errors.ts +5 -5
  50. package/index.ts +2 -0
  51. package/package.json +1 -1
package/Client.ts CHANGED
@@ -21,31 +21,20 @@ import {
21
21
  Cause,
22
22
  Result,
23
23
  flow,
24
- identity,
25
24
  } from "effect"
26
25
  import { Socket } from "effect/unstable/socket"
27
26
  import { Worker } from "effect/unstable/workers"
28
27
 
29
- import type { MethodDefinition } from "./Method.ts"
30
-
31
28
  import * as Diagnostic from "./_util/Diagnostic.ts"
29
+ import { decodeJsonString, encodeJsonString } from "./_util/schema.ts"
32
30
  import { type ClientError, AuditionError, ConnectionError, type FError, UnresolvedError } from "./errors.ts"
33
31
  import { type F } from "./F.ts"
34
- import * as Protocol from "./Protocol.ts"
32
+ import { Protocol, type ProtocolDefinition } from "./Protocol.ts"
35
33
 
36
34
  const { debug, span } = Diagnostic.module("Client")
37
35
 
38
36
  export const TypeId = "~liminal/Client" as const
39
37
 
40
- export interface ClientDefinition<
41
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
42
- EventDefinitions extends Record<string, S.Struct.Fields>,
43
- > {
44
- readonly methods: MethodDefinitions
45
-
46
- readonly events: EventDefinitions
47
- }
48
-
49
38
  export interface ReplayConfig {
50
39
  readonly mode: "startup" | "all-subscribers"
51
40
 
@@ -58,60 +47,49 @@ interface EventTake<A, E> {
58
47
  readonly take: Take.Take<A, E>
59
48
  }
60
49
 
61
- export interface Session<
62
- ClientSelf,
63
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
64
- EventDefinitions extends Record<string, S.Struct.Fields>,
65
- > {
66
- readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<EventDefinitions>>["Type"], ClientError>
50
+ export interface Session<Self, D extends ProtocolDefinition> {
51
+ readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>
67
52
 
68
- readonly f: F<ClientSelf, MethodDefinitions>
53
+ readonly f: F<Self, D>
69
54
 
70
55
  readonly end: Effect.Effect<void>
71
56
  }
72
57
 
73
- export type Service<
74
- ClientSelf,
75
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
76
- EventDefinitions extends Record<string, S.Struct.Fields>,
77
- > = RcRef.RcRef<Session<ClientSelf, MethodDefinitions, EventDefinitions>, ClientError>
58
+ export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<Session<ClientSelf, D>, ClientError>
78
59
 
79
- export interface Client<
80
- ClientSelf,
81
- ClientId extends string,
82
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
83
- EventDefinitions extends Record<string, S.Struct.Fields>,
84
- > extends Context.Service<ClientSelf, Service<ClientSelf, MethodDefinitions, EventDefinitions>> {
85
- new (_: never): Context.ServiceClass.Shape<ClientId, Service<ClientSelf, MethodDefinitions, EventDefinitions>>
60
+ export interface Client<Self, ClientId extends string, D extends ProtocolDefinition> extends Context.Service<
61
+ Self,
62
+ Service<Self, D>
63
+ > {
64
+ new (_: never): Context.ServiceClass.Shape<ClientId, Service<Self, D>>
86
65
 
87
66
  readonly [TypeId]: typeof TypeId
88
67
 
89
- readonly definition: ClientDefinition<MethodDefinitions, EventDefinitions>
68
+ readonly definition: D
90
69
 
91
- readonly schema: Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>
70
+ readonly protocol: Protocol<D>
92
71
 
93
- readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<EventDefinitions>>["Type"], ClientError, ClientSelf>
72
+ readonly events: Stream.Stream<
73
+ ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"],
74
+ ClientError | S.SchemaError,
75
+ Self
76
+ >
94
77
 
95
- readonly f: F<ClientSelf, MethodDefinitions>
78
+ readonly f: F<Self, D>
96
79
 
97
- readonly invalidate: Effect.Effect<void, never, ClientSelf>
80
+ readonly invalidate: Effect.Effect<void, never, Self>
98
81
  }
99
82
 
100
83
  export const Service =
101
- <ClientSelf>() =>
102
- <
103
- ClientId extends string,
104
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
105
- EventDefinitions extends Record<string, S.Struct.Fields>,
106
- >(
107
- id: ClientId,
108
- definition: ClientDefinition<MethodDefinitions, EventDefinitions>,
109
- ): Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions> => {
110
- const tag = Context.Service<ClientSelf, Service<ClientSelf, MethodDefinitions, EventDefinitions>>()(id)
84
+ <Self>() =>
85
+ <Id extends string, D extends ProtocolDefinition>(id: Id, definition: D): Client<Self, Id, D> => {
86
+ const tag = Context.Service<Self, Service<Self, D>>()(id)
87
+
88
+ const protocol = Protocol(definition)
111
89
 
112
90
  const events = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(Struct.get("events")), Stream.unwrap)
113
91
 
114
- const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
92
+ const f: F<Self, D> = (_tag) =>
115
93
  Effect.fnUntraced(function* (value) {
116
94
  const { f } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get))
117
95
  return yield* f(_tag)(value)
@@ -131,54 +109,33 @@ export const Service =
131
109
  return Object.assign(tag, {
132
110
  [TypeId]: TypeId,
133
111
  definition,
134
- schema: Protocol.ProtocolSchemas(definition.methods, definition.events),
112
+ protocol,
135
113
  events,
136
114
  f,
137
115
  invalidate,
138
116
  })
139
117
  }
140
118
 
141
- export interface Transport<
142
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
143
- EventDefinitions extends Record<string, S.Struct.Fields>,
144
- > {
119
+ export interface ClientTransport<D extends ProtocolDefinition> {
145
120
  readonly listen: (
146
- publish: (
147
- message:
148
- | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["Type"]
149
- | typeof Protocol.TransportFailure.Type,
150
- ) => Effect.Effect<void, ClientError>,
151
- ) => Effect.Effect<
152
- void,
153
- ClientError,
154
- Scope.Scope | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["DecodingServices"]
155
- >
121
+ publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError>,
122
+ ) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"]>
156
123
 
157
124
  readonly send: (
158
- message: Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["Type"],
159
- ) => Effect.Effect<
160
- void,
161
- ConnectionError,
162
- Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["EncodingServices"]
163
- >
125
+ message: Protocol<D>["F"]["Payload"]["Type"],
126
+ ) => Effect.Effect<void, ClientError | S.SchemaError, Protocol<D>["F"]["Payload"]["EncodingServices"]>
164
127
  }
165
128
 
166
- const make = <
167
- ClientSelf,
168
- ClientId extends string,
169
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
170
- EventDefinitions extends Record<string, S.Struct.Fields>,
171
- R,
172
- >(
173
- client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>,
174
- build: Effect.Effect<Transport<MethodDefinitions, EventDefinitions>, ClientError, R | Scope.Scope>,
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>,
175
132
  replay?: ReplayConfig | undefined,
176
133
  ) =>
177
134
  Effect.gen(function* () {
178
- const rcr: RcRef.RcRef<Session<ClientSelf, MethodDefinitions, EventDefinitions>, ClientError> = yield* RcRef.make({
135
+ const rcr: RcRef.RcRef<Session<Self, D>, ClientError> = yield* RcRef.make({
179
136
  acquire: Effect.gen(function* () {
180
- type _ = typeof client.schema
181
- type Event = ReturnType<typeof S.TaggedUnion<EventDefinitions>>["Type"]
137
+ type _ = typeof client.protocol
138
+ type Event = ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"]
182
139
 
183
140
  yield* debug("AcquisitionStarted")
184
141
 
@@ -186,7 +143,7 @@ const make = <
186
143
 
187
144
  const audition = yield* Deferred.make<void>()
188
145
 
189
- const inflights: Record<string, Deferred.Deferred<_["f"]["success"]["Type"], FError<MethodDefinitions>>> = {}
146
+ const inflights: Record<string, Deferred.Deferred<_["F"]["Success"]["Type"], FError<D>>> = {}
190
147
  let callId = 0
191
148
  let takeCount = 0
192
149
  const pubsub = yield* PubSub.unbounded<EventTake<Event, ClientError>>()
@@ -228,33 +185,38 @@ const make = <
228
185
  const fiber = yield* listen(
229
186
  Effect.fnUntraced(function* (message) {
230
187
  switch (message._tag) {
231
- case "AuditionSuccess": {
232
- yield* debug("AuditionSucceeded")
188
+ case "Audition.Success": {
189
+ yield* debug("Audition.Succeeded")
233
190
  yield* Deferred.succeed(audition, void 0)
234
191
  return
235
192
  }
193
+ case "Audition.Failure": {
194
+ const { expected, actual } = message
195
+ yield* debug("Audition.Failed", { expected, actual })
196
+ return yield* new AuditionError({ value: { expected, actual } })
197
+ }
236
198
  case "Event": {
237
199
  const { event } = message
238
- yield* debug("EventEmitted", { event })
200
+ yield* debug("Event.Emitted", { event })
239
201
  yield* publishTake([event as never], true)
240
202
  return
241
203
  }
242
- case "FSuccess":
243
- case "FFailure": {
204
+ case "F.Success":
205
+ case "F.Failure": {
244
206
  const { id } = message
245
207
  const deferred = inflights[id]
246
208
  if (deferred) {
247
209
  delete inflights[id]
248
210
  switch (message._tag) {
249
- case "FSuccess": {
211
+ case "F.Success": {
250
212
  const { _tag, value } = message.success as never
251
- yield* debug("CallSucceeded", { id, _tag, value })
213
+ yield* debug("Call.Succeeded", { id, _tag, value })
252
214
  yield* Deferred.succeed(deferred, value)
253
215
  return
254
216
  }
255
- case "FFailure": {
217
+ case "F.Failure": {
256
218
  const { _tag, value } = message.failure as never
257
- yield* debug("CallFailed", { id, _tag, value })
219
+ yield* debug("Call.Failed", { id, _tag, value })
258
220
  yield* Deferred.fail(deferred, value)
259
221
  return
260
222
  }
@@ -262,27 +224,17 @@ const make = <
262
224
  }
263
225
  return
264
226
  }
265
- case "AuditionFailure": {
266
- const { client, routed } = message
267
- yield* debug("AuditionFailed", { client, routed })
268
- return yield* new AuditionError({ value: { client, routed } })
269
- }
270
227
  case "Disconnect": {
271
228
  yield* debug("Disconnected")
272
229
  return
273
230
  }
274
- case "TransportFailure": {
275
- const { cause } = message
276
- yield* debug("TransportFailed", { cause })
277
- return yield* new ConnectionError({ cause })
278
- }
279
231
  }
280
232
  }),
281
233
  ).pipe(
282
234
  Effect.ensuring(
283
235
  Effect.all(
284
236
  [
285
- debug("ClientClosed", { unresolved: Record.keys(inflights).length }),
237
+ debug("Client.Closed", { unresolved: Record.keys(inflights).length }),
286
238
  Deferred.succeed(audition, void 0),
287
239
  RcRef.invalidate(rcr),
288
240
  ],
@@ -353,9 +305,9 @@ const make = <
353
305
 
354
306
  yield* Deferred.await(audition)
355
307
 
356
- const encodingServices = yield* Effect.context<_["f"]["payload"]["EncodingServices"]>()
308
+ const encodingServices = yield* Effect.context<_["F"]["Payload"]["EncodingServices"]>()
357
309
 
358
- const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
310
+ const f: F<Self, D> = (_tag) =>
359
311
  Effect.fnUntraced(
360
312
  function* (value) {
361
313
  const exit = fiber.pollUnsafe()
@@ -365,17 +317,17 @@ const make = <
365
317
  onFailure: flow(
366
318
  Cause.findError,
367
319
  Result.match({
368
- onSuccess: identity,
320
+ onSuccess: Effect.fail,
369
321
  onFailure: () => new UnresolvedError(),
370
322
  }),
371
323
  ),
372
324
  })
373
325
  }
374
326
  const id = callId++
375
- const inflight = yield* Deferred.make<_["f"]["success"]["Type"], FError<MethodDefinitions>>()
327
+ const inflight = yield* Deferred.make<_["F"]["Success"]["Type"], FError<D>>()
376
328
  inflights[id] = inflight
377
329
  yield* send({
378
- _tag: "FPayload",
330
+ _tag: "F.Payload",
379
331
  id,
380
332
  payload: { _tag, value } as never,
381
333
  })
@@ -383,7 +335,7 @@ const make = <
383
335
  Deferred.await(inflight),
384
336
  Fiber.await(fiber).pipe(
385
337
  Effect.flatMap(
386
- (exit): Effect.Effect<never, ClientError | UnresolvedError> =>
338
+ (exit): Effect.Effect<never, ClientError | UnresolvedError | S.SchemaError> =>
387
339
  Exit.match(exit, {
388
340
  onSuccess: () => new UnresolvedError().asEffect(),
389
341
  onFailure: flow(
@@ -410,29 +362,28 @@ const make = <
410
362
  return rcr
411
363
  }).pipe(Layer.effect(client))
412
364
 
413
- export const layerSocket = <
414
- ClientSelf,
415
- ClientId extends string,
416
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
417
- EventDefinitions extends Record<string, S.Struct.Fields>,
418
- >({
365
+ export const layerSocket = <Self, Id extends string, D extends ProtocolDefinition>({
419
366
  client,
420
367
  url,
421
368
  protocols,
422
369
  replay,
423
370
  }: {
424
- readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
371
+ readonly client: Client<Self, Id, D>
425
372
  readonly url?: string | undefined
426
373
  readonly protocols?: string | Array<string> | undefined
427
374
  readonly replay?: ReplayConfig | undefined
428
375
  }): Layer.Layer<
429
- ClientSelf,
376
+ Self,
430
377
  never,
431
378
  | Socket.WebSocketConstructor
432
- | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["DecodingServices"]
433
- | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["EncodingServices"]
434
- > =>
435
- make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Socket.WebSocketConstructor>(
379
+ | Protocol<D>["Actor"]["DecodingServices"]
380
+ | Protocol<D>["F"]["Payload"]["EncodingServices"]
381
+ > => {
382
+ const { F, Actor } = client.protocol
383
+ const encodeFPayload = encodeJsonString(F.Payload)
384
+ const decodeActor = decodeJsonString(Actor)
385
+
386
+ return make<Self, Id, D, Socket.WebSocketConstructor>(
436
387
  client,
437
388
  Effect.gen(function* () {
438
389
  const socket = yield* Socket.makeWebSocket(url ?? "/", {
@@ -444,49 +395,29 @@ export const layerSocket = <
444
395
  .runRaw((raw) =>
445
396
  pipe(
446
397
  raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw,
447
- S.decodeUnknownEffect(S.fromJsonString(S.toCodecJson(client.schema.actor))),
398
+ decodeActor,
448
399
  Effect.andThen(publish),
449
400
  ),
450
401
  )
451
402
  .pipe(
452
- Effect.catchTag(
453
- "SocketError",
403
+ Effect.catchIf(
404
+ Socket.isSocketError,
454
405
  Effect.fnUntraced(function* (cause) {
455
406
  const { reason } = cause
456
- switch (reason._tag) {
457
- case "SocketReadError":
458
- case "SocketWriteError":
459
- case "SocketOpenError": {
460
- yield* debug(reason._tag, { cause })
461
- return yield* publish({ _tag: "TransportFailure", cause })
462
- }
463
- case "SocketCloseError": {
464
- const { code, closeReason } = reason
465
- switch (code) {
466
- case 1000: {
467
- return yield* publish({ _tag: "Disconnect" })
468
- }
469
- case 4003: {
470
- return yield* S.decodeUnknownEffect(S.fromJsonString(Protocol.AuditionFailure))(
471
- closeReason,
472
- ).pipe(Effect.andThen(publish))
473
- }
474
- }
475
- yield* debug("SocketCloseError", { cause })
476
- return yield* publish({ _tag: "TransportFailure", cause })
477
- }
407
+ if (reason._tag === "SocketCloseError" && reason.code === 1000) {
408
+ yield* debug("Socket.Disconnected")
409
+ return yield* publish({ _tag: "Disconnect" })
478
410
  }
411
+ yield* debug(`SocketErrored.${reason._tag}`, { cause })
412
+ return yield* new ConnectionError({ cause })
479
413
  }),
480
414
  ),
481
- Effect.catchTag("SchemaError", Effect.die),
482
415
  )
483
416
  }, span("listen")),
484
417
  send: Effect.fnUntraced(
485
418
  function* (v) {
486
419
  const write = yield* socket.writer
487
- const message = yield* S.encodeEffect(S.fromJsonString(S.toCodecJson(client.schema.f.payload)))(v).pipe(
488
- Effect.mapError((cause) => new ConnectionError({ cause })),
489
- )
420
+ const message = yield* encodeFPayload(v)
490
421
  yield* write(message).pipe(
491
422
  Effect.catchTag("SocketError", (cause) => new ConnectionError({ cause }).asEffect()),
492
423
  )
@@ -498,37 +429,28 @@ export const layerSocket = <
498
429
  }),
499
430
  replay,
500
431
  )
432
+ }
501
433
 
502
- export const layerWorker = <
503
- ClientSelf,
504
- ClientId extends string,
505
- MethodDefinitions extends Record<string, MethodDefinition.Any>,
506
- EventDefinitions extends Record<string, S.Struct.Fields>,
507
- >({
434
+ export const layerWorker = <Self, Id extends string, D extends ProtocolDefinition, T extends Protocol<D>>({
508
435
  client,
509
436
  replay,
510
437
  }: {
511
- readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
438
+ readonly client: Client<Self, Id, D>
512
439
  readonly replay?: ReplayConfig | undefined
513
440
  }): Layer.Layer<
514
- ClientSelf,
441
+ Self,
515
442
  never,
516
- | Worker.WorkerPlatform
517
- | Worker.Spawner
518
- | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["DecodingServices"]
519
- | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["EncodingServices"]
443
+ Worker.WorkerPlatform | Worker.Spawner | T["Actor"]["DecodingServices"] | T["F"]["Payload"]["EncodingServices"]
520
444
  > =>
521
- make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Worker.WorkerPlatform | Worker.Spawner>(
445
+ make<Self, Id, D, Worker.WorkerPlatform | Worker.Spawner>(
522
446
  client,
523
447
  Effect.gen(function* () {
524
- type T = typeof client.schema
525
-
526
448
  const platform = yield* Worker.WorkerPlatform
527
449
  const backing = yield* platform
528
- .spawn<T["actor"]["Type"], T["f"]["payload"]["Type"] | string>(0)
450
+ .spawn<T["Actor"]["Type"], T["Client"]["Type"]>(0)
529
451
  .pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()))
530
452
 
531
- const send = (message: T["f"]["payload"]["Type"]) =>
453
+ const send = (message: T["Client"]["Type"]) =>
532
454
  backing.send(message).pipe(
533
455
  Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()),
534
456
  span("send"),
@@ -537,18 +459,27 @@ export const layerWorker = <
537
459
  return {
538
460
  listen: Effect.fnUntraced(function* (publish) {
539
461
  const stop = yield* Deferred.make<void>()
540
- yield* Effect.raceFirst(
541
- backing.run(
462
+ yield* backing
463
+ .run(
542
464
  Effect.fnUntraced(function* (message) {
543
465
  yield* publish(message)
544
- if (message._tag === "Disconnect" || message._tag === "AuditionFailure") {
466
+ if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
545
467
  yield* Deferred.succeed(stop, void 0)
546
468
  }
547
469
  }),
548
- { onSpawn: backing.send(client.key).pipe(Effect.orDie) },
549
- ),
550
- Deferred.await(stop),
551
- ).pipe(Effect.catchTag("WorkerError", (cause) => publish({ _tag: "TransportFailure", cause })))
470
+ {
471
+ onSpawn: backing
472
+ .send({
473
+ _tag: "Audition.Payload",
474
+ client: client.key,
475
+ })
476
+ .pipe(Effect.orDie),
477
+ },
478
+ )
479
+ .pipe(
480
+ Effect.raceFirst(Deferred.await(stop)),
481
+ Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()),
482
+ )
552
483
  }, span("listen")),
553
484
  send,
554
485
  }
@@ -0,0 +1,104 @@
1
+ import { Schema as S, Effect, Cause, Ref } from "effect"
2
+
3
+ import type { TopFromString } from "./_util/schema.ts"
4
+ import type { Actor } from "./Actor.ts"
5
+ import type { ActorTransport } from "./ActorTransport.ts"
6
+ import type { ProtocolDefinition, Disconnect, Protocol } from "./Protocol.ts"
7
+
8
+ import * as Diagnostic from "./_util/Diagnostic.ts"
9
+ import { phantom } from "./_util/phantom.ts"
10
+ import * as ClientHandle from "./ClientHandle.ts"
11
+
12
+ const { span } = Diagnostic.module("ClientDirectory")
13
+
14
+ export interface ClientDirectory<
15
+ Raw,
16
+ ActorSelf,
17
+ AttachmentFields extends S.Struct.Fields,
18
+ D extends ProtocolDefinition,
19
+ > {
20
+ readonly "": {
21
+ readonly Handle: ClientHandle.ClientHandle<ActorSelf, AttachmentFields, D>
22
+ }
23
+
24
+ readonly handles: ReadonlySet<this[""]["Handle"]>
25
+
26
+ readonly register: (
27
+ raw: Raw,
28
+ attachments: S.Struct<AttachmentFields>["Type"],
29
+ ) => Effect.Effect<this[""]["Handle"], S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
30
+
31
+ readonly get: (raw: Raw) => Effect.Effect<this[""]["Handle"], Cause.NoSuchElementError>
32
+
33
+ readonly unregister: (raw: Raw) => Effect.Effect<void>
34
+ }
35
+
36
+ export interface HandleEncoders<T, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
37
+ attachments: (
38
+ value: S.Struct<AttachmentFields>["Type"],
39
+ ) => Effect.Effect<T, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
40
+ event: (
41
+ value: Protocol<D>["Event"]["Type"],
42
+ ) => Effect.Effect<T, S.SchemaError, Protocol<D>["Event"]["EncodingServices"]>
43
+ disconnect: Effect.Effect<T, S.SchemaError, (typeof Disconnect)["EncodingServices"]>
44
+ }
45
+
46
+ export const make = <
47
+ Raw,
48
+ ActorSelf,
49
+ ActorId extends string,
50
+ Name extends TopFromString,
51
+ AttachmentFields extends S.Struct.Fields,
52
+ ClientSelf,
53
+ ClientId extends string,
54
+ D extends ProtocolDefinition,
55
+ >(
56
+ _actor: Actor<ActorSelf, ActorId, Name, AttachmentFields, ClientSelf, ClientId, D>,
57
+ { send, close, snapshot }: ActorTransport<Raw, AttachmentFields, D>,
58
+ ): ClientDirectory<Raw, ActorSelf, AttachmentFields, D> => {
59
+ type Handle = ClientHandle.ClientHandle<ActorSelf, AttachmentFields, D>
60
+
61
+ const raws = new Map<Raw, Handle>()
62
+ const handles = new Set<Handle>()
63
+
64
+ const get = (raw: Raw) => Effect.fromNullishOr(raws.get(raw))
65
+
66
+ const register = Effect.fnUntraced(function* (raw: Raw, attachments: S.Struct<AttachmentFields>["Type"]) {
67
+ yield* snapshot(raw, attachments)
68
+ const attachmentsRef = yield* Ref.make(attachments)
69
+ const handle: Handle = ClientHandle.make({
70
+ attachments: Ref.get(attachmentsRef),
71
+ save: Effect.fnUntraced(function* (attachments) {
72
+ yield* Ref.set(attachmentsRef, attachments)
73
+ yield* snapshot(raw, attachments)
74
+ }),
75
+ send: (_tag, payload) =>
76
+ send(raw, {
77
+ _tag: "Event",
78
+ event: { _tag, ...payload } as never,
79
+ }),
80
+ disconnect: close(raw).pipe(
81
+ Effect.andThen(() =>
82
+ Effect.sync(() => {
83
+ raws.delete(raw)
84
+ handles.delete(handle)
85
+ }),
86
+ ),
87
+ ),
88
+ })
89
+ raws.set(raw, handle)
90
+ handles.add(handle)
91
+ return handle
92
+ }, span("register"))
93
+
94
+ const unregister = (raw: Raw) =>
95
+ Effect.sync(() => {
96
+ const handle = raws.get(raw)
97
+ if (handle) {
98
+ raws.delete(raw)
99
+ handles.delete(handle)
100
+ }
101
+ }).pipe(span("unregister"))
102
+
103
+ return { ...phantom, handles, register, get, unregister }
104
+ }
package/ClientHandle.ts CHANGED
@@ -1,17 +1,14 @@
1
1
  import { Schema as S, Effect } from "effect"
2
2
 
3
+ import type { ProtocolDefinition } from "./Protocol.ts"
3
4
  import type { Send } from "./Send.ts"
4
5
 
5
6
  const TypeId = "~liminal/ClientHandle" as const
6
7
 
7
- export interface ClientHandle<
8
- ActorSelf,
9
- AttachmentFields extends S.Struct.Fields,
10
- EventDefinitions extends Record<string, S.Struct.Fields>,
11
- > {
8
+ export interface ClientHandle<ActorSelf, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
12
9
  readonly [TypeId]: typeof TypeId
13
10
 
14
- readonly send: Send<ActorSelf, EventDefinitions>
11
+ readonly send: Send<ActorSelf, D>
15
12
 
16
13
  readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
17
14
 
@@ -22,17 +19,13 @@ export interface ClientHandle<
22
19
  readonly disconnect: Effect.Effect<void, never, ActorSelf>
23
20
  }
24
21
 
25
- export const make = <
26
- ActorSelf,
27
- AttachmentFields extends S.Struct.Fields,
28
- EventDefinitions extends Record<string, S.Struct.Fields>,
29
- >({
22
+ export const make = <ActorSelf, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition>({
30
23
  send,
31
24
  attachments,
32
25
  save,
33
26
  disconnect,
34
27
  }: {
35
- readonly send: Send<ActorSelf, EventDefinitions>
28
+ readonly send: Send<ActorSelf, D>
36
29
 
37
30
  readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
38
31
 
@@ -41,7 +34,7 @@ export const make = <
41
34
  ) => Effect.Effect<void, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
42
35
 
43
36
  readonly disconnect: Effect.Effect<void, never, ActorSelf>
44
- }): ClientHandle<ActorSelf, AttachmentFields, EventDefinitions> => ({
37
+ }): ClientHandle<ActorSelf, AttachmentFields, D> => ({
45
38
  [TypeId]: TypeId,
46
39
  send,
47
40
  attachments,
package/F.ts CHANGED
@@ -1,12 +1,10 @@
1
- import { Record, Effect } from "effect"
1
+ import { Effect } from "effect"
2
2
 
3
3
  import type { FError } from "./errors.ts"
4
- import type { MethodDefinition } from "./Method.ts"
4
+ import type { ProtocolDefinition } from "./Protocol.ts"
5
5
 
6
- export type F<ClientSelf, MethodDefinitions extends Record<string, MethodDefinition.Any>> = <
7
- Method extends keyof MethodDefinitions,
8
- >(
6
+ export type F<Self, D extends ProtocolDefinition> = <Method extends keyof D["methods"]>(
9
7
  method: Method,
10
8
  ) => (
11
- payload: MethodDefinitions[Method]["payload"]["Type"],
12
- ) => Effect.Effect<MethodDefinitions[Method]["success"]["Type"], FError<MethodDefinitions>, ClientSelf>
9
+ payload: D["methods"][Method]["payload"]["Type"],
10
+ ) => Effect.Effect<D["methods"][Method]["success"]["Type"], FError<D>, Self>