liminal 0.17.0 → 0.17.2

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 (288) hide show
  1. package/Accumulator.ts +103 -0
  2. package/Actor.ts +124 -0
  3. package/Audition.ts +110 -0
  4. package/CHANGELOG.md +4 -208
  5. package/Client.ts +525 -0
  6. package/ClientHandle.ts +50 -0
  7. package/F.ts +12 -0
  8. package/LICENSE +202 -0
  9. package/Method.ts +37 -0
  10. package/Protocol.ts +120 -0
  11. package/Send.ts +12 -0
  12. package/_constants.ts +1 -0
  13. package/_util/Diagnostic.ts +16 -0
  14. package/_util/Mutex.ts +13 -0
  15. package/_util/phantom.ts +1 -0
  16. package/dist/Accumulator.d.ts +22 -0
  17. package/dist/Accumulator.js +37 -0
  18. package/dist/Accumulator.js.map +1 -0
  19. package/dist/Actor.d.ts +29 -0
  20. package/dist/Actor.js +22 -0
  21. package/dist/Actor.js.map +1 -0
  22. package/dist/Audition.d.ts +17 -0
  23. package/dist/Audition.js +29 -0
  24. package/dist/Audition.js.map +1 -0
  25. package/dist/Client.d.ts +46 -0
  26. package/dist/Client.js +228 -0
  27. package/dist/Client.js.map +1 -0
  28. package/dist/ClientHandle.d.ts +17 -0
  29. package/dist/ClientHandle.js +10 -0
  30. package/dist/ClientHandle.js.map +1 -0
  31. package/dist/F.d.ts +4 -0
  32. package/dist/F.js +2 -0
  33. package/dist/F.js.map +1 -0
  34. package/dist/Method.d.ts +22 -0
  35. package/dist/Method.js +4 -0
  36. package/dist/Method.js.map +1 -0
  37. package/dist/Protocol.d.ts +53 -0
  38. package/dist/Protocol.js +37 -0
  39. package/dist/Protocol.js.map +1 -0
  40. package/dist/Send.d.ts +2 -0
  41. package/dist/Send.js +2 -0
  42. package/dist/Send.js.map +1 -0
  43. package/dist/_constants.d.ts +1 -0
  44. package/dist/_constants.js +2 -0
  45. package/dist/_constants.js.map +1 -0
  46. package/dist/_util/Diagnostic.d.ts +5 -0
  47. package/dist/_util/Diagnostic.js +10 -0
  48. package/dist/_util/Diagnostic.js.map +1 -0
  49. package/dist/_util/Mutex.d.ts +7 -0
  50. package/dist/_util/Mutex.js +9 -0
  51. package/dist/_util/Mutex.js.map +1 -0
  52. package/dist/_util/phantom.d.ts +3 -0
  53. package/dist/_util/phantom.js +2 -0
  54. package/dist/_util/phantom.js.map +1 -0
  55. package/dist/errors.d.ts +23 -0
  56. package/dist/errors.js +15 -0
  57. package/dist/errors.js.map +1 -0
  58. package/dist/experimental/BranchLive.d.ts +3 -0
  59. package/dist/experimental/BranchLive.js +5 -0
  60. package/dist/experimental/BranchLive.js.map +1 -0
  61. package/dist/experimental/L/L.d.ts +11 -0
  62. package/dist/experimental/L/L.js +12 -0
  63. package/dist/experimental/L/L.js.map +1 -0
  64. package/dist/experimental/L/append.d.ts +3 -0
  65. package/dist/experimental/L/append.js +4 -0
  66. package/dist/experimental/L/append.js.map +1 -0
  67. package/dist/experimental/L/assistant.d.ts +3 -0
  68. package/dist/experimental/L/assistant.js +13 -0
  69. package/dist/experimental/L/assistant.js.map +1 -0
  70. package/dist/experimental/L/assistantSchema.d.ts +3 -0
  71. package/dist/experimental/L/assistantSchema.js +13 -0
  72. package/dist/experimental/L/assistantSchema.js.map +1 -0
  73. package/dist/experimental/L/assistantStream.d.ts +3 -0
  74. package/dist/experimental/L/assistantStream.js +5 -0
  75. package/dist/experimental/L/assistantStream.js.map +1 -0
  76. package/dist/experimental/L/branch.d.ts +2 -0
  77. package/dist/experimental/L/branch.js +4 -0
  78. package/dist/experimental/L/branch.js.map +1 -0
  79. package/dist/experimental/L/clear.d.ts +3 -0
  80. package/dist/experimental/L/clear.js +7 -0
  81. package/dist/experimental/L/clear.js.map +1 -0
  82. package/dist/experimental/L/history.d.ts +3 -0
  83. package/dist/experimental/L/history.js +4 -0
  84. package/dist/experimental/L/history.js.map +1 -0
  85. package/dist/experimental/L/init.d.ts +3 -0
  86. package/dist/experimental/L/init.js +4 -0
  87. package/dist/experimental/L/init.js.map +1 -0
  88. package/dist/experimental/L/matrix.d.ts +10 -0
  89. package/dist/experimental/L/matrix.js +6 -0
  90. package/dist/experimental/L/matrix.js.map +1 -0
  91. package/dist/experimental/L/system.d.ts +1 -0
  92. package/dist/experimental/L/system.js +4 -0
  93. package/dist/experimental/L/system.js.map +1 -0
  94. package/dist/experimental/L/user.d.ts +1 -0
  95. package/dist/experimental/L/user.js +6 -0
  96. package/dist/experimental/L/user.js.map +1 -0
  97. package/dist/experimental/Loader.d.ts +16 -0
  98. package/dist/experimental/Loader.js +20 -0
  99. package/dist/experimental/Loader.js.map +1 -0
  100. package/dist/experimental/TaggedTemplateFunction.d.ts +11 -0
  101. package/dist/experimental/TaggedTemplateFunction.js +14 -0
  102. package/dist/experimental/TaggedTemplateFunction.js.map +1 -0
  103. package/dist/experimental/Template.d.ts +28 -0
  104. package/dist/experimental/Template.js +60 -0
  105. package/dist/experimental/Template.js.map +1 -0
  106. package/dist/experimental/index.d.ts +4 -0
  107. package/dist/experimental/index.js +5 -0
  108. package/dist/experimental/index.js.map +1 -0
  109. package/dist/index.d.ts +9 -7
  110. package/dist/index.js +9 -7
  111. package/dist/index.js.map +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/errors.ts +22 -0
  114. package/experimental/BranchLive.ts +6 -0
  115. package/experimental/L/L.ts +11 -0
  116. package/experimental/L/append.ts +12 -0
  117. package/experimental/L/assistant.ts +16 -0
  118. package/experimental/L/assistantSchema.ts +18 -0
  119. package/experimental/L/assistantStream.ts +9 -0
  120. package/experimental/L/branch.ts +5 -0
  121. package/experimental/L/clear.ts +7 -0
  122. package/experimental/L/history.ts +4 -0
  123. package/experimental/L/init.ts +4 -0
  124. package/experimental/L/matrix.ts +26 -0
  125. package/experimental/L/system.ts +5 -0
  126. package/experimental/L/user.ts +10 -0
  127. package/experimental/Loader.ts +35 -0
  128. package/experimental/TaggedTemplateFunction.ts +44 -0
  129. package/experimental/Template.ts +85 -0
  130. package/experimental/index.ts +4 -0
  131. package/index.ts +9 -7
  132. package/package.json +17 -30
  133. package/tsconfig.json +9 -0
  134. package/Digest.ts +0 -30
  135. package/Envelope.ts +0 -16
  136. package/F/F.ts +0 -1
  137. package/L/L.ts +0 -20
  138. package/L/append.ts +0 -14
  139. package/L/assistant.ts +0 -30
  140. package/L/assistantSchema.ts +0 -61
  141. package/L/assistantStream.ts +0 -22
  142. package/L/branch.ts +0 -20
  143. package/L/clear.ts +0 -14
  144. package/L/disable.ts +0 -13
  145. package/L/enable.ts +0 -24
  146. package/L/events.ts +0 -11
  147. package/L/json.ts +0 -12
  148. package/L/line.ts +0 -22
  149. package/L/listen.ts +0 -26
  150. package/L/messages.ts +0 -10
  151. package/L/prev.ts +0 -4
  152. package/L/provide.ts +0 -10
  153. package/L/self.ts +0 -5
  154. package/L/send.ts +0 -56
  155. package/L/system.ts +0 -25
  156. package/L/thread.ts +0 -16
  157. package/L/toolkit.ts +0 -14
  158. package/L/user.test.ts +0 -33
  159. package/L/user.ts +0 -32
  160. package/LEvent.ts +0 -33
  161. package/Thread.ts +0 -67
  162. package/dist/Digest.d.ts +0 -17
  163. package/dist/Digest.js +0 -7
  164. package/dist/Digest.js.map +0 -1
  165. package/dist/Envelope.d.ts +0 -13
  166. package/dist/Envelope.js +0 -2
  167. package/dist/Envelope.js.map +0 -1
  168. package/dist/F/F.d.ts +0 -1
  169. package/dist/F/F.js +0 -2
  170. package/dist/F/F.js.map +0 -1
  171. package/dist/L/L.d.ts +0 -20
  172. package/dist/L/L.js +0 -21
  173. package/dist/L/L.js.map +0 -1
  174. package/dist/L/append.d.ts +0 -5
  175. package/dist/L/append.js +0 -10
  176. package/dist/L/append.js.map +0 -1
  177. package/dist/L/assistant.d.ts +0 -6
  178. package/dist/L/assistant.js +0 -26
  179. package/dist/L/assistant.js.map +0 -1
  180. package/dist/L/assistantSchema.d.ts +0 -13
  181. package/dist/L/assistantSchema.js +0 -44
  182. package/dist/L/assistantSchema.js.map +0 -1
  183. package/dist/L/assistantStream.d.ts +0 -7
  184. package/dist/L/assistantStream.js +0 -17
  185. package/dist/L/assistantStream.js.map +0 -1
  186. package/dist/L/branch.d.ts +0 -3
  187. package/dist/L/branch.js +0 -19
  188. package/dist/L/branch.js.map +0 -1
  189. package/dist/L/clear.d.ts +0 -5
  190. package/dist/L/clear.js +0 -12
  191. package/dist/L/clear.js.map +0 -1
  192. package/dist/L/disable.d.ts +0 -4
  193. package/dist/L/disable.js +0 -10
  194. package/dist/L/disable.js.map +0 -1
  195. package/dist/L/enable.d.ts +0 -5
  196. package/dist/L/enable.js +0 -13
  197. package/dist/L/enable.js.map +0 -1
  198. package/dist/L/events.d.ts +0 -5
  199. package/dist/L/events.js +0 -6
  200. package/dist/L/events.js.map +0 -1
  201. package/dist/L/json.d.ts +0 -5
  202. package/dist/L/json.js +0 -8
  203. package/dist/L/json.js.map +0 -1
  204. package/dist/L/line.d.ts +0 -5
  205. package/dist/L/line.js +0 -11
  206. package/dist/L/line.js.map +0 -1
  207. package/dist/L/listen.d.ts +0 -7
  208. package/dist/L/listen.js +0 -14
  209. package/dist/L/listen.js.map +0 -1
  210. package/dist/L/messages.d.ts +0 -5
  211. package/dist/L/messages.js +0 -5
  212. package/dist/L/messages.js.map +0 -1
  213. package/dist/L/prev.d.ts +0 -5
  214. package/dist/L/prev.js +0 -5
  215. package/dist/L/prev.js.map +0 -1
  216. package/dist/L/provide.d.ts +0 -3
  217. package/dist/L/provide.js +0 -4
  218. package/dist/L/provide.js.map +0 -1
  219. package/dist/L/self.d.ts +0 -3
  220. package/dist/L/self.js +0 -4
  221. package/dist/L/self.js.map +0 -1
  222. package/dist/L/send.d.ts +0 -6
  223. package/dist/L/send.js +0 -34
  224. package/dist/L/send.js.map +0 -1
  225. package/dist/L/system.d.ts +0 -5
  226. package/dist/L/system.js +0 -14
  227. package/dist/L/system.js.map +0 -1
  228. package/dist/L/thread.d.ts +0 -3
  229. package/dist/L/thread.js +0 -15
  230. package/dist/L/thread.js.map +0 -1
  231. package/dist/L/toolkit.d.ts +0 -4
  232. package/dist/L/toolkit.js +0 -9
  233. package/dist/L/toolkit.js.map +0 -1
  234. package/dist/L/user.d.ts +0 -6
  235. package/dist/L/user.js +0 -13
  236. package/dist/L/user.js.map +0 -1
  237. package/dist/L/user.test.d.ts +0 -1
  238. package/dist/L/user.test.js.map +0 -1
  239. package/dist/LEvent.d.ts +0 -154
  240. package/dist/LEvent.js +0 -19
  241. package/dist/LEvent.js.map +0 -1
  242. package/dist/Thread.d.ts +0 -52
  243. package/dist/Thread.js +0 -32
  244. package/dist/Thread.js.map +0 -1
  245. package/dist/patterns/Debate.d.ts +0 -1
  246. package/dist/patterns/Debate.js +0 -3
  247. package/dist/patterns/Debate.js.map +0 -1
  248. package/dist/patterns/Model.d.ts +0 -6
  249. package/dist/patterns/Model.js +0 -14
  250. package/dist/patterns/Model.js.map +0 -1
  251. package/dist/patterns/Route.d.ts +0 -3
  252. package/dist/patterns/Route.js +0 -14
  253. package/dist/patterns/Route.js.map +0 -1
  254. package/dist/util/JsonValue.d.ts +0 -8
  255. package/dist/util/JsonValue.js +0 -109
  256. package/dist/util/JsonValue.js.map +0 -1
  257. package/dist/util/NeverTool.d.ts +0 -3
  258. package/dist/util/NeverTool.js +0 -3
  259. package/dist/util/NeverTool.js.map +0 -1
  260. package/dist/util/Taggable.d.ts +0 -6
  261. package/dist/util/Taggable.js +0 -13
  262. package/dist/util/Taggable.js.map +0 -1
  263. package/dist/util/extract.d.ts +0 -3
  264. package/dist/util/extract.js +0 -2
  265. package/dist/util/extract.js.map +0 -1
  266. package/dist/util/messageCodec.d.ts +0 -98
  267. package/dist/util/messageCodec.js +0 -5
  268. package/dist/util/messageCodec.js.map +0 -1
  269. package/dist/util/normalizeRaw.d.ts +0 -4
  270. package/dist/util/normalizeRaw.js +0 -75
  271. package/dist/util/normalizeRaw.js.map +0 -1
  272. package/dist/util/prefix.d.ts +0 -1
  273. package/dist/util/prefix.js +0 -2
  274. package/dist/util/prefix.js.map +0 -1
  275. package/dist/util/raw.d.ts +0 -6
  276. package/dist/util/raw.js +0 -5
  277. package/dist/util/raw.js.map +0 -1
  278. package/patterns/Debate.ts +0 -2
  279. package/patterns/Model.ts +0 -29
  280. package/patterns/Route.ts +0 -25
  281. package/util/JsonValue.ts +0 -144
  282. package/util/NeverTool.ts +0 -10
  283. package/util/Taggable.ts +0 -33
  284. package/util/extract.ts +0 -7
  285. package/util/messageCodec.ts +0 -5
  286. package/util/normalizeRaw.ts +0 -93
  287. package/util/prefix.ts +0 -1
  288. package/util/raw.ts +0 -27
package/Client.ts ADDED
@@ -0,0 +1,525 @@
1
+ import {
2
+ Context,
3
+ Encoding,
4
+ Deferred,
5
+ Effect,
6
+ Layer,
7
+ Option,
8
+ PubSub,
9
+ RcRef,
10
+ Record,
11
+ pipe,
12
+ Ref,
13
+ Scope,
14
+ Stream,
15
+ Take,
16
+ Schema as S,
17
+ Array,
18
+ Struct,
19
+ Fiber,
20
+ Exit,
21
+ Cause,
22
+ Result,
23
+ flow,
24
+ identity,
25
+ } from "effect"
26
+ import { Socket } from "effect/unstable/socket"
27
+ import { Worker } from "effect/unstable/workers"
28
+
29
+ import type { MethodDefinition } from "./Method.ts"
30
+
31
+ import * as Diagnostic from "./_util/Diagnostic.ts"
32
+ import { type ClientError, AuditionError, ConnectionError, type FError, UnresolvedError } from "./errors.ts"
33
+ import { type F } from "./F.ts"
34
+ import * as Protocol from "./Protocol.ts"
35
+
36
+ const { debug, span } = Diagnostic.module("Client")
37
+
38
+ export const TypeId = "~liminal/Client" as const
39
+
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
+ export interface ReplayConfig {
50
+ readonly mode: "startup" | "all-subscribers"
51
+
52
+ readonly limit?: number | undefined
53
+ }
54
+
55
+ interface EventTake<A, E> {
56
+ readonly seq: number
57
+
58
+ readonly take: Take.Take<A, E>
59
+ }
60
+
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>
67
+
68
+ readonly f: F<ClientSelf, MethodDefinitions>
69
+
70
+ readonly end: Effect.Effect<void>
71
+ }
72
+
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>
78
+
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>>
86
+
87
+ readonly [TypeId]: typeof TypeId
88
+
89
+ readonly definition: ClientDefinition<MethodDefinitions, EventDefinitions>
90
+
91
+ readonly schema: Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>
92
+
93
+ readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<EventDefinitions>>["Type"], ClientError, ClientSelf>
94
+
95
+ readonly f: F<ClientSelf, MethodDefinitions>
96
+
97
+ readonly invalidate: Effect.Effect<void, never, ClientSelf>
98
+ }
99
+
100
+ 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)
111
+
112
+ const events = tag.asEffect().pipe(Effect.flatMap(RcRef.get), Effect.map(Struct.get("events")), Stream.unwrap)
113
+
114
+ const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
115
+ Effect.fnUntraced(function* (value) {
116
+ const { f } = yield* tag.asEffect().pipe(Effect.flatMap(RcRef.get))
117
+ return yield* f(_tag)(value)
118
+ }, Effect.scoped)
119
+
120
+ const invalidate = tag.asEffect().pipe(
121
+ Effect.flatMap((rc) =>
122
+ RcRef.get(rc).pipe(
123
+ Effect.flatMap(({ end }) => end),
124
+ Effect.andThen(RcRef.invalidate(rc)),
125
+ ),
126
+ ),
127
+ Effect.scoped,
128
+ Effect.ignore,
129
+ )
130
+
131
+ return Object.assign(tag, {
132
+ [TypeId]: TypeId,
133
+ definition,
134
+ schema: Protocol.ProtocolSchemas(definition.methods, definition.events),
135
+ events,
136
+ f,
137
+ invalidate,
138
+ })
139
+ }
140
+
141
+ export interface Transport<
142
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
143
+ EventDefinitions extends Record<string, S.Struct.Fields>,
144
+ > {
145
+ 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
+ >
156
+
157
+ 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
+ >
164
+ }
165
+
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>,
175
+ replay?: ReplayConfig | undefined,
176
+ ) =>
177
+ Effect.gen(function* () {
178
+ const rcr: RcRef.RcRef<Session<ClientSelf, MethodDefinitions, EventDefinitions>, ClientError> = yield* RcRef.make({
179
+ acquire: Effect.gen(function* () {
180
+ type _ = typeof client.schema
181
+ type Event = ReturnType<typeof S.TaggedUnion<EventDefinitions>>["Type"]
182
+
183
+ yield* debug("AcquisitionStarted")
184
+
185
+ const { listen, send } = yield* build
186
+
187
+ const audition = yield* Deferred.make<void>()
188
+
189
+ const inflights: Record<string, Deferred.Deferred<_["f"]["success"]["Type"], FError<MethodDefinitions>>> = {}
190
+ let callId = 0
191
+ let takeCount = 0
192
+ const pubsub = yield* PubSub.unbounded<EventTake<Event, ClientError>>()
193
+
194
+ const replayState = yield* Ref.make<{
195
+ readonly startupOpen: boolean
196
+ readonly buffer: ReadonlyArray<EventTake<Event, ClientError>>
197
+ }>({
198
+ startupOpen: true,
199
+ buffer: [],
200
+ })
201
+
202
+ const publishTake = (take: Take.Take<Event, ClientError>, replayable?: boolean | undefined) =>
203
+ Effect.gen(function* () {
204
+ const eventTake: EventTake<Event, ClientError> = {
205
+ seq: takeCount++,
206
+ take,
207
+ }
208
+ if (replay && replayable) {
209
+ yield* Ref.update(replayState, (state) => {
210
+ if (replay.mode === "startup" && !state.startupOpen) {
211
+ return state
212
+ }
213
+ const buffer =
214
+ replay.limit === undefined
215
+ ? [...state.buffer, eventTake]
216
+ : [...(state.buffer.length >= replay.limit ? state.buffer.slice(1) : state.buffer), eventTake]
217
+ const { startupOpen } = state
218
+ return { startupOpen, buffer }
219
+ })
220
+ }
221
+ yield* PubSub.publish(pubsub, eventTake)
222
+ })
223
+
224
+ const outer = yield* Scope.Scope
225
+ const scope = yield* Scope.fork(outer, "sequential")
226
+ const end = Scope.close(scope, Exit.void)
227
+
228
+ const fiber = yield* listen(
229
+ Effect.fnUntraced(function* (message) {
230
+ switch (message._tag) {
231
+ case "AuditionSuccess": {
232
+ yield* debug("AuditionSucceeded")
233
+ yield* Deferred.succeed(audition, void 0)
234
+ return
235
+ }
236
+ case "Event": {
237
+ const { event } = message
238
+ yield* debug("EventEmitted", { event })
239
+ yield* publishTake([event as never], true)
240
+ return
241
+ }
242
+ case "FSuccess":
243
+ case "FFailure": {
244
+ const { id } = message
245
+ const deferred = inflights[id]
246
+ if (deferred) {
247
+ delete inflights[id]
248
+ switch (message._tag) {
249
+ case "FSuccess": {
250
+ const { _tag, value } = message.success as never
251
+ yield* debug("CallSucceeded", { id, _tag, value })
252
+ yield* Deferred.succeed(deferred, value)
253
+ return
254
+ }
255
+ case "FFailure": {
256
+ const { _tag, value } = message.failure as never
257
+ yield* debug("CallFailed", { id, _tag, value })
258
+ yield* Deferred.fail(deferred, value)
259
+ return
260
+ }
261
+ }
262
+ }
263
+ return
264
+ }
265
+ case "AuditionFailure": {
266
+ const { client, routed } = message
267
+ yield* debug("AuditionFailed", { client, routed })
268
+ return yield* new AuditionError({ value: { client, routed } })
269
+ }
270
+ case "Disconnect": {
271
+ yield* debug("Disconnected")
272
+ return
273
+ }
274
+ case "TransportFailure": {
275
+ const { cause } = message
276
+ yield* debug("TransportFailed", { cause })
277
+ return yield* new ConnectionError({ cause })
278
+ }
279
+ }
280
+ }),
281
+ ).pipe(
282
+ Effect.ensuring(
283
+ Effect.all(
284
+ [
285
+ debug("ClientClosed", { unresolved: Record.keys(inflights).length }),
286
+ Deferred.succeed(audition, void 0),
287
+ RcRef.invalidate(rcr),
288
+ ],
289
+ { concurrency: "unbounded" },
290
+ ),
291
+ ),
292
+ Effect.forkScoped,
293
+ Effect.provideService(Scope.Scope, scope),
294
+ )
295
+
296
+ const events = Effect.gen(function* () {
297
+ const queue = yield* PubSub.subscribe(pubsub)
298
+ const live = (replayCount: number) =>
299
+ Stream.fromSubscription(queue).pipe(
300
+ Stream.filter((entry) => entry.seq > replayCount),
301
+ Stream.map((entry) => entry.take),
302
+ Stream.flattenTake,
303
+ )
304
+ if (!replay) {
305
+ return live(-1)
306
+ }
307
+ const buffer =
308
+ replay.mode === "all-subscribers"
309
+ ? (yield* Ref.get(replayState)).buffer
310
+ : yield* Ref.modify(replayState, (state) =>
311
+ state.startupOpen
312
+ ? [
313
+ state.buffer,
314
+ {
315
+ startupOpen: false,
316
+ buffer: [],
317
+ },
318
+ ]
319
+ : [[], state],
320
+ )
321
+ const replayCount = Array.get(buffer, buffer.length - 1).pipe(
322
+ Option.map(({ seq }) => seq),
323
+ Option.getOrElse(() => -1),
324
+ )
325
+ return buffer.length === 0
326
+ ? live(replayCount)
327
+ : Stream.concat(
328
+ Stream.fromIterable(buffer).pipe(
329
+ Stream.map((entry) => entry.take),
330
+ Stream.flattenTake,
331
+ ),
332
+ live(replayCount),
333
+ )
334
+ }).pipe(Stream.unwrap, Stream.interruptWhen(Fiber.join(fiber)))
335
+
336
+ yield* Deferred.await(audition)
337
+
338
+ const encodingServices = yield* Effect.context<_["f"]["payload"]["EncodingServices"]>()
339
+
340
+ const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
341
+ Effect.fnUntraced(
342
+ function* (value) {
343
+ const exit = fiber.pollUnsafe()
344
+ if (exit) {
345
+ return yield* Exit.match(exit, {
346
+ onSuccess: () => new UnresolvedError(),
347
+ onFailure: flow(
348
+ Cause.findError,
349
+ Result.match({
350
+ onSuccess: identity,
351
+ onFailure: () => new UnresolvedError(),
352
+ }),
353
+ ),
354
+ })
355
+ }
356
+ const id = callId++
357
+ const inflight = yield* Deferred.make<_["f"]["success"]["Type"], FError<MethodDefinitions>>()
358
+ inflights[id] = inflight
359
+ yield* send({
360
+ _tag: "FPayload",
361
+ id,
362
+ payload: { _tag, value } as never,
363
+ })
364
+ return yield* Effect.raceFirst(
365
+ Deferred.await(inflight),
366
+ Fiber.join(fiber).pipe(Effect.andThen(() => new UnresolvedError().asEffect())),
367
+ )
368
+ },
369
+ span("f"),
370
+ Effect.scoped,
371
+ Effect.provide(encodingServices),
372
+ )
373
+
374
+ return { events, f, end }
375
+ }).pipe(span("acquire", { attributes: { client: client.key } }), Effect.annotateLogs("client", client.key)),
376
+ })
377
+
378
+ return rcr
379
+ }).pipe(Layer.effect(client))
380
+
381
+ export const layerSocket = <
382
+ ClientSelf,
383
+ ClientId extends string,
384
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
385
+ EventDefinitions extends Record<string, S.Struct.Fields>,
386
+ >({
387
+ client,
388
+ url,
389
+ protocols,
390
+ replay,
391
+ }: {
392
+ readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
393
+ readonly url?: string | undefined
394
+ readonly protocols?: string | Array<string> | undefined
395
+ readonly replay?: ReplayConfig | undefined
396
+ }): Layer.Layer<
397
+ ClientSelf,
398
+ never,
399
+ | Socket.WebSocketConstructor
400
+ | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["DecodingServices"]
401
+ | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["EncodingServices"]
402
+ > =>
403
+ make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Socket.WebSocketConstructor>(
404
+ client,
405
+ Effect.gen(function* () {
406
+ const socket = yield* Socket.makeWebSocket(url ?? "/", {
407
+ protocols: ["liminal", Encoding.encodeBase64Url(client.key), ...(protocols ? Array.ensure(protocols) : [])],
408
+ })
409
+ return {
410
+ listen: Effect.fnUntraced(function* (publish) {
411
+ yield* socket
412
+ .runRaw((raw) =>
413
+ pipe(
414
+ raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw,
415
+ S.decodeUnknownEffect(S.fromJsonString(client.schema.actor)),
416
+ Effect.andThen(publish),
417
+ ),
418
+ )
419
+ .pipe(
420
+ Effect.catchTag(
421
+ "SocketError",
422
+ Effect.fnUntraced(function* (cause) {
423
+ const { reason } = cause
424
+ switch (reason._tag) {
425
+ case "SocketReadError":
426
+ case "SocketWriteError":
427
+ case "SocketOpenError": {
428
+ yield* debug(reason._tag, { cause })
429
+ return yield* publish({ _tag: "TransportFailure", cause })
430
+ }
431
+ case "SocketCloseError": {
432
+ const { code, closeReason } = reason
433
+ switch (code) {
434
+ case 1000: {
435
+ return yield* publish({ _tag: "Disconnect" })
436
+ }
437
+ case 4003: {
438
+ return yield* S.decodeUnknownEffect(S.fromJsonString(Protocol.AuditionFailure))(
439
+ closeReason,
440
+ ).pipe(Effect.andThen(publish))
441
+ }
442
+ }
443
+ yield* debug("SocketCloseError", { cause })
444
+ return yield* publish({ _tag: "TransportFailure", cause })
445
+ }
446
+ }
447
+ }),
448
+ ),
449
+ Effect.catchTag("SchemaError", Effect.die),
450
+ )
451
+ }, span("listen")),
452
+ send: Effect.fnUntraced(
453
+ function* (v) {
454
+ const write = yield* socket.writer
455
+ const message = yield* S.encodeEffect(S.fromJsonString(client.schema.f.payload))(v).pipe(
456
+ Effect.mapError((cause) => new ConnectionError({ cause })),
457
+ )
458
+ yield* write(message).pipe(
459
+ Effect.catchTag("SocketError", (cause) => new ConnectionError({ cause }).asEffect()),
460
+ )
461
+ },
462
+ span("send"),
463
+ Effect.scoped,
464
+ ),
465
+ }
466
+ }),
467
+ replay,
468
+ )
469
+
470
+ export const layerWorker = <
471
+ ClientSelf,
472
+ ClientId extends string,
473
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
474
+ EventDefinitions extends Record<string, S.Struct.Fields>,
475
+ >({
476
+ client,
477
+ replay,
478
+ }: {
479
+ readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
480
+ readonly replay?: ReplayConfig | undefined
481
+ }): Layer.Layer<
482
+ ClientSelf,
483
+ never,
484
+ | Worker.WorkerPlatform
485
+ | Worker.Spawner
486
+ | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["actor"]["DecodingServices"]
487
+ | Protocol.ProtocolSchemas<MethodDefinitions, EventDefinitions>["f"]["payload"]["EncodingServices"]
488
+ > =>
489
+ make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Worker.WorkerPlatform | Worker.Spawner>(
490
+ client,
491
+ Effect.gen(function* () {
492
+ type T = typeof client.schema
493
+
494
+ const platform = yield* Worker.WorkerPlatform
495
+ const backing = yield* platform
496
+ .spawn<T["actor"]["Type"], T["f"]["payload"]["Type"] | string>(0)
497
+ .pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()))
498
+
499
+ const send = (message: T["f"]["payload"]["Type"]) =>
500
+ backing.send(message).pipe(
501
+ Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()),
502
+ span("send"),
503
+ )
504
+
505
+ return {
506
+ listen: Effect.fnUntraced(function* (publish) {
507
+ const stop = yield* Deferred.make<void>()
508
+ yield* Effect.raceFirst(
509
+ backing.run(
510
+ Effect.fnUntraced(function* (message) {
511
+ yield* publish(message)
512
+ if (message._tag === "Disconnect" || message._tag === "AuditionFailure") {
513
+ yield* Deferred.succeed(stop, void 0)
514
+ }
515
+ }),
516
+ { onSpawn: backing.send(client.key).pipe(Effect.orDie) },
517
+ ),
518
+ Deferred.await(stop),
519
+ ).pipe(Effect.catchTag("WorkerError", (cause) => publish({ _tag: "TransportFailure", cause })))
520
+ }, span("listen")),
521
+ send,
522
+ }
523
+ }),
524
+ replay,
525
+ )
@@ -0,0 +1,50 @@
1
+ import { Schema as S, Effect } from "effect"
2
+
3
+ import type { Send } from "./Send.ts"
4
+
5
+ const TypeId = "~liminal/ClientHandle" as const
6
+
7
+ export interface ClientHandle<
8
+ ActorSelf,
9
+ AttachmentFields extends S.Struct.Fields,
10
+ EventDefinitions extends Record<string, S.Struct.Fields>,
11
+ > {
12
+ readonly [TypeId]: typeof TypeId
13
+
14
+ readonly send: Send<ActorSelf, EventDefinitions>
15
+
16
+ readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
17
+
18
+ readonly save: (
19
+ attachments: S.Struct<AttachmentFields>["Type"],
20
+ ) => Effect.Effect<void, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
21
+
22
+ readonly disconnect: Effect.Effect<void, never, ActorSelf>
23
+ }
24
+
25
+ export const make = <
26
+ ActorSelf,
27
+ AttachmentFields extends S.Struct.Fields,
28
+ EventDefinitions extends Record<string, S.Struct.Fields>,
29
+ >({
30
+ send,
31
+ attachments,
32
+ save,
33
+ disconnect,
34
+ }: {
35
+ readonly send: Send<ActorSelf, EventDefinitions>
36
+
37
+ readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
38
+
39
+ readonly save: (
40
+ attachments: S.Struct<AttachmentFields>["Type"],
41
+ ) => Effect.Effect<void, S.SchemaError, S.Struct<AttachmentFields>["EncodingServices"]>
42
+
43
+ readonly disconnect: Effect.Effect<void, never, ActorSelf>
44
+ }): ClientHandle<ActorSelf, AttachmentFields, EventDefinitions> => ({
45
+ [TypeId]: TypeId,
46
+ send,
47
+ attachments,
48
+ save,
49
+ disconnect,
50
+ })
package/F.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { Record, Effect } from "effect"
2
+
3
+ import type { FError } from "./errors.ts"
4
+ import type { MethodDefinition } from "./Method.ts"
5
+
6
+ export type F<ClientSelf, MethodDefinitions extends Record<string, MethodDefinition.Any>> = <
7
+ Method extends keyof MethodDefinitions,
8
+ >(
9
+ method: Method,
10
+ ) => (
11
+ payload: MethodDefinitions[Method]["payload"]["Type"],
12
+ ) => Effect.Effect<MethodDefinitions[Method]["success"]["Type"], FError<MethodDefinitions>, ClientSelf>