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
@@ -1,36 +1,45 @@
1
1
  import { Schema as S, Effect, Cause, Ref } from "effect"
2
+ import * as Spanner from "liminal-util/Spanner"
2
3
 
4
+ import { phantom } from "./_util/phantom.ts"
3
5
  import type { TopFromString } from "./_util/schema.ts"
4
6
  import type { Actor } from "./Actor.ts"
5
7
  import type { ActorTransport } from "./ActorTransport.ts"
8
+ import * as ClientHandle from "./ClientHandle.ts"
6
9
  import type { ProtocolDefinition, Disconnect, Protocol } from "./Protocol.ts"
7
10
 
8
- import { diagnostic } from "./_diagnostic.ts"
9
- import { phantom } from "./_util/phantom.ts"
10
- import * as ClientHandle from "./ClientHandle.ts"
11
+ const span = Spanner.make(import.meta.url)
11
12
 
12
- const { span } = diagnostic("ClientDirectory")
13
+ export interface ClientEntry<Client, Handle> {
14
+ readonly client: Client
15
+
16
+ readonly handle: Handle
17
+ }
13
18
 
14
19
  export interface ClientDirectory<
15
- Raw,
20
+ Key,
21
+ Client,
16
22
  ActorSelf,
17
23
  AttachmentFields extends S.Struct.Fields,
18
24
  D extends ProtocolDefinition,
19
25
  > {
20
26
  readonly "": {
21
27
  readonly Handle: ClientHandle.ClientHandle<ActorSelf, AttachmentFields, D>
28
+ readonly Entry: ClientEntry<Client, ClientHandle.ClientHandle<ActorSelf, AttachmentFields, D>>
22
29
  }
23
30
 
24
31
  readonly handles: ReadonlySet<this[""]["Handle"]>
25
32
 
26
33
  readonly register: (
27
- raw: Raw,
34
+ client: Client,
28
35
  attachments: S.Struct<AttachmentFields>["Type"],
29
36
  ) => Effect.Effect<this[""]["Handle"], S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
30
37
 
31
- readonly get: (raw: Raw) => Effect.Effect<this[""]["Handle"], Cause.NoSuchElementError>
38
+ readonly get: (key: Key) => Effect.Effect<this[""]["Handle"], Cause.NoSuchElementError>
32
39
 
33
- readonly unregister: (raw: Raw) => Effect.Effect<void>
40
+ readonly entry: (key: Key) => Effect.Effect<this[""]["Entry"], Cause.NoSuchElementError>
41
+
42
+ readonly unregister: (key: Key) => Effect.Effect<void>
34
43
  }
35
44
 
36
45
  export interface HandleEncoders<T, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
@@ -44,7 +53,8 @@ export interface HandleEncoders<T, AttachmentFields extends S.Struct.Fields, D e
44
53
  }
45
54
 
46
55
  export const make = <
47
- Raw,
56
+ Key,
57
+ Client,
48
58
  ActorSelf,
49
59
  ActorId extends string,
50
60
  Name extends TopFromString,
@@ -54,51 +64,55 @@ export const make = <
54
64
  D extends ProtocolDefinition,
