liminal 0.17.14 → 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 +8 -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 -20
  61. package/dist/workerd/WorkerdActorNamespace.js +30 -122
  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 +7 -22
  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 +72 -260
  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/dist/Actor.d.ts CHANGED
@@ -21,6 +21,6 @@ export interface Actor<ActorSelf, ActorId extends string, Name extends TopFromSt
21
21
  readonly definition: ActorDefinition<Name, AttachmentFields, ActorClientSelf, ActorClientId, D>;
22
22
  readonly all: Sender<ActorSelf, D>;
23
23
  readonly others: Sender<ActorSelf, D>;
24
- readonly handler: <K extends keyof D["methods"], R>(tag: K, f: Method.Handler<D["methods"][K], R>) => Method.Handler<D["methods"][K], R>;
24
+ readonly handler: <K extends keyof D["external"], R>(tag: K, f: Method.Handler<D["external"][K], R>) => Method.Handler<D["external"][K], R>;
25
25
  }
26
26
  export declare const Service: <ActorSelf>() => <ActorId extends string, Name extends TopFromString, D extends ProtocolDefinition, AttachmentFields extends S.Struct.Fields, ClientSelf, ClientId extends string>(id: ActorId, definition: ActorDefinition<Name, AttachmentFields, ClientSelf, ClientId, D>) => Actor<ActorSelf, ActorId, Name, AttachmentFields, ClientSelf, ClientId, D>;
package/dist/Actor.js CHANGED
@@ -1,24 +1,24 @@
1
1
  import { Context, Schema as S, Effect } from "effect";
2
- import { diagnostic } from "./_diagnostic.js";
2
+ import * as Spanner from "liminal-util/Spanner";
3
3
  import * as Method from "./Method.js";
4
4
  import {} from "./Protocol.js";
5
- const { span } = diagnostic("Actor");
5
+ const span = Spanner.make(import.meta.url);
6
6
  export const TypeId = "~liminal/Actor";
7
7
  export const Service = () => (id, definition) => {
8
8
  const tag = Context.Service()(id);
9
9
  const all = {
10
- send: (key, payload) => tag.asEffect().pipe(Effect.flatMap(({ clients }) => Effect.forEach(clients, (client) => client.send(key, payload), { concurrency: "unbounded" })), span("all.send")),
11
- disconnect: tag.asEffect().pipe(Effect.flatMap(({ clients }) => Effect.forEach(clients, ({ disconnect }) => disconnect)), span("all.disconnect")),
10
+ send: (key, payload) => tag.asEffect().pipe(Effect.flatMap(({ clients }) => Effect.forEach(clients, (client) => client.send(key, payload), { concurrency: "unbounded" })), span("send-all")),
11
+ disconnect: tag.asEffect().pipe(Effect.flatMap(({ clients }) => Effect.forEach(clients, ({ disconnect }) => disconnect)), span("disconnect-all")),
12
12
  };
13
13
  const others = {
14
14
  send: Effect.fnUntraced(function* (key, payload) {
15
15
  const { clients, currentClient } = yield* tag;
16
16
  yield* Effect.forEach(clients, (client) => (client === currentClient ? Effect.void : client.send(key, payload)), { concurrency: "unbounded" });
17
- }, span("others.send")),
17
+ }, span("send-others")),
18
18
  disconnect: Effect.gen(function* () {
19
19
  const { clients, currentClient } = yield* tag;
20
20
  yield* Effect.forEach(clients, (client) => (client === currentClient ? Effect.void : client.disconnect));
21
- }).pipe(span("others.disconnect")),
21
+ }).pipe(span("disconnect-others")),
22
22
  };
23
23
  const handler = (_tag, f) => f;
24
24
  return Object.assign(tag, {
package/dist/Actor.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Actor.js","sourceRoot":"","sources":["../Actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAMrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAA2B,MAAM,eAAe,CAAA;AAEvD,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAA;AAEpC,MAAM,CAAC,MAAM,MAAM,GAAG,gBAAyB,CAAA;AAsD/C,MAAM,CAAC,MAAM,OAAO,GAClB,GAAc,EAAE,CAChB,CAQE,EAAW,EACX,UAA4E,EACA,EAAE;IAC9E,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,EAA4D,CAAC,EAAE,CAAC,CAAA;IAE3F,MAAM,GAAG,GAAyB;QAChC,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CACrB,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CACjB,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC7F,EACD,IAAI,CAAC,UAAU,CAAC,CACjB;QACH,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CAC7B,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EACxF,IAAI,CAAC,gBAAgB,CAAC,CACvB;KACF,CAAA;IAED,MAAM,MAAM,GAAyB;QACnC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO;YAC7C,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;YAC7C,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CACnB,OAAO,EACP,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAChF,EAAE,WAAW,EAAE,WAAW,EAAE,CAC7B,CAAA;QACH,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;YAC7C,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAA;QAC1G,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;KACnC,CAAA;IAED,MAAM,OAAO,GAAG,CACd,IAAO,EACP,CAAqC,EACD,EAAE,CAAC,CAAC,CAAA;IAE1C,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE;QACxB,CAAC,MAAM,CAAC,EAAE,MAAM;QAChB,UAAU;QACV,GAAG;QACH,MAAM;QACN,OAAO;KACR,CAAC,CAAA;AACJ,CAAC,CAAA"}
1
+ {"version":3,"file":"Actor.js","sourceRoot":"","sources":["../Actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACrD,OAAO,KAAK,OAAO,MAAM,sBAAsB,CAAA;AAK/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAA2B,MAAM,eAAe,CAAA;AAEvD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE1C,MAAM,CAAC,MAAM,MAAM,GAAG,gBAAyB,CAAA;AAsD/C,MAAM,CAAC,MAAM,OAAO,GAClB,GAAc,EAAE,CAChB,CAQE,EAAW,EACX,UAA4E,EACA,EAAE;IAC9E,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,EAA4D,CAAC,EAAE,CAAC,CAAA;IAE3F,MAAM,GAAG,GAAyB;QAChC,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CACrB,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CACjB,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC7F,EACD,IAAI,CAAC,UAAU,CAAC,CACjB;QACH,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CAC7B,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EACxF,IAAI,CAAC,gBAAgB,CAAC,CACvB;KACF,CAAA;IAED,MAAM,MAAM,GAAyB;QACnC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO;YAC7C,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;YAC7C,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CACnB,OAAO,EACP,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAChF,EAAE,WAAW,EAAE,WAAW,EAAE,CAC7B,CAAA;QACH,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;YAC7C,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAA;QAC1G,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;KACnC,CAAA;IAED,MAAM,OAAO,GAAG,CACd,IAAO,EACP,CAAsC,EACD,EAAE,CAAC,CAAC,CAAA;IAE3C,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE;QACxB,CAAC,MAAM,CAAC,EAAE,MAAM;QAChB,UAAU;QACV,GAAG;QACH,MAAM;QACN,OAAO;KACR,CAAC,CAAA;AACJ,CAAC,CAAA"}
@@ -1,7 +1,8 @@
1
1
  import { Effect, Schema as S } from "effect";
2
2
  import type { Protocol, ProtocolDefinition } from "./Protocol.ts";
3
- export interface ActorTransport<Raw, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
4
- readonly send: (transport: Raw, event: Protocol<D>["Event"]["Type"]) => Effect.Effect<void, S.SchemaError, Protocol<D>["Event"]["EncodingServices"]>;
5
- readonly close: (transport: Raw) => Effect.Effect<void>;
6
- readonly snapshot: (transport: Raw, attachments: S.Struct<AttachmentFields>["Type"]) => Effect.Effect<void, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>;
3
+ export interface ActorTransport<Key, Client, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
4
+ readonly key: (client: Client) => Key;
5
+ readonly send: (client: Client, event: Protocol<D>["Event"]["Type"]) => Effect.Effect<void, S.SchemaError, Protocol<D>["Event"]["EncodingServices"]>;
6
+ readonly close: (client: Client) => Effect.Effect<void>;
7
+ readonly snapshot: (client: Client, attachments: S.Struct<AttachmentFields>["Type"]) => Effect.Effect<void, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>;
7
8
  }
@@ -1,17 +1,24 @@
1
- import { Schema as S, Pipeable, Stream } from "effect";
2
- import type { F } from "./F.ts";
3
- import type { ProtocolDefinition } from "./Protocol.ts";
1
+ import { Schema as S, Pipeable, Stream, Types } from "effect";
4
2
  import * as Client from "./Client.ts";
5
3
  import { type ClientError } from "./errors.ts";
4
+ import type { Fn } from "./Fn.ts";
5
+ import type { Methods } from "./Method.ts";
6
+ import type { ProtocolDefinition } from "./Protocol.ts";
6
7
  declare const TypeId: "~liminal/Audition";
7
- export interface Audition<ClientSelf, D extends ProtocolDefinition> extends Pipeable.Pipeable {
8
+ export interface Audition<AuditionSelf, State extends S.Union<ReadonlyArray<S.Top>>, External extends Methods, Event> extends Pipeable.Pipeable {
8
9
  readonly [TypeId]: typeof TypeId;
9
- readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError, ClientSelf>;
10
- readonly f: F<ClientSelf, D>;
10
+ readonly state: Stream.Stream<State["Type"], ClientError | S.SchemaError, AuditionSelf | State["DecodingServices"]>;
11
+ readonly fn: Fn<AuditionSelf, External>;
12
+ readonly events: Stream.Stream<Event, ClientError | S.SchemaError, AuditionSelf>;
11
13
  }
12
- export declare const empty: Audition<never, never>;
14
+ type MergeMethods<T extends Methods, U extends Methods> = [keyof T] extends [never] ? U : {
15
+ [K in keyof T & keyof U]: Types.Equals<T[K], U[K]> extends true ? T[K] : never;
16
+ };
17
+ type MergeState<State, D extends ProtocolDefinition> = [State] extends [never] ? S.Union<[S.Struct<D["state"]>]> : State extends S.Union<ReadonlyArray<S.Top>> ? S.Union<[...State["members"], S.Struct<D["state"]>]> : never;
18
+ export declare const empty: Audition<never, never, {}, never>;
19
+ export declare const cycleOn: <Event>(predicate: (event: Event) => boolean) => <AuditionSelf, State extends S.Union<ReadonlyArray<S.Top>>, External extends Methods>(audition: Audition<AuditionSelf, State, External, Event>) => Audition<AuditionSelf, State, External, Event>;
13
20
  export declare const add: {
14
- <ClientSelf, ClientId extends string, ClientD extends ProtocolDefinition>(client: Client.Client<ClientSelf, ClientId, ClientD>): <AuditionSelf, AuditionD extends ProtocolDefinition>(audition: Audition<AuditionSelf, AuditionD>) => Audition<AuditionSelf | ClientSelf, ProtocolDefinition.Merge<AuditionD, ClientD>>;
15
- <AuditionClientSelf, AuditionD extends ProtocolDefinition, ClientSelf, ClientId extends string, ClientD extends ProtocolDefinition>(audition: Audition<AuditionClientSelf, AuditionD>, client: Client.Client<ClientSelf, ClientId, ClientD>): Audition<AuditionClientSelf | ClientSelf, ProtocolDefinition.Merge<AuditionD, ClientD>>;
21
+ <ClientSelf, ClientId extends string, ClientD extends ProtocolDefinition>(client: Client.Client<ClientSelf, ClientId, ClientD>): <AuditionSelf, State extends S.Union<ReadonlyArray<S.Top>> | never, External extends Methods, Event>(audition: Audition<AuditionSelf, State, External, Event>) => Audition<AuditionSelf | ClientSelf, MergeState<State, ClientD>, MergeMethods<External, ClientD["external"]>, Event | ReturnType<typeof S.TaggedUnion<ClientD["events"]>>["Type"]>;
22
+ <AuditionSelf, State extends S.Union<ReadonlyArray<S.Top>> | never, External extends Methods, Event, ClientSelf, ClientId extends string, ClientD extends ProtocolDefinition>(audition: Audition<AuditionSelf, State, External, Event>, client: Client.Client<ClientSelf, ClientId, ClientD>): Audition<AuditionSelf | ClientSelf, MergeState<State, ClientD>, MergeMethods<External, ClientD["external"]>, Event | ReturnType<typeof S.TaggedUnion<ClientD["events"]>>["Type"]>;
16
23
  };
17
24
  export {};
package/dist/Audition.js CHANGED
@@ -1,29 +1,45 @@
1
- import { Schema as S, Pipeable, Stream, Effect, Function } from "effect";
2
- import { diagnostic } from "./_diagnostic.js";
1
+ import { Schema as S, Pipeable, Stream, Effect, Function, Types } from "effect";
3
2
  import * as Client from "./Client.js";
4
3
  import { AuditionError } from "./errors.js";
5
- const { debug, span } = diagnostic("Audition");
6
4
  const TypeId = "~liminal/Audition";
7
5
  export const empty = {
8
6
  [TypeId]: TypeId,
9
7
  pipe() {
10
8
  return Pipeable.pipeArguments(this, arguments);
11
9
  },
10
+ state: Stream.fail(new AuditionError()),
11
+ fn: () => () => new AuditionError().asEffect(),
12
12
  events: Stream.fail(new AuditionError()),
13
- f: () => () => new AuditionError().asEffect(),
13
+ };
14
+ export const cycleOn = (predicate) => (audition) => {
15
+ const events = audition.events.pipe(Stream.takeUntil(predicate), Stream.forever);
16
+ const state = audition.state.pipe(Stream.forever);
17
+ return {
18
+ [TypeId]: TypeId,
19
+ pipe() {
20
+ return Pipeable.pipeArguments(this, arguments);
21
+ },
22
+ events,
23
+ fn: audition.fn,
24
+ state,
25
+ };
14
26
  };
15
27
  export const add = Function.dual(2, (audition, client) => {
16
- const f = (method) => (payload) => audition
17
- .f(method)(payload)
18
- .pipe(Effect.catchTag("AuditionError", () => client.f(method)(payload)), span("f"));
19
- const events = audition.events.pipe(Stream.catchTag("AuditionError", () => Effect.succeed(client.events).pipe(Effect.tap(() => debug("AuditionStaged", { client: client.key })), Stream.unwrap)));
28
+ const fn = ((method, ...f) => Effect.fnUntraced(function* (payload) {
29
+ return yield* audition
30
+ .fn(method)(payload)
31
+ .pipe(Effect.catchTag("AuditionError", () => client.fn(method)(payload)));
32
+ }, ...f));
33
+ const events = audition.events.pipe(Stream.catchTag("AuditionError", () => Effect.succeed(client.events).pipe(Stream.unwrap)));
34
+ const state = audition.state.pipe(Stream.catchTag("AuditionError", () => Effect.succeed(client.state).pipe(Stream.unwrap)));
20
35
  return {
21
36
  [TypeId]: TypeId,
22
37
  pipe() {
23
38
  return Pipeable.pipeArguments(this, arguments);
24
39
  },
25
40
  events,
26
- f,
41
+ fn,
42
+ state,
27
43
  };
28
44
  });
29
45
  //# sourceMappingURL=Audition.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Audition.js","sourceRoot":"","sources":["../Audition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAKxE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAoB,aAAa,EAAE,MAAM,aAAa,CAAA;AAE7D,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;AAE9C,MAAM,MAAM,GAAG,mBAA4B,CAAA;AAc3C,MAAM,CAAC,MAAM,KAAK,GAA2B;IAC3C,CAAC,MAAM,CAAC,EAAE,MAAM;IAChB,IAAI;QACF,OAAO,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAChD,CAAC;IACD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,aAAa,EAAE,CAAC;IACxC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC,QAAQ,EAAE;CAC9C,CAAA;AAED,MAAM,CAAC,MAAM,GAAG,GAgBZ,QAAQ,CAAC,IAAI,CACf,CAAC,EACD,CAOE,QAA2C,EAC3C,MAAoD,EAC+B,EAAE;IACrF,MAAM,CAAC,GAA+E,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAC5G,QAAQ;SACL,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;SAClB,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EACjE,IAAI,CAAC,GAAG,CAAC,CACV,CAAA;IAEL,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CACjC,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CACpC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAChC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,EACjE,MAAM,CAAC,MAAM,CACd,CACF,CACF,CAAA;IAED,OAAO;QACL,CAAC,MAAM,CAAC,EAAE,MAAM;QAChB,IAAI;YACF,OAAO,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAChD,CAAC;QACD,MAAM;QACN,CAAC;KACF,CAAA;AACH,CAAC,CACF,CAAA"}
1
+ {"version":3,"file":"Audition.js","sourceRoot":"","sources":["../Audition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE/E,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAoB,aAAa,EAAE,MAAM,aAAa,CAAA;AAK7D,MAAM,MAAM,GAAG,mBAA4B,CAAA;AAuB3C,MAAM,CAAC,MAAM,KAAK,GAAsC;IACtD,CAAC,MAAM,CAAC,EAAE,MAAM;IAChB,IAAI;QACF,OAAO,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAChD,CAAC;IACD,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,aAAa,EAAE,CAAC;IACvC,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC,QAAQ,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,aAAa,EAAE,CAAC;CACzC,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAClB,CAAQ,SAAoC,EAAE,EAAE,CAChD,CACE,QAAwD,EACR,EAAE;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;IAChF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAEjD,OAAO;QACL,CAAC,MAAM,CAAC,EAAE,MAAM;QAChB,IAAI;YACF,OAAO,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAChD,CAAC;QACD,MAAM;QACN,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,KAAK;KACN,CAAA;AACH,CAAC,CAAA;AAEH,MAAM,CAAC,MAAM,GAAG,GA4BZ,QAAQ,CAAC,IAAI,CACf,CAAC,EACD,CASE,QAAwD,EACxD,MAAoD,EAMpD,EAAE;IACF,MAAM,EAAE,GAAG,CAAC,CAAC,MAAc,EAAE,GAAG,CAAQ,EAAE,EAAE,CAC1C,MAAM,CAAC,UAAU,CACf,QAAQ,CAAC,EAAE,OAAY;QACrB,OAAO,KAAK,CAAC,CAAC,QAAQ;aACnB,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;aACnB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7E,CAAC,EACD,GAAG,CAAC,CACL,CAA+E,CAAA;IAElF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CACjC,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAC1F,CAAA;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAC/B,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CACzF,CAAA;IAED,OAAO;QACL,CAAC,MAAM,CAAC,EAAE,MAAM;QAChB,IAAI;YACF,OAAO,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAChD,CAAC;QACD,MAAM;QACN,EAAE;QACF,KAAK;KACN,CAAA;AACH,CAAC,CACF,CAAA"}
package/dist/Client.d.ts CHANGED
@@ -2,40 +2,47 @@ import { Context, Effect, Layer, RcRef, Scope, Stream, Schema as S } from "effec
2
2
  import { Socket } from "effect/unstable/socket";
3
3
  import { Worker } from "effect/unstable/workers";
4
4
  import { type ClientError } from "./errors.ts";
5
- import { type F } from "./F.ts";
5
+ import type { Fn } from "./Fn.ts";
6
6
  import { Protocol, type ProtocolDefinition } from "./Protocol.ts";
7
+ import * as Reducer from "./Reducer.ts";
7
8
  export declare const TypeId: "~liminal/Client";
8
9
  export interface ReplayConfig {
9
10
  readonly mode: "startup" | "all-subscribers";
10
11
  readonly limit?: number | undefined;
11
12
  }
12
- export interface Session<Self, D extends ProtocolDefinition> {
13
+ export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<{
14
+ readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError>;
13
15
  readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>;
14
- readonly f: F<Self, D>;
16
+ readonly fnRaw: <K extends keyof D["external"], M extends D["external"][K]>(tag: K, payload: M["payload"]["Type"]) => Effect.Effect<M["success"]["Type"], M["failure"]["Type"], ClientSelf>;
15
17
  readonly end: Effect.Effect<void>;
16
- }
17
- export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<Session<ClientSelf, D>, ClientError>;
18
+ }, ClientError>;
18
19
  export interface Client<Self, ClientId extends string, D extends ProtocolDefinition> extends Context.Service<Self, Service<Self, D>> {
19
20
  new (_: never): Context.ServiceClass.Shape<ClientId, Service<Self, D>>;
20
21
  readonly [TypeId]: typeof TypeId;
21
22
  readonly definition: D;
22
23
  readonly protocol: Protocol<D>;
24
+ readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError, Self | S.Struct<D["state"]>["DecodingServices"]>;
23
25
  readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError, Self>;
24
- readonly f: F<Self, D>;
26
+ readonly fn: Fn<Self, D["external"]>;
25
27
  readonly invalidate: Effect.Effect<void, never, Self>;
28
+ readonly reducer: <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_tag: K, f: R) => R;
26
29
  }
27
30
  export declare const Service: <Self>() => <Id extends string, D extends ProtocolDefinition>(id: Id, definition: D) => Client<Self, Id, D>;
28
- export interface ClientTransport<D extends ProtocolDefinition> {
29
- readonly listen: (publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError>) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"]>;
31
+ export interface ClientTransport<D extends ProtocolDefinition, R> {
32
+ readonly listen: (publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError, R>) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"] | R>;
30
33
  readonly send: (message: Protocol<D>["F"]["Payload"]["Type"]) => Effect.Effect<void, ClientError | S.SchemaError, Protocol<D>["F"]["Payload"]["EncodingServices"]>;
31
34
  }
32
- export declare const layerSocket: <Self, Id extends string, D extends ProtocolDefinition>({ client, url, protocols, replay, }: {
35
+ export declare const layerSocket: <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, CR = never>({ client, reducers, url, protocols, replay, onConnect, }: {
33
36
  readonly client: Client<Self, Id, D>;
34
- readonly url?: string | undefined;
35
- readonly protocols?: string | Array<string> | undefined;
37
+ readonly reducers: Reducers;
36
38
  readonly replay?: ReplayConfig | undefined;
37
- }) => Layer.Layer<Self, never, Socket.WebSocketConstructor | Protocol<D>["Actor"]["DecodingServices"] | Protocol<D>["F"]["Payload"]["EncodingServices"]>;
38
- export declare const layerWorker: <Self, Id extends string, D extends ProtocolDefinition, T extends Protocol<D>>({ client, replay, }: {
39
+ readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>);
40
+ readonly protocols?: string | Array<string> | undefined;
41
+ readonly url?: string | undefined;
42
+ }) => Layer.Layer<Self, never, Socket.WebSocketConstructor | Protocol<D>["Actor"]["DecodingServices"] | Protocol<D>["F"]["Payload"]["EncodingServices"] | Reducer.Reducers.Services<Self, Reducers> | CR>;
43
+ export declare const layerWorker: <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, T extends Protocol<D>, CR = never>({ client, reducers, replay, onConnect, }: {
39
44
  readonly client: Client<Self, Id, D>;
45
+ readonly reducers: Reducers;
40
46
  readonly replay?: ReplayConfig | undefined;
41
- }) => Layer.Layer<Self, never, Worker.WorkerPlatform | Worker.Spawner | T["Actor"]["DecodingServices"] | T["F"]["Payload"]["EncodingServices"]>;
47
+ readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>);
48
+ }) => Layer.Layer<Self, never, Worker.WorkerPlatform | Worker.Spawner | T["Actor"]["DecodingServices"] | T["F"]["Payload"]["EncodingServices"] | Reducer.Reducers.Services<Self, Reducers>>;
package/dist/Client.js CHANGED
@@ -1,41 +1,47 @@
1
- import { Context, Encoding, Deferred, Effect, Layer, Option, PubSub, RcRef, Record, pipe, Ref, Scope, Stream, Take, Schema as S, Array, Struct, Fiber, Exit, Cause, Result, flow, } from "effect";
1
+ import { Context, Encoding, Deferred, Effect, Layer, Option, PubSub, RcRef, Record, pipe, Ref, Scope, Stream, Take, Schema as S, Array, Fiber, Exit, Cause, Result, flow, Tracer, identity, Semaphore, } from "effect";
2
2
  import { Socket } from "effect/unstable/socket";
3
3
  import { Worker } from "effect/unstable/workers";
4
- import { diagnostic } from "./_diagnostic.js";
4
+ import * as Spanner from "liminal-util/Spanner";
5
5
  import { decodeJsonString, encodeJsonString } from "./_util/schema.js";
6
6
  import { AuditionError, ConnectionError, UnresolvedError } from "./errors.js";
7
- import {} from "./F.js";
8
7
  import { Protocol } from "./Protocol.js";
9
- const { debug, span } = diagnostic("Client");
8
+ import * as Reducer from "./Reducer.js";
9
+ import * as Tracing from "./Tracing.js";
10
+ const span = Spanner.make(import.meta.url);
10
11
  export const TypeId = "~liminal/Client";
11
12
  export const Service = () => (id, definition) => {
12
13
  const tag = Context.Service()(id);
13
14
  const protocol = Protocol(definition);
14
- const events = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(Struct.get("events")), Stream.unwrap);
15
- const f = (_tag) => Effect.fnUntraced(function* (value) {
16
- const { f } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get));
17
- return yield* f(_tag)(value);
18
- }, Effect.scoped);
15
+ const state = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(({ state }) => state), Stream.unwrap);
16
+ const events = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(({ events }) => events), Stream.unwrap);
17
+ const fn = ((_tag, ...f) => Effect.fnUntraced(function* (payload) {
18
+ const { fnRaw: fn } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get));
19
+ return yield* fn(_tag, payload);
20
+ }, Effect.scoped, ...f));
19
21
  const invalidate = tag.asEffect().pipe(Effect.flatMap((rc) => RcRef.get(rc).pipe(Effect.flatMap(({ end }) => end), Effect.andThen(RcRef.invalidate(rc)))), Effect.scoped, Effect.ignore);
22
+ const reducer = (_event, f) => f;
20
23
  return Object.assign(tag, {
21
24
  [TypeId]: TypeId,
22
25
  definition,
23
26
  protocol,
27
+ state,
24
28
  events,
25
- f,
29
+ fn,
26
30
  invalidate,
31
+ reducer,
27
32
  });
28
33
  };
29
- const make = (client, build, replay) => Effect.gen(function* () {
34
+ const make = ({ build, client, reducers, onConnect, replay, }) => Effect.gen(function* () {
30
35
  const rcr = yield* RcRef.make({
31
36
  acquire: Effect.gen(function* () {
32
- yield* debug("AcquisitionStarted");
33
37
  const { listen, send } = yield* build;
34
38
  const audition = yield* Deferred.make();
39
+ const stateDeferred = yield* Deferred.make();
35
40
  const inflights = {};
36
41
  let callId = 0;
37
42
  let takeCount = 0;
38
- const pubsub = yield* PubSub.unbounded();
43
+ const eventsPubsub = yield* PubSub.unbounded();
44
+ const statePubsub = yield* PubSub.unbounded({ replay: 1 });
39
45
  const replayState = yield* Ref.make({
40
46
  startupOpen: true,
41
47
  buffer: [],
@@ -57,64 +63,93 @@ const make = (client, build, replay) => Effect.gen(function* () {
57
63
  return { startupOpen, buffer };
58
64
  });
59
65
  }
60
- yield* PubSub.publish(pubsub, eventTake);
66
+ yield* PubSub.publish(eventsPubsub, eventTake);
61
67
  });
62
68
  const outer = yield* Scope.Scope;
63
69
  const scope = yield* Scope.fork(outer, "sequential");
64
70
  const end = Scope.close(scope, Exit.void);
71
+ const reduceMutex = yield* Semaphore.make(1);
72
+ const reduceTask = Semaphore.withPermits(reduceMutex, 1);
65
73
  const fiber = yield* listen(Effect.fnUntraced(function* (message) {
66
74
  switch (message._tag) {
67
75
  case "Audition.Success": {
68
- yield* debug("Audition.Succeeded");
76
+ const { initial } = message;
77
+ yield* PubSub.publish(statePubsub, initial);
78
+ const state = yield* Ref.make(initial);
79
+ yield* Deferred.succeed(stateDeferred, state);
69
80
  yield* Deferred.succeed(audition, void 0);
81
+ yield* onConnect?.(initial) ?? Effect.void;
70
82
  return;
71
83
  }
72
84
  case "Audition.Failure": {
73
85
  const { expected, actual } = message;
74
- yield* debug("Audition.Failed", { expected, actual });
75
86
  return yield* new AuditionError({ value: { expected, actual } });
76
87
  }
77
88
  case "Event": {
78
89
  const { event } = message;
79
- yield* debug("Event.Emitted", { event });
80
- yield* publishTake([event], true);
90
+ const { _tag } = event;
91
+ const reducer = reducers[_tag];
92
+ const state = yield* Deferred.await(stateDeferred);
93
+ yield* Effect.gen(function* () {
94
+ const current = yield* Ref.get(state);
95
+ const reduced = yield* reducer(event)(current).pipe(Effect.provideService(client, rcr));
96
+ if (reduced) {
97
+ yield* PubSub.publish(statePubsub, reduced);
98
+ yield* Ref.set(state, reduced);
99
+ }
100
+ }).pipe(reduceTask);
101
+ const parent = message.trace ? Tracer.externalSpan(message.trace) : undefined;
102
+ yield* publishTake([event], true).pipe(span("enqueue-event", {
103
+ attributes: { _tag },
104
+ kind: "consumer",
105
+ parent,
106
+ }));
81
107
  return;
82
108
  }
83
109
  case "F.Success":
84
110
  case "F.Failure": {
85
111
  const { id } = message;
86
- const deferred = inflights[id];
87
- if (deferred) {
112
+ const inflight = inflights[id];
113
+ if (inflight) {
88
114
  delete inflights[id];
89
- switch (message._tag) {
90
- case "F.Success": {
91
- const { _tag, value } = message.success;
92
- yield* debug("Call.Succeeded", { id, _tag, value });
93
- yield* Deferred.succeed(deferred, value);
94
- return;
95
- }
96
- case "F.Failure": {
97
- const { _tag, value } = message.failure;
98
- yield* debug("Call.Failed", { id, _tag, value });
99
- yield* Deferred.fail(deferred, value);
100
- return;
115
+ return yield* Effect.gen(function* () {
116
+ switch (message._tag) {
117
+ case "F.Success": {
118
+ const { value } = message.success;
119
+ yield* Deferred.succeed(inflight.deferred, value);
120
+ return;
121
+ }
122
+ case "F.Failure": {
123
+ const { _tag, value } = message.failure;
124
+ yield* Effect.annotateLogs(Effect.logDebug("Call.Failed"), { id, _tag });
125
+ yield* Deferred.fail(inflight.deferred, value);
126
+ return;
127
+ }
101
128
  }
102
- }
129
+ }).pipe(inflight.span ? Effect.withParentSpan(inflight.span, { captureStackTrace: false }) : identity);
103
130
  }
104
131
  return;
105
132
  }
106
133
  case "Disconnect": {
107
- yield* debug("Disconnected");
108
134
  return;
109
135
  }
110
136
  }
111
137
  })).pipe(Effect.ensuring(Effect.all([
112
- debug("Client.Closed", { unresolved: Record.keys(inflights).length }),
138
+ Effect.sync(() => Record.keys(inflights).length).pipe(Effect.flatMap((unresolved) => unresolved === 0
139
+ ? Effect.void
140
+ : Effect.annotateLogs(Effect.logDebug("Client.Closed"), { unresolved }))),
113
141
  Deferred.succeed(audition, void 0),
114
142
  RcRef.invalidate(rcr),
115
143
  ], { concurrency: "unbounded" })), Effect.forkScoped, Effect.provideService(Scope.Scope, scope));
144
+ const interrupt = Stream.interruptWhen(Fiber.await(fiber).pipe(Effect.flatMap(Exit.match({
145
+ onSuccess: () => Effect.void,
146
+ onFailure: flow(Cause.findError, Result.match({
147
+ onSuccess: Effect.fail,
148
+ onFailure: () => Effect.void,
149
+ })),
150
+ }))));
116
151
  const events = Effect.gen(function* () {
117
- const queue = yield* PubSub.subscribe(pubsub);
152
+ const queue = yield* PubSub.subscribe(eventsPubsub);
118
153
  const live = (replayCount) => Stream.fromSubscription(queue).pipe(Stream.filter((entry) => entry.seq > replayCount), Stream.map((entry) => entry.take), Stream.flattenTake);
119
154
  if (!replay) {
120
155
  return live(-1);
@@ -134,16 +169,11 @@ const make = (client, build, replay) => Effect.gen(function* () {
134
169
  return buffer.length === 0
135
170
  ? live(replayCount)
136
171
  : Stream.concat(Stream.fromIterable(buffer).pipe(Stream.map((entry) => entry.take), Stream.flattenTake), live(replayCount));
137
- }).pipe(Stream.unwrap, Stream.interruptWhen(Fiber.await(fiber).pipe(Effect.flatMap(Exit.match({
138
- onSuccess: () => Effect.void,
139
- onFailure: flow(Cause.findError, Result.match({
140
- onSuccess: Effect.fail,
141
- onFailure: () => Effect.void,
142
- })),
143
- })))));
144
- yield* Deferred.await(audition);
172
+ }).pipe(Stream.unwrap, interrupt);
173
+ const state = Stream.fromPubSub(statePubsub).pipe(interrupt);
145
174
  const encodingServices = yield* Effect.context();
146
- const f = (_tag) => Effect.fnUntraced(function* (value) {
175
+ yield* Deferred.await(audition);
176
+ const fnRaw = (_tag, value) => Effect.gen(function* () {
147
177
  const exit = fiber.pollUnsafe();
148
178
  if (exit) {
149
179
  return yield* Exit.match(exit, {
@@ -155,82 +185,99 @@ const make = (client, build, replay) => Effect.gen(function* () {
155
185
  });
156
186
  }
157
187
  const id = callId++;
158
- const inflight = yield* Deferred.make();
159
- inflights[id] = inflight;
188
+ const deferred = yield* Deferred.make();
189
+ const span = yield* Tracing.current;
190
+ const trace = span ? Tracing.toTraceEnvelope(span) : undefined;
191
+ inflights[id] = { deferred, span };
160
192
  yield* send({
161
193
  _tag: "F.Payload",
162
194
  id,
163
195
  payload: { _tag, value },
196
+ ...(trace && { trace }),
164
197
  });
165
- return yield* Effect.raceFirst(Deferred.await(inflight), Fiber.await(fiber).pipe(Effect.flatMap((exit) => Exit.match(exit, {
198
+ return yield* Effect.raceFirst(Deferred.await(deferred), Fiber.await(fiber).pipe(Effect.flatMap((exit) => Exit.match(exit, {
166
199
  onSuccess: () => new UnresolvedError().asEffect(),
167
200
  onFailure: flow(Cause.findError, Result.match({
168
201
  onSuccess: Effect.fail,
169
202
  onFailure: () => new UnresolvedError().asEffect(),
170
203
  })),
171
204
  }))));
172
- }, span("f"), Effect.scoped, Effect.provide(encodingServices));
173
- return { events, f, end };
205
+ }).pipe(span("fn", {
206
+ kind: "client",
207
+ attributes: { _tag },
208
+ }), Effect.provide(encodingServices));
209
+ return { state, events, fnRaw, end };
174
210
  }).pipe(span("acquire", { attributes: { client: client.key } }), Effect.annotateLogs("client", client.key)),
175
211
  });
176
212
  return rcr;
177
213
  }).pipe(Layer.effect(client));
178
- export const layerSocket = ({ client, url, protocols, replay, }) => {
214
+ export const layerSocket = ({ client, reducers, url, protocols, replay, onConnect, }) => {
179
215
  const { F, Actor } = client.protocol;
180
216
  const encodeFPayload = encodeJsonString(F.Payload);
181
217
  const decodeActor = decodeJsonString(Actor);
182
- return make(client, Effect.gen(function* () {
183
- const socket = yield* Socket.makeWebSocket(url ?? "/", {
184
- protocols: ["liminal", Encoding.encodeBase64Url(client.key), ...(protocols ? Array.ensure(protocols) : [])],
185
- });
218
+ return make({
219
+ client,
220
+ reducers,
221
+ onConnect,
222
+ replay,
223
+ build: Effect.gen(function* () {
224
+ const socket = yield* Socket.makeWebSocket(url ?? "/", {
225
+ protocols: ["liminal", Encoding.encodeBase64Url(client.key), ...(protocols ? Array.ensure(protocols) : [])],
226
+ });
227
+ return {
228
+ listen: Effect.fnUntraced(function* (publish) {
229
+ yield* socket
230
+ .runRaw((raw) => pipe(raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw, decodeActor, Effect.andThen(publish)))
231
+ .pipe(Effect.catchIf(Socket.isSocketError, Effect.fnUntraced(function* (cause) {
232
+ const { reason } = cause;
233
+ if (reason._tag === "SocketCloseError" && reason.code === 1000) {
234
+ return yield* publish({ _tag: "Disconnect" });
235
+ }
236
+ yield* Effect.annotateLogs(Effect.logDebug(`SocketErrored.${reason._tag}`), { cause });
237
+ return yield* new ConnectionError({ cause });
238
+ })));
239
+ }, span("listen")),
240
+ send: Effect.fnUntraced(function* (v) {
241
+ const write = yield* socket.writer;
242
+ const message = yield* encodeFPayload(v);
243
+ yield* write(message).pipe(Effect.catchTag("SocketError", (cause) => new ConnectionError({ cause }).asEffect()));
244
+ }, span("send"), Effect.scoped),
245
+ };
246
+ }),
247
+ });
248
+ };
249
+ export const layerWorker = ({ client, reducers, replay, onConnect, }) => make({
250
+ client,
251
+ reducers,
252
+ onConnect,
253
+ replay,
254
+ build: Effect.gen(function* () {
255
+ const platform = yield* Worker.WorkerPlatform;
256
+ const backing = yield* platform
257
+ .spawn(0)
258
+ .pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
259
+ const send = (message) => backing.send(message).pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()), span("send"));
186
260
  return {
187
261
  listen: Effect.fnUntraced(function* (publish) {
188
- yield* socket
189
- .runRaw((raw) => pipe(raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw, decodeActor, Effect.andThen(publish)))
190
- .pipe(Effect.catchIf(Socket.isSocketError, Effect.fnUntraced(function* (cause) {
191
- const { reason } = cause;
192
- if (reason._tag === "SocketCloseError" && reason.code === 1000) {
193
- yield* debug("Socket.Disconnected");
194
- return yield* publish({ _tag: "Disconnect" });
262
+ const stop = yield* Deferred.make();
263
+ yield* backing
264
+ .run(Effect.fnUntraced(function* (message) {
265
+ yield* publish(message);
266
+ if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
267
+ yield* Deferred.succeed(stop, void 0);
195
268
  }
196
- yield* debug(`SocketErrored.${reason._tag}`, { cause });
197
- return yield* new ConnectionError({ cause });
198
- })));
269
+ }), {
270
+ onSpawn: backing
271
+ .send({
272
+ _tag: "Audition.Payload",
273
+ client: client.key,
274
+ })
275
+ .pipe(Effect.orDie),
276
+ })
277
+ .pipe(Effect.raceFirst(Deferred.await(stop)), Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
199
278
  }, span("listen")),
200
- send: Effect.fnUntraced(function* (v) {
201
- const write = yield* socket.writer;
202
- const message = yield* encodeFPayload(v);
203
- yield* write(message).pipe(Effect.catchTag("SocketError", (cause) => new ConnectionError({ cause }).asEffect()));
204
- }, span("send"), Effect.scoped),
279
+ send,
205
280
  };
206
- }), replay);
207
- };
208
- export const layerWorker = ({ client, replay, }) => make(client, Effect.gen(function* () {
209
- const platform = yield* Worker.WorkerPlatform;
210
- const backing = yield* platform
211
- .spawn(0)
212
- .pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
213
- const send = (message) => backing.send(message).pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()), span("send"));
214
- return {
215
- listen: Effect.fnUntraced(function* (publish) {
216
- const stop = yield* Deferred.make();
217
- yield* backing
218
- .run(Effect.fnUntraced(function* (message) {
219
- yield* publish(message);
220
- if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
221
- yield* Deferred.succeed(stop, void 0);
222
- }
223
- }), {
224
- onSpawn: backing
225
- .send({
226
- _tag: "Audition.Payload",
227
- client: client.key,
228
- })
229
- .pipe(Effect.orDie),
230
- })
231
- .pipe(Effect.raceFirst(Deferred.await(stop)), Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
232
- }, span("listen")),
233
- send,
234
- };
235
- }), replay);
281
+ }),
282
+ });
236
283
  //# sourceMappingURL=Client.js.map