55
65
  >(
56
66
  _actor: Actor<ActorSelf, ActorId, Name, AttachmentFields, ClientSelf, ClientId, D>,
57
- { send, close, snapshot }: ActorTransport<Raw, AttachmentFields, D>,
58
- ): ClientDirectory<Raw, ActorSelf, AttachmentFields, D> => {
67
+ {
68
+ transport: { key, send, close, snapshot },
69
+ }: {
70
+ readonly transport: ActorTransport<Key, Client, AttachmentFields, D>
71
+ },
72
+ ): ClientDirectory<Key, Client, ActorSelf, AttachmentFields, D> => {
59
73
  type Handle = ClientHandle.ClientHandle<ActorSelf, AttachmentFields, D>
74
+ type Entry = ClientEntry<Client, Handle>
60
75
 
61
- const raws = new Map<Raw, Handle>()
76
+ const entries = new Map<Key, Entry>()
62
77
  const handles = new Set<Handle>()
63
78
 
64
- const get = (raw: Raw) => Effect.fromNullishOr(raws.get(raw))
79
+ const entry = (key: Key) => Effect.fromNullishOr(entries.get(key))
80
+ const get = (key: Key) => entry(key).pipe(Effect.map(({ handle }) => handle))
81
+
82
+ const unregister = (key: Key) =>
83
+ Effect.sync(() => {
84
+ const current = entries.get(key)
85
+ if (current) {
86
+ entries.delete(key)
87
+ handles.delete(current.handle)
88
+ }
89
+ }).pipe(span("unregister"))
65
90
 
66
- const register = Effect.fnUntraced(function* (raw: Raw, attachments: S.Struct<AttachmentFields>["Type"]) {
67
- yield* snapshot(raw, attachments)
91
+ const register = Effect.fnUntraced(function* (client: Client, attachments: S.Struct<AttachmentFields>["Type"]) {
92
+ const clientKey = key(client)
93
+ yield* snapshot(client, attachments)
68
94
  const attachmentsRef = yield* Ref.make(attachments)
69
95
  const handle: Handle = {
70
96
  attachments: Ref.get(attachmentsRef),
71
97
  save: Effect.fnUntraced(function* (attachments) {
72
98
  yield* Ref.set(attachmentsRef, attachments)
73
- yield* snapshot(raw, attachments)
99
+ yield* snapshot(client, attachments)
74
100
  }),
75
101
  send: (_tag, payload) =>
76
- send(raw, {
102
+ send(client, {
77
103
  _tag: "Event",
78
104
  event: { _tag, ...payload } as never,
79
105
  }),
80
- disconnect: close(raw).pipe(
81
- Effect.andThen(() =>
82
- Effect.sync(() => {
83
- raws.delete(raw)
84
- handles.delete(handle)
85
- }),
86
- ),
87
- ),
106
+ disconnect: close(client).pipe(Effect.andThen(unregister(clientKey)), Effect.asVoid),
107
+ }
108
+ const previous = entries.get(clientKey)
109
+ if (previous) {
110
+ handles.delete(previous.handle)
88
111
  }
89
- raws.set(raw, handle)
112
+ entries.set(clientKey, { client, handle })
90
113
  handles.add(handle)
91
114
  return handle
92
115
  }, span("register"))
93
116
 
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 }
117
+ return { ...phantom, handles, register, get, entry, unregister }
104
118
  }
@@ -0,0 +1,15 @@
1
+ import { Schema as S, Effect } from "effect"
2
+
3
+ import type { ProtocolDefinition, Disconnect, Protocol } from "./Protocol.ts"
4
+
5
+ export interface HandleEncoders<T, AttachmentFields extends S.Struct.Fields, D extends ProtocolDefinition> {
6
+ readonly attachments: (
7
+ value: S.Struct<AttachmentFields>["Type"],
8
+ ) => Effect.Effect<T, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
9
+
10
+ readonly event: (
11
+ value: Protocol<D>["Event"]["Type"],
12
+ ) => Effect.Effect<T, S.SchemaError, Protocol<D>["Event"]["EncodingServices"]>
13
+
14
+ readonly disconnect: Effect.Effect<T, S.SchemaError, (typeof Disconnect)["EncodingServices"]>
15
+ }
package/Fn.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { Effect, Schema as S } from "effect"
2
+
3
+ import type { ClientError, UnresolvedError } from "./errors.ts"
4
+ import type { Methods } from "./Method.ts"
5
+
6
+ export type FnPayload<External extends Methods, K extends keyof External> = External[K]["payload"]["Type"]
7
+
8
+ export type FnError<External extends Methods, K extends keyof External> = [
9
+ External[K]["failure"]["Type"] | ClientError | S.SchemaError | UnresolvedError,
10
+ ][0]
11
+
12
+ export type FnEffect<Self, External extends Methods, K extends keyof External> = Effect.Effect<
13
+ External[K]["success"]["Type"],
14
+ FnError<External, K>,
15
+ Self
16
+ >
17
+
18
+ export interface Fn<Self, Internal extends Methods> {
19
+ <K extends keyof Internal>(tag: K): (payload: FnPayload<Internal, K>) => FnEffect<Self, Internal, K>
20
+
21
+ <K extends keyof Internal, A>(
22
+ tag: K,
23
+ a: (effect: FnEffect<Self, Internal, K>, payload: FnPayload<Internal, K>) => A,
24
+ ): (payload: FnPayload<Internal, K>) => A
25
+
26
+ <K extends keyof Internal, A, B>(
27
+ tag: K,
28
+ a: (effect: FnEffect<Self, Internal, K>, payload: FnPayload<Internal, K>) => A,
29
+ b: (value: A, payload: FnPayload<Internal, K>) => B,
30
+ ): (payload: FnPayload<Internal, K>) => B
31
+
32
+ <K extends keyof Internal, A, B, C>(
33
+ tag: K,
34
+ a: (effect: FnEffect<Self, Internal, K>, payload: FnPayload<Internal, K>) => A,
35
+ b: (value: A, payload: FnPayload<Internal, K>) => B,
36
+ c: (value: B, payload: FnPayload<Internal, K>) => C,
37
+ ): (payload: FnPayload<Internal, K>) => C
38
+
39
+ <K extends keyof Internal, A, B, C, D>(
40
+ tag: K,
41
+ a: (effect: FnEffect<Self, Internal, K>, payload: FnPayload<Internal, K>) => A,
42
+ b: (value: A, payload: FnPayload<Internal, K>) => B,
43
+ c: (value: B, payload: FnPayload<Internal, K>) => C,
44
+ d: (value: C, payload: FnPayload<Internal, K>) => D,
45
+ ): (payload: FnPayload<Internal, K>) => D
46
+
47
+ <K extends keyof Internal, A, B, C, D, E>(
48
+ tag: K,
49
+ a: (effect: FnEffect<Self, Internal, K>, payload: FnPayload<Internal, K>) => A,
50
+ b: (value: A, payload: FnPayload<Internal, K>) => B,
51
+ c: (value: B, payload: FnPayload<Internal, K>) => C,
52
+ d: (value: C, payload: FnPayload<Internal, K>) => D,
53
+ e: (value: D, payload: FnPayload<Internal, K>) => E,
54
+ ): (payload: FnPayload<Internal, K>) => E
55
+ }
package/Method.ts CHANGED
@@ -1,29 +1,19 @@
1
1
  import { Schema as S, Effect } from "effect"
2
2
 
3
- export interface MethodDefinition<Payload extends S.Top, Success extends S.Top, Failure extends S.Top> {
4
- readonly payload: Payload
5
- readonly success: Success
6
- readonly failure: Failure
3
+ export interface Method {
4
+ readonly payload: S.Top
5
+ readonly success: S.Top
6
+ readonly failure: S.Top
7
7
  }
8
8
 
9
- export type Any = MethodDefinition<S.Top, S.Top, S.Top>
9
+ export type Methods = Record<string, Method>
10
10
 
11
- export const make = <Payload extends S.Top, Success extends S.Top, Failure extends S.Top>({
12
- payload,
13
- success,
14
- failure,
15
- }: {
16
- readonly payload: Payload
17
- readonly success: Success
18
- readonly failure: Failure
19
- }): MethodDefinition<Payload, Success, Failure> => ({ payload, success, failure })
11
+ export type Handler<M extends Method, R> = (
12
+ payload: M["payload"]["Type"],
13
+ ) => Effect.Effect<M["success"]["Type"], M["failure"]["Type"], R>
20
14
 
21
- export type Handler<Method extends Any, R> = (
22
- payload: Method["payload"]["Type"],
23
- ) => Effect.Effect<Method["success"]["Type"], Method["failure"]["Type"], R>
15
+ export const handler = <M extends Method, R>(_method: M, f: Handler<M, R>): Handler<M, R> => f
24
16
 
25
- export type Handlers<Methods extends Record<string, Any>, R> = {
26
- [K in keyof Methods]: Handler<Methods[K], R>
17
+ export type Handlers<Internal extends Methods, R> = {
18
+ readonly [K in keyof Internal]: Handler<Internal[K], R>
27
19
  }
28
-
29
- export const handler = <Method extends Any, R>(_method: Method, f: Handler<Method, R>): Handler<Method, R> => f
package/Protocol.ts CHANGED
@@ -1,33 +1,24 @@
1
- import { Schema as S, Record, Types } from "effect"
1
+ import { Schema as S, Record } from "effect"
2
2
 
3
- import type * as Method from "./Method.ts"
3
+ import type { Methods } from "./Method.ts"
4
+ import * as Tracing from "./Tracing.ts"
4
5
 
5
6
  export interface ProtocolDefinition<
6
- Methods extends Record<string, Method.Any> = Record<string, Method.Any>,
7
+ State extends S.Struct.Fields = S.Struct.Fields,
8
+ External extends Methods = Methods,
7
9
  Events extends Record<string, S.Struct.Fields> = Record<string, S.Struct.Fields>,
8
10
  > {
9
- readonly methods: Methods
11
+ readonly state: State
10
12
 
11
- readonly events: Events
12
- }
13
+ readonly external: External
13
14
 
14
- export declare namespace ProtocolDefinition {
15
- export type Merge<T extends ProtocolDefinition, U extends ProtocolDefinition> = ProtocolDefinition<
16
- [T] extends [never]
17
- ? U["methods"]
18
- : {
19
- [K in keyof T["methods"] & keyof U["methods"] as Types.Equals<T["methods"][K], U["methods"][K]> extends true
20
- ? K
21
- : never]: T["methods"][K]
22
- },
23
- [T] extends [never] ? U["events"] : T["events"] & U["events"]
24
- >
15
+ readonly events: Events
25
16
  }
26
17
 
27
18
  export interface Protocol<D extends ProtocolDefinition> {
28
19
  readonly Audition: {
29
- readonly Payload: S.TaggedStruct<"Audition.Payload", { client: S.String }>
30
- readonly Success: S.TaggedStruct<"Audition.Success", {}>
20
+ readonly Payload: S.TaggedStruct<"Audition.Payload", { readonly client: S.String }>
21
+ readonly Success: S.TaggedStruct<"Audition.Success", { readonly initial: S.Struct<D["state"]> }>
31
22
  readonly Failure: S.TaggedStruct<
32
23
  "Audition.Failure",
33
24
  {
@@ -43,6 +34,7 @@ export interface Protocol<D extends ProtocolDefinition> {
43
34
  readonly event: S.TaggedUnion<{
44
35
  readonly [K in keyof D["events"] & string]: S.TaggedStruct<K, D["events"][K]>
45
36
  }>
37
+ readonly trace: S.optional<typeof Tracing.TraceEnvelope>
46
38
  }
47
39
  >
48
40
 
@@ -52,8 +44,12 @@ export interface Protocol<D extends ProtocolDefinition> {
52
44
  {
53
45
  readonly id: S.Int
54
46
  readonly payload: S.TaggedUnion<{
55
- readonly [K in keyof D["methods"] & string]: S.TaggedStruct<K, { readonly value: D["methods"][K]["payload"] }>
47
+ readonly [K in keyof D["external"] & string]: S.TaggedStruct<
48
+ K,
49
+ { readonly value: D["external"][K]["payload"] }
50
+ >
56
51
  }>
52
+ readonly trace: S.optional<typeof Tracing.TraceEnvelope>
57
53
  }
58
54
  >
59
55
 
@@ -62,7 +58,10 @@ export interface Protocol<D extends ProtocolDefinition> {
62
58
  {
63
59
  readonly id: S.Int
64
60
  readonly success: S.TaggedUnion<{
65
- readonly [K in keyof D["methods"] & string]: S.TaggedStruct<K, { readonly value: D["methods"][K]["success"] }>
61
+ readonly [K in keyof D["external"] & string]: S.TaggedStruct<
62
+ K,
63
+ { readonly value: D["external"][K]["success"] }
64
+ >
66
65
  }>
67
66
  }
68
67
  >
@@ -72,7 +71,10 @@ export interface Protocol<D extends ProtocolDefinition> {
72
71
  {
73
72
  readonly id: S.Int
74
73
  readonly failure: S.TaggedUnion<{
75
- readonly [K in keyof D["methods"] & string]: S.TaggedStruct<K, { readonly value: D["methods"][K]["failure"] }>
74
+ readonly [K in keyof D["external"] & string]: S.TaggedStruct<
75
+ K,
76
+ { readonly value: D["external"][K]["failure"] }
77
+ >
76
78
  }>
77
79
  }
78
80
  >
@@ -96,37 +98,43 @@ export interface Protocol<D extends ProtocolDefinition> {
96
98
 
97
99
  export const Disconnect = S.TaggedStruct("Disconnect", {})
98
100
 
99
- const Audition = {
100
- Payload: S.TaggedStruct("Audition.Payload", {
101
- client: S.String,
102
- }),
103
- Success: S.TaggedStruct("Audition.Success", {}),
104
- Failure: S.TaggedStruct("Audition.Failure", {
105
- expected: S.String,
106
- actual: S.String,
107
- }),
108
- }
101
+ const AuditionPayload = S.TaggedStruct("Audition.Payload", {
102
+ client: S.String,
103
+ })
104
+
105
+ const AuditionFailure = S.TaggedStruct("Audition.Failure", {
106
+ expected: S.String,
107
+ actual: S.String,
108
+ })
109
109
 
110
- export const Protocol = <D extends ProtocolDefinition>({ events, methods }: D): Protocol<D> => {
110
+ export const Protocol = <D extends ProtocolDefinition>({ state, events, external }: D): Protocol<D> => {
111
111
  type T = Protocol<D>
112
112
 
113
+ const Audition = {
114
+ Payload: AuditionPayload,
115
+ Success: S.TaggedStruct("Audition.Success", { initial: S.Struct(state) }),
116
+ Failure: AuditionFailure,
117
+ }
118
+
113
119
  const F: T["F"] = {
114
120
  Payload: S.TaggedStruct("F.Payload", {
115
121
  id: S.Int,
116
- payload: S.TaggedUnion(Record.map(methods, ({ payload: value }) => ({ value }))),
122
+ payload: S.TaggedUnion(Record.map(external, ({ payload: value }) => ({ value }))),
123
+ trace: S.optional(Tracing.TraceEnvelope),
117
124
  }) as never,
118
125
  Success: S.TaggedStruct("F.Success", {
119
126
  id: S.Int,
120
- success: S.TaggedUnion(Record.map(methods, ({ success: value }) => ({ value }))),
127
+ success: S.TaggedUnion(Record.map(external, ({ success: value }) => ({ value }))),
121
128
  }) as never,
122
129
  Failure: S.TaggedStruct("F.Failure", {
123
130
  id: S.Int,
124
- failure: S.TaggedUnion(Record.map(methods, ({ failure: value }) => ({ value }))),
131
+ failure: S.TaggedUnion(Record.map(external, ({ failure: value }) => ({ value }))),
125
132
  }) as never,
126
133
  }
127
134
 
128
135
  const Event: T["Event"] = S.TaggedStruct("Event", {
129
136
  event: S.TaggedUnion(events),
137
+ trace: S.optional(Tracing.TraceEnvelope),
130
138
  }) as never
131
139
 
132
140
  const Client: T["Client"] = S.Union([Audition.Payload, F.Payload, Disconnect])
package/Reducer.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { Schema as S, Types, Effect } from "effect"
2
+
3
+ import type { ProtocolDefinition } from "./Protocol.ts"
4
+
5
+ export type Reducer<D extends ProtocolDefinition, K extends keyof D["events"] = keyof D["events"]> = (
6
+ event: Types.ExtractTag<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], Extract<K, string>>,
7
+ ) => (state: S.Struct<D["state"]>["Type"]) => Effect.Effect<S.Struct<D["state"]>["Type"] | void, never, unknown>
8
+
9
+ export type Reducers<D extends ProtocolDefinition> = {
10
+ readonly [K in keyof D["events"]]: Reducer<D, K>
11
+ }
12
+
13
+ export declare namespace Reducers {
14
+ export type Services<Self, D extends Record<string, Reducer<any, any>>> = Exclude<
15
+ {
16
+ readonly [K in keyof D]: D[K] extends (...args: any) => (...args: any) => Effect.Effect<any, any, infer R>
17
+ ? R
18
+ : never
19
+ }[keyof D],
20
+ Self
21
+ >
22
+ }
package/Tracing.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { Effect, Schema as S, Tracer } from "effect"
2
+
3
+ export const TraceEnvelope = S.Struct({
4
+ traceId: S.String,
5
+ spanId: S.String,
6
+ sampled: S.Boolean,
7
+ })
8
+
9
+ export const toTraceEnvelope = ({ traceId, spanId, sampled }: typeof TraceEnvelope.Type) => ({
10
+ traceId,
11
+ spanId,
12
+ sampled,
13
+ })
14
+
15
+ export const SessionId = S.String.check(S.isUUID()).pipe(S.brand("SessionId"))
16
+
17
+ export const Session = S.Struct({
18
+ id: SessionId,
19
+ trace: TraceEnvelope,
20
+ })
21
+
22
+ export const parent = Effect.currentParentSpan.pipe(Effect.catchTag("NoSuchElementError", () => Effect.undefined))
23
+
24
+ export const current = Effect.currentSpan.pipe(Effect.catchTag("NoSuchElementError", () => Effect.undefined))
25
+
26
+ export const currentTrace = current.pipe(Effect.map((span) => (span ? toTraceEnvelope(span) : undefined)))
27
+
28
+ export const sessionAttributes = ({ id }: typeof Session.Type) => ({ "liminal.session.id": id })
29
+
30
+ export const sessionLink = (session: typeof Session.Type, attributes: Record<string, unknown> = {}) => ({
31
+ span: Tracer.externalSpan(session.trace),
32
+ attributes: {
33
+ "liminal.link": "session",
34
+ ...sessionAttributes(session),
35
+ ...attributes,
36
+ },
37
+ })
@@ -1,19 +1,19 @@
1
1
  import { BrowserWorkerRunner } from "@effect/platform-browser"
2
- import { Cause, Effect, Exit, Layer, Option, Ref, Schema as S, Scope, Semaphore, Stream } from "effect"
2
+ import { Cause, Effect, Exit, Layer, Option, Ref, Schema as S, Scope, Semaphore, Stream, Tracer } from "effect"
3
3
  import { WorkerRunner } from "effect/unstable/workers"
4
4
  import { logCause } from "liminal-util/logCause"
5
+ import * as Spanner from "liminal-util/Spanner"
5
6
 
6
7
  import type { TopFromString } from "../_util/schema.ts"
7
8
  import type { Actor } from "../Actor.ts"
8
9
  import type { ActorTransport } from "../ActorTransport.ts"
9
- import type { ClientHandle } from "../ClientHandle.ts"
10
- import type { ProtocolDefinition } from "../Protocol.ts"
11
-
12
- import { diagnostic } from "../_diagnostic.ts"
13
10
  import * as ClientDirectory from "../ClientDirectory.ts"
11
+ import type { ClientHandle } from "../ClientHandle.ts"
14
12
  import * as Method from "../Method.ts"
13
+ import type { ProtocolDefinition } from "../Protocol.ts"
14
+ import * as Tracing from "../Tracing.ts"
15
15
 
16
- const { debug, span } = diagnostic("browser.BrowserActorNamespace")
16
+ const span = Spanner.make(import.meta.url)
17
17
 
18
18
  export interface Introduction<Name extends TopFromString, AttachmentFields extends S.Struct.Fields> {
19
19
  readonly port: MessagePort
@@ -29,8 +29,7 @@ export const make = Effect.fnUntraced(function* <
29
29
  ClientSelf,
30
30
  ClientId extends string,
31
31
  D extends ProtocolDefinition,
32
- const Handlers extends Method.Handlers<D["methods"], any>,
33
- A,
32
+ const Handlers extends Method.Handlers<D["external"], any>,
34
33
  E,
35
34
  R,
36
35
  IntroductionE,
@@ -38,12 +37,12 @@ export const make = Effect.fnUntraced(function* <
38
37
  >({
39
38
  actor,
40
39
  handlers,
41
- onConnect,
40
+ hydrate,
42
41
  introductions,
43
42
  }: {
44
43
  readonly actor: Actor<ActorSelf, ActorId, Name, AttachmentFields, ClientSelf, ClientId, D>
45
44
  readonly handlers: Handlers
46
- readonly onConnect: Effect.Effect<A, E, R>
45
+ readonly hydrate: Effect.Effect<S.Struct<D["state"]>["Type"], E, R>
47
46
  readonly introductions: Stream.Stream<Introduction<Name, AttachmentFields>, IntroductionE, IntroductionR>
48
47
  }) {
49
48
  const {
@@ -59,17 +58,34 @@ export const make = Effect.fnUntraced(function* <
59
58
  const validateClientMessage = S.decodeUnknownEffect(S.toType(ClientM))
60
59
  const encodeName = S.encodeEffect(Name)
61
60
 
61
+ interface BrowserClient {
62
+ readonly port: MessagePort
63
+
64
+ readonly backing: WorkerRunner.WorkerRunner<typeof Actor.Type, typeof ClientM.Type>
65
+
66
+ readonly close: Effect.Effect<void>
67
+ }
68
+
62
69
  interface Entry {
63
- readonly directory: ClientDirectory.ClientDirectory<MessagePort, ActorSelf, AttachmentFields, D>
70
+ readonly directory: ClientDirectory.ClientDirectory<MessagePort, BrowserClient, ActorSelf, AttachmentFields, D>
64
71
  readonly mutex: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
65
72
  }
66
73
 
67
74
  const entries: Record<string, Entry> = {}
68
- const runners = new Map<MessagePort, WorkerRunner.WorkerRunner<typeof Actor.Type, typeof ClientM.Type>>()
69
75
 
70
- const transport: ActorTransport<MessagePort, AttachmentFields, D> = {
71
- send: (port, event) => runners.get(port)?.send(0, event) ?? Effect.void,
72
- close: () => Effect.void,
76
+ const transport: ActorTransport<MessagePort, BrowserClient, AttachmentFields, D> = {
77
+ key: ({ port }) => port,
78
+ send: ({ backing }, event) => {
79
+ const { _tag } = event.event as never
80
+ return Effect.gen(function* () {
81
+ const trace = yield* Tracing.currentTrace
82
+ yield* backing.send(0, {
83
+ ...event,
84
+ ...(trace && { trace }),
85
+ })
86
+ }).pipe(span("send", { attributes: { _tag }, kind: "producer" }))
87
+ },
88
+ close: ({ close }) => close,
73
89
  snapshot: () => Effect.void,
74
90
  }
75
91
 
@@ -78,7 +94,7 @@ export const make = Effect.fnUntraced(function* <
78
94
  const getEntry = Effect.fnUntraced(function* (key: string) {
79
95
  const existing = entries[key]
80
96
  if (existing) return existing
81
- const directory = ClientDirectory.make(actor, transport)
97
+ const directory = ClientDirectory.make(actor, { transport })
82
98
  const semaphore = yield* Semaphore.make(1)
83
99
  const fresh = {
84
100
  directory,
@@ -93,8 +109,6 @@ export const make = Effect.fnUntraced(function* <
93
109
  yield* introductions.pipe(
94
110
  Stream.runForEach(
95
111
  Effect.fnUntraced(function* ({ name, port, attachments }) {
96
- yield* debug("IntroductionReceived", { name })
97
-
98
112
  const stateRef = yield* Ref.make<
99
113
  Option.Option<{
100
114
  readonly key: string
@@ -108,14 +122,12 @@ export const make = Effect.fnUntraced(function* <
108
122
  const closeScope = Scope.close(scope, Exit.void)
109
123
 
110
124
  const backing = yield* BrowserWorkerRunner.make(port).start<typeof Actor.Type, typeof ClientM.Type>()
111
- runners.set(port, backing)
112
125
 
113
126
  yield* Scope.addFinalizer(
114
127
  scope,
115
128
  Effect.gen(function* () {
116
- runners.delete(port)
117
129
  const state = yield* Ref.get(stateRef)
118
- if (Option.isSome(state)) {
130
+ if (state._tag === "Some") {
119
131
  const {
120
132
  key,
121
133
  entry: { directory },
@@ -134,8 +146,7 @@ export const make = Effect.fnUntraced(function* <
134
146
  const state = yield* Ref.get(stateRef)
135
147
  yield* Effect.gen(function* () {
136
148
  const message = yield* validateClientMessage(raw)
137
- yield* debug("MessageReceived", { message })
138
- if (Option.isNone(state)) {
149
+ if (state._tag === "None") {
139
150
  if (message._tag !== "Audition.Payload") {
140
151
  return yield* Effect.die(undefined)
141
152
  }
@@ -150,17 +161,23 @@ export const make = Effect.fnUntraced(function* <
150
161
  }
151
162
  const key = yield* encodeName(name)
152
163
  const entry = yield* getEntry(key)
153
- const currentClient = Object.assign(yield* entry.directory.register(port, attachments), {
154
- disconnect: closeScope,
155
- })
164
+ const currentClient = yield* entry.directory.register(
165
+ { port, backing, close: closeScope },
166
+ attachments,
167
+ )
156
168
  const ActorLive = Layer.succeed(actor, {
157
169
  name,
158
170
  clients: entry.directory.handles,
159
171
  currentClient,
160
172
  })
161
173
  yield* Ref.set(stateRef, Option.some({ key, entry, currentClient, ActorLive }))
162
- yield* backing.send(0, { _tag: "Audition.Success" })
163
- return yield* onConnect.pipe(entry.mutex, Effect.scoped, span("onConnect"), Effect.provide(ActorLive))
174
+ const initial = yield* hydrate.pipe(
175
+ entry.mutex,
176
+ Effect.scoped,
177
+ span("onConnect"),
178
+ Effect.provide(ActorLive),
179
+ )
180
+ return yield* backing.send(0, { _tag: "Audition.Success", initial })
164
181
  }
165
182
  const { entry, ActorLive } = state.value
166
183
  if (message._tag === "Audition.Payload") {
@@ -171,9 +188,11 @@ export const make = Effect.fnUntraced(function* <
171
188
  }
172
189
  const { id, payload } = message
173
190
  const { _tag, value } = payload as never
191
+ const parent = message.trace && Tracer.externalSpan(message.trace)
192
+ const transportSpan = yield* Tracing.parent
174
193
  yield* (
175
194
  handlers as Method.Handlers<
176
- D["methods"],
195
+ D["external"],
177
196
  Handlers[keyof Handlers] extends (v: never) => Effect.Effect<any, any, infer R> ? R : never
178
197
  >
179
198
  )[_tag]!(value).pipe(
@@ -190,7 +209,23 @@ export const make = Effect.fnUntraced(function* <
190
209
  }),
191
210
  }),
192
211
  Effect.andThen((v) => backing.send(0, v)),
193
- span("handler", { attributes: { _tag } }),
212
+ span("handle", {
213
+ attributes: { _tag },
214
+ kind: "server",
215
+ parent,
216
+ links:
217
+ parent && transportSpan
218
+ ? [
219
+ {
220
+ span: transportSpan,
221
+ attributes: {
222
+ "liminal.link": "transport",
223
+ "liminal.transport": "worker",
224
+ },
225
+ },
226
+ ]
227
+ : undefined,
228
+ }),
194
229
  Effect.scoped,
195
230
  Effect.provide(ActorLive),
196
231
  entry.mutex,