liminal 0.16.0 → 0.17.1

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 (275) hide show
  1. package/Accumulator.ts +97 -0
  2. package/Actor.ts +118 -0
  3. package/Audition.ts +105 -0
  4. package/CHANGELOG.md +2 -206
  5. package/Client.ts +560 -0
  6. package/ClientHandle.ts +39 -0
  7. package/F.ts +18 -0
  8. package/Method.ts +39 -0
  9. package/Protocol.ts +142 -0
  10. package/Send.ts +8 -0
  11. package/_constants.ts +1 -0
  12. package/_types.ts +27 -0
  13. package/_util/Mutex.ts +13 -0
  14. package/_util/phantom.ts +1 -0
  15. package/dist/Accumulator.d.ts +22 -0
  16. package/dist/Accumulator.js +33 -0
  17. package/dist/Accumulator.js.map +1 -0
  18. package/dist/Actor.d.ts +30 -0
  19. package/dist/Actor.js +31 -0
  20. package/dist/Actor.js.map +1 -0
  21. package/dist/Audition.d.ts +18 -0
  22. package/dist/Audition.js +27 -0
  23. package/dist/Audition.js.map +1 -0
  24. package/dist/Client.d.ts +65 -0
  25. package/dist/Client.js +248 -0
  26. package/dist/Client.js.map +1 -0
  27. package/dist/ClientHandle.d.ts +18 -0
  28. package/dist/ClientHandle.js +10 -0
  29. package/dist/ClientHandle.js.map +1 -0
  30. package/dist/F.d.ts +13 -0
  31. package/dist/F.js +4 -0
  32. package/dist/F.js.map +1 -0
  33. package/dist/Method.d.ts +23 -0
  34. package/dist/Method.js +4 -0
  35. package/dist/Method.js.map +1 -0
  36. package/dist/Protocol.d.ts +96 -0
  37. package/dist/Protocol.js +13 -0
  38. package/dist/Protocol.js.map +1 -0
  39. package/dist/Send.d.ts +3 -0
  40. package/dist/Send.js +2 -0
  41. package/dist/Send.js.map +1 -0
  42. package/dist/_constants.d.ts +1 -0
  43. package/dist/_constants.js +2 -0
  44. package/dist/_constants.js.map +1 -0
  45. package/dist/_types.d.ts +22 -0
  46. package/dist/_types.js +2 -0
  47. package/dist/_types.js.map +1 -0
  48. package/dist/_util/Mutex.d.ts +7 -0
  49. package/dist/_util/Mutex.js +9 -0
  50. package/dist/_util/Mutex.js.map +1 -0
  51. package/dist/_util/phantom.d.ts +3 -0
  52. package/dist/_util/phantom.js +2 -0
  53. package/dist/_util/phantom.js.map +1 -0
  54. package/dist/errors.d.ts +20 -0
  55. package/dist/errors.js +13 -0
  56. package/dist/errors.js.map +1 -0
  57. package/dist/experimental/BranchLive.d.ts +3 -0
  58. package/dist/experimental/BranchLive.js +5 -0
  59. package/dist/experimental/BranchLive.js.map +1 -0
  60. package/dist/experimental/L/L.d.ts +11 -0
  61. package/dist/experimental/L/L.js +12 -0
  62. package/dist/experimental/L/L.js.map +1 -0
  63. package/dist/experimental/L/append.d.ts +3 -0
  64. package/dist/experimental/L/append.js +4 -0
  65. package/dist/experimental/L/append.js.map +1 -0
  66. package/dist/experimental/L/assistant.d.ts +3 -0
  67. package/dist/experimental/L/assistant.js +13 -0
  68. package/dist/experimental/L/assistant.js.map +1 -0
  69. package/dist/experimental/L/assistantSchema.d.ts +3 -0
  70. package/dist/experimental/L/assistantSchema.js +13 -0
  71. package/dist/experimental/L/assistantSchema.js.map +1 -0
  72. package/dist/experimental/L/assistantStream.d.ts +3 -0
  73. package/dist/experimental/L/assistantStream.js +5 -0
  74. package/dist/experimental/L/assistantStream.js.map +1 -0
  75. package/dist/experimental/L/branch.d.ts +2 -0
  76. package/dist/experimental/L/branch.js +4 -0
  77. package/dist/experimental/L/branch.js.map +1 -0
  78. package/dist/experimental/L/clear.d.ts +3 -0
  79. package/dist/experimental/L/clear.js +7 -0
  80. package/dist/experimental/L/clear.js.map +1 -0
  81. package/dist/experimental/L/history.d.ts +3 -0
  82. package/dist/experimental/L/history.js +4 -0
  83. package/dist/experimental/L/history.js.map +1 -0
  84. package/dist/experimental/L/init.d.ts +3 -0
  85. package/dist/experimental/L/init.js +4 -0
  86. package/dist/experimental/L/init.js.map +1 -0
  87. package/dist/experimental/L/matrix.d.ts +10 -0
  88. package/dist/experimental/L/matrix.js +6 -0
  89. package/dist/experimental/L/matrix.js.map +1 -0
  90. package/dist/experimental/L/system.d.ts +1 -0
  91. package/dist/experimental/L/system.js +4 -0
  92. package/dist/experimental/L/system.js.map +1 -0
  93. package/dist/experimental/L/user.d.ts +1 -0
  94. package/dist/experimental/L/user.js +6 -0
  95. package/dist/experimental/L/user.js.map +1 -0
  96. package/dist/experimental/Loader.d.ts +16 -0
  97. package/dist/experimental/Loader.js +20 -0
  98. package/dist/experimental/Loader.js.map +1 -0
  99. package/dist/experimental/TaggedTemplateFunction.d.ts +11 -0
  100. package/dist/experimental/TaggedTemplateFunction.js +14 -0
  101. package/dist/experimental/TaggedTemplateFunction.js.map +1 -0
  102. package/dist/experimental/Template.d.ts +32 -0
  103. package/dist/experimental/Template.js +58 -0
  104. package/dist/experimental/Template.js.map +1 -0
  105. package/dist/experimental/index.d.ts +4 -0
  106. package/dist/experimental/index.js +5 -0
  107. package/dist/experimental/index.js.map +1 -0
  108. package/dist/index.d.ts +9 -5
  109. package/dist/index.js +9 -5
  110. package/dist/index.js.map +1 -1
  111. package/dist/tsconfig.tsbuildinfo +1 -1
  112. package/errors.ts +14 -0
  113. package/experimental/BranchLive.ts +6 -0
  114. package/experimental/L/L.ts +11 -0
  115. package/experimental/L/append.ts +12 -0
  116. package/experimental/L/assistant.ts +16 -0
  117. package/experimental/L/assistantSchema.ts +18 -0
  118. package/experimental/L/assistantStream.ts +9 -0
  119. package/experimental/L/branch.ts +5 -0
  120. package/experimental/L/clear.ts +7 -0
  121. package/experimental/L/history.ts +4 -0
  122. package/experimental/L/init.ts +4 -0
  123. package/experimental/L/matrix.ts +26 -0
  124. package/experimental/L/system.ts +5 -0
  125. package/experimental/L/user.ts +10 -0
  126. package/experimental/Loader.ts +35 -0
  127. package/experimental/TaggedTemplateFunction.ts +44 -0
  128. package/experimental/Template.ts +93 -0
  129. package/experimental/index.ts +4 -0
  130. package/index.ts +9 -5
  131. package/package.json +26 -23
  132. package/tsconfig.json +9 -0
  133. package/Envelope.ts +0 -48
  134. package/L/L.ts +0 -21
  135. package/L/Self.ts +0 -5
  136. package/L/append.ts +0 -14
  137. package/L/assistant.ts +0 -30
  138. package/L/assistantSchema.ts +0 -61
  139. package/L/assistantStream.ts +0 -22
  140. package/L/branch.ts +0 -33
  141. package/L/clear.ts +0 -14
  142. package/L/disable.ts +0 -13
  143. package/L/enable.ts +0 -24
  144. package/L/events.ts +0 -11
  145. package/L/fqn.ts +0 -9
  146. package/L/handle.ts +0 -26
  147. package/L/messages.ts +0 -10
  148. package/L/raw.ts +0 -30
  149. package/L/sequence.ts +0 -9
  150. package/L/system.ts +0 -14
  151. package/L/thread.ts +0 -28
  152. package/L/to.ts +0 -4
  153. package/L/toolkit.ts +0 -14
  154. package/L/user.test.ts +0 -30
  155. package/L/user.ts +0 -22
  156. package/L/userJson.ts +0 -14
  157. package/LEvent.ts +0 -23
  158. package/LPretty.ts +0 -41
  159. package/Thread.ts +0 -69
  160. package/dist/Envelope.d.ts +0 -15
  161. package/dist/Envelope.js +0 -30
  162. package/dist/Envelope.js.map +0 -1
  163. package/dist/L/L.d.ts +0 -21
  164. package/dist/L/L.js +0 -22
  165. package/dist/L/L.js.map +0 -1
  166. package/dist/L/Self.d.ts +0 -3
  167. package/dist/L/Self.js +0 -4
  168. package/dist/L/Self.js.map +0 -1
  169. package/dist/L/append.d.ts +0 -5
  170. package/dist/L/append.js +0 -10
  171. package/dist/L/append.js.map +0 -1
  172. package/dist/L/assistant.d.ts +0 -6
  173. package/dist/L/assistant.js +0 -26
  174. package/dist/L/assistant.js.map +0 -1
  175. package/dist/L/assistantSchema.d.ts +0 -13
  176. package/dist/L/assistantSchema.js +0 -44
  177. package/dist/L/assistantSchema.js.map +0 -1
  178. package/dist/L/assistantStream.d.ts +0 -7
  179. package/dist/L/assistantStream.js +0 -17
  180. package/dist/L/assistantStream.js.map +0 -1
  181. package/dist/L/branch.d.ts +0 -6
  182. package/dist/L/branch.js +0 -22
  183. package/dist/L/branch.js.map +0 -1
  184. package/dist/L/clear.d.ts +0 -5
  185. package/dist/L/clear.js +0 -12
  186. package/dist/L/clear.js.map +0 -1
  187. package/dist/L/disable.d.ts +0 -4
  188. package/dist/L/disable.js +0 -10
  189. package/dist/L/disable.js.map +0 -1
  190. package/dist/L/enable.d.ts +0 -5
  191. package/dist/L/enable.js +0 -13
  192. package/dist/L/enable.js.map +0 -1
  193. package/dist/L/events.d.ts +0 -5
  194. package/dist/L/events.js +0 -6
  195. package/dist/L/events.js.map +0 -1
  196. package/dist/L/fqn.d.ts +0 -3
  197. package/dist/L/fqn.js +0 -8
  198. package/dist/L/fqn.js.map +0 -1
  199. package/dist/L/handle.d.ts +0 -7
  200. package/dist/L/handle.js +0 -14
  201. package/dist/L/handle.js.map +0 -1
  202. package/dist/L/messages.d.ts +0 -5
  203. package/dist/L/messages.js +0 -5
  204. package/dist/L/messages.js.map +0 -1
  205. package/dist/L/raw.d.ts +0 -3
  206. package/dist/L/raw.js +0 -18
  207. package/dist/L/raw.js.map +0 -1
  208. package/dist/L/sequence.d.ts +0 -2
  209. package/dist/L/sequence.js +0 -5
  210. package/dist/L/sequence.js.map +0 -1
  211. package/dist/L/system.d.ts +0 -5
  212. package/dist/L/system.js +0 -12
  213. package/dist/L/system.js.map +0 -1
  214. package/dist/L/thread.d.ts +0 -6
  215. package/dist/L/thread.js +0 -17
  216. package/dist/L/thread.js.map +0 -1
  217. package/dist/L/to.d.ts +0 -3
  218. package/dist/L/to.js +0 -3
  219. package/dist/L/to.js.map +0 -1
  220. package/dist/L/toolkit.d.ts +0 -4
  221. package/dist/L/toolkit.js +0 -9
  222. package/dist/L/toolkit.js.map +0 -1
  223. package/dist/L/user.d.ts +0 -4
  224. package/dist/L/user.js +0 -13
  225. package/dist/L/user.js.map +0 -1
  226. package/dist/L/user.test.d.ts +0 -1
  227. package/dist/L/user.test.js.map +0 -1
  228. package/dist/L/userJson.d.ts +0 -6
  229. package/dist/L/userJson.js +0 -9
  230. package/dist/L/userJson.js.map +0 -1
  231. package/dist/LEvent.d.ts +0 -26
  232. package/dist/LEvent.js +0 -16
  233. package/dist/LEvent.js.map +0 -1
  234. package/dist/LPretty.d.ts +0 -4
  235. package/dist/LPretty.js +0 -37
  236. package/dist/LPretty.js.map +0 -1
  237. package/dist/Thread.d.ts +0 -55
  238. package/dist/Thread.js +0 -38
  239. package/dist/Thread.js.map +0 -1
  240. package/dist/patterns/Debate.d.ts +0 -1
  241. package/dist/patterns/Debate.js +0 -3
  242. package/dist/patterns/Debate.js.map +0 -1
  243. package/dist/patterns/Model.d.ts +0 -6
  244. package/dist/patterns/Model.js +0 -14
  245. package/dist/patterns/Model.js.map +0 -1
  246. package/dist/patterns/Route.d.ts +0 -3
  247. package/dist/patterns/Route.js +0 -14
  248. package/dist/patterns/Route.js.map +0 -1
  249. package/dist/util/JsonValue.d.ts +0 -8
  250. package/dist/util/JsonValue.js +0 -109
  251. package/dist/util/JsonValue.js.map +0 -1
  252. package/dist/util/NeverTool.d.ts +0 -3
  253. package/dist/util/NeverTool.js +0 -3
  254. package/dist/util/NeverTool.js.map +0 -1
  255. package/dist/util/Sequencer.d.ts +0 -4
  256. package/dist/util/Sequencer.js +0 -2
  257. package/dist/util/Sequencer.js.map +0 -1
  258. package/dist/util/Taggable.d.ts +0 -5
  259. package/dist/util/Taggable.js +0 -2
  260. package/dist/util/Taggable.js.map +0 -1
  261. package/dist/util/normalizeRaw.d.ts +0 -4
  262. package/dist/util/normalizeRaw.js +0 -75
  263. package/dist/util/normalizeRaw.js.map +0 -1
  264. package/dist/util/prefix.d.ts +0 -1
  265. package/dist/util/prefix.js +0 -2
  266. package/dist/util/prefix.js.map +0 -1
  267. package/patterns/Debate.ts +0 -2
  268. package/patterns/Model.ts +0 -29
  269. package/patterns/Route.ts +0 -21
  270. package/util/JsonValue.ts +0 -144
  271. package/util/NeverTool.ts +0 -10
  272. package/util/Sequencer.ts +0 -13
  273. package/util/Taggable.ts +0 -14
  274. package/util/normalizeRaw.ts +0 -93
  275. package/util/prefix.ts +0 -1
package/Client.ts ADDED
@@ -0,0 +1,560 @@
1
+ import { Socket, Worker } from "@effect/platform"
2
+ import {
3
+ Context,
4
+ Encoding,
5
+ Deferred,
6
+ Effect,
7
+ Layer,
8
+ Option,
9
+ PubSub,
10
+ RcRef,
11
+ Record,
12
+ Ref,
13
+ Scope,
14
+ Stream,
15
+ Take,
16
+ Schema as S,
17
+ Array,
18
+ Struct,
19
+ } from "effect"
20
+
21
+ import type { FieldsRecord } from "./_types.ts"
22
+ import { type ClientError, AuditionError, ConnectionError } from "./errors.ts"
23
+ import { type F, type FError, UnresolvedError } from "./F.ts"
24
+ import type { MethodDefinition } from "./Method.ts"
25
+ import * as Protocol from "./Protocol.ts"
26
+
27
+ export const TypeId = "~liminal/Client" as const
28
+
29
+ export interface ClientDefinition<
30
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
31
+ EventDefinitions extends FieldsRecord,
32
+ > {
33
+ readonly methods: MethodDefinitions
34
+
35
+ readonly events: EventDefinitions
36
+ }
37
+
38
+ export interface ReplayConfig {
39
+ readonly mode: "startup" | "all-subscribers"
40
+
41
+ readonly limit?: number | undefined
42
+ }
43
+
44
+ interface EventTake<A, E> {
45
+ readonly seq: number
46
+
47
+ readonly take: Take.Take<A, E>
48
+ }
49
+
50
+ export interface TransportSession<
51
+ ClientSelf,
52
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
53
+ EventDefinitions extends FieldsRecord,
54
+ > {
55
+ readonly events: Stream.Stream<FieldsRecord.TaggedMember.Type<EventDefinitions>, ClientError>
56
+
57
+ readonly f: F<ClientSelf, MethodDefinitions>
58
+
59
+ readonly endEvents: Effect.Effect<void>
60
+ }
61
+
62
+ export type Service<
63
+ ClientSelf,
64
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
65
+ EventDefinitions extends FieldsRecord,
66
+ > = RcRef.RcRef<TransportSession<ClientSelf, MethodDefinitions, EventDefinitions>, ClientError>
67
+
68
+ export interface Spec<
69
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
70
+ EventDefinitions extends FieldsRecord,
71
+ > {
72
+ Call: {
73
+ Payload: Protocol.Call.Payload.Type<MethodDefinitions>
74
+ Success: Protocol.Call.Success.Type<MethodDefinitions>
75
+ Failure: Protocol.Call.Failure.Type<MethodDefinitions>
76
+ }
77
+ Event: FieldsRecord.TaggedMember.Type<EventDefinitions>
78
+ Actor: Protocol.Actor.Type<MethodDefinitions, EventDefinitions>
79
+ }
80
+
81
+ export interface ClientSchema<
82
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
83
+ EventDefinitions extends FieldsRecord,
84
+ > {
85
+ readonly call: {
86
+ readonly payload: S.Schema<
87
+ Protocol.Call.Payload.Type<MethodDefinitions>,
88
+ Protocol.Call.Payload.Encoded<MethodDefinitions>
89
+ >
90
+
91
+ readonly success: S.Schema<
92
+ Protocol.Call.Success.Type<MethodDefinitions>,
93
+ Protocol.Call.Success.Encoded<MethodDefinitions>
94
+ >
95
+
96
+ readonly failure: S.Schema<
97
+ Protocol.Call.Failure.Type<MethodDefinitions>,
98
+ Protocol.Call.Failure.Encoded<MethodDefinitions>
99
+ >
100
+ }
101
+
102
+ readonly event: S.Schema<Protocol.Event.Type<EventDefinitions>, Protocol.Event.Encoded<EventDefinitions>>
103
+
104
+ readonly actor: S.Schema<
105
+ Protocol.Actor.Type<MethodDefinitions, EventDefinitions>,
106
+ Protocol.Actor.Encoded<MethodDefinitions, EventDefinitions>
107
+ >
108
+ }
109
+
110
+ export interface Client<
111
+ ClientSelf,
112
+ ClientId extends string,
113
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
114
+ EventDefinitions extends FieldsRecord,
115
+ > extends Context.Tag<ClientSelf, Service<ClientSelf, MethodDefinitions, EventDefinitions>> {
116
+ new (_: never): Context.TagClassShape<ClientId, Service<ClientSelf, MethodDefinitions, EventDefinitions>>
117
+
118
+ readonly [TypeId]: typeof TypeId
119
+
120
+ readonly definition: ClientDefinition<MethodDefinitions, EventDefinitions>
121
+
122
+ readonly schema: ClientSchema<MethodDefinitions, EventDefinitions>
123
+
124
+ readonly events: Stream.Stream<FieldsRecord.TaggedMember.Type<EventDefinitions>, ClientError, ClientSelf>
125
+
126
+ readonly endEvents: Effect.Effect<void, never, ClientSelf>
127
+
128
+ readonly f: F<ClientSelf, MethodDefinitions>
129
+
130
+ readonly invalidate: Effect.Effect<void, never, ClientSelf>
131
+ }
132
+
133
+ export const Service =
134
+ <ClientSelf>() =>
135
+ <
136
+ ClientId extends string,
137
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
138
+ EventDefinitions extends FieldsRecord,
139
+ >(
140
+ id: ClientId,
141
+ definition: ClientDefinition<MethodDefinitions, EventDefinitions>,
142
+ ): Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions> => {
143
+ const tag = Context.Tag(id)<ClientSelf, Service<ClientSelf, MethodDefinitions, EventDefinitions>>()
144
+
145
+ const call: ClientSchema<MethodDefinitions, EventDefinitions>["call"] = {
146
+ payload: S.TaggedStruct("Call.Payload", {
147
+ id: S.Int,
148
+ payload: S.Union(
149
+ ...Record.toEntries(definition.methods).map(([_tag, { payload }]) =>
150
+ S.TaggedStruct(_tag, { value: S.Struct(payload) }),
151
+ ),
152
+ ),
153
+ }) as never,
154
+ success: S.TaggedStruct("Call.Success", {
155
+ id: S.Int,
156
+ value: S.Union(
157
+ ...Record.toEntries(definition.methods).map(([_tag, { success: value }]) => S.TaggedStruct(_tag, { value })),
158
+ ),
159
+ }) as never,
160
+ failure: S.TaggedStruct("Call.Failure", {
161
+ id: S.Int,
162
+ cause: S.Union(
163
+ ...Record.toEntries(definition.methods).map(([_tag, { failure: value }]) => S.TaggedStruct(_tag, { value })),
164
+ ),
165
+ }) as never,
166
+ }
167
+
168
+ const event: S.Schema<
169
+ Protocol.Event.Type<EventDefinitions>,
170
+ Protocol.Event.Encoded<EventDefinitions>
171
+ > = S.TaggedStruct("Event", {
172
+ event: S.Union(...Object.entries(definition.events).map(([_tag, fields]) => S.TaggedStruct(_tag, fields))),
173
+ }) as never
174
+
175
+ const actor: S.Schema<
176
+ Protocol.Actor.Type<MethodDefinitions, EventDefinitions>,
177
+ Protocol.Actor.Encoded<MethodDefinitions, EventDefinitions>
178
+ > = S.Union(
179
+ call.success,
180
+ call.failure,
181
+ event,
182
+ Protocol.Audition.Success,
183
+ Protocol.Audition.Failure,
184
+ Protocol.Disconnect,
185
+ )
186
+
187
+ const events: Stream.Stream<FieldsRecord.TaggedMember.Type<EventDefinitions>, ClientError, ClientSelf> = tag.pipe(
188
+ Effect.flatMap(RcRef.get),
189
+ Effect.map(Struct.get("events")),
190
+ Stream.unwrapScoped,
191
+ )
192
+
193
+ const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
194
+ Effect.fnUntraced(function* (value) {
195
+ const { f } = yield* tag.pipe(Effect.flatMap(RcRef.get))
196
+ return yield* f(_tag)(value)
197
+ }, Effect.scoped)
198
+
199
+ const invalidate = tag.pipe(Effect.flatMap(RcRef.invalidate))
200
+
201
+ const endEvents = tag.pipe(
202
+ Effect.flatMap(RcRef.get),
203
+ Effect.flatMap(({ endEvents }) => endEvents),
204
+ Effect.scoped,
205
+ Effect.ignore,
206
+ )
207
+
208
+ return Object.assign(tag, {
209
+ [TypeId]: TypeId,
210
+ definition,
211
+ schema: { call, event, actor },
212
+ events,
213
+ endEvents,
214
+ f,
215
+ invalidate,
216
+ })
217
+ }
218
+
219
+ export interface Transport<
220
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
221
+ EventDefinitions extends FieldsRecord,
222
+ > {
223
+ readonly listen: (
224
+ publish: (
225
+ message: Protocol.Actor.Type<MethodDefinitions, EventDefinitions> | typeof Protocol.TransportFailure.Type,
226
+ ) => Effect.Effect<void, never>,
227
+ ) => Effect.Effect<void, never, Scope.Scope>
228
+
229
+ readonly send: (v: Protocol.Call.Payload.Type<MethodDefinitions>) => Effect.Effect<void, ConnectionError, never>
230
+ }
231
+
232
+ const make = <
233
+ ClientSelf,
234
+ ClientId extends string,
235
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
236
+ EventDefinitions extends FieldsRecord,
237
+ R,
238
+ >(
239
+ client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>,
240
+ build: Effect.Effect<Transport<MethodDefinitions, EventDefinitions>, ClientError, R | Scope.Scope>,
241
+ replay?: ReplayConfig | undefined,
242
+ ) =>
243
+ Effect.gen(function* () {
244
+ type _ = Spec<MethodDefinitions, EventDefinitions>
245
+
246
+ const rcr: RcRef.RcRef<
247
+ TransportSession<ClientSelf, MethodDefinitions, EventDefinitions>,
248
+ ClientError
249
+ > = yield* RcRef.make({
250
+ acquire: Effect.gen(function* () {
251
+ const { listen, send } = yield* build
252
+
253
+ const audition = yield* Deferred.make<void, AuditionError>()
254
+ const inflights: Record<string, Deferred.Deferred<_["Call"]["Success"], FError<MethodDefinitions>>> = {}
255
+ let callId = 0
256
+ let takeCount = 0
257
+ let finalError: ClientError | UnresolvedError | undefined
258
+ const pubsub = yield* PubSub.unbounded<EventTake<_["Event"], ClientError>>()
259
+
260
+ const replayState = yield* Ref.make<{
261
+ readonly startupOpen: boolean
262
+ readonly buffer: ReadonlyArray<EventTake<_["Event"], ClientError>>
263
+ }>({
264
+ startupOpen: true,
265
+ buffer: [],
266
+ })
267
+
268
+ const publishTake = (take: Take.Take<_["Event"], ClientError>, replayable?: boolean | undefined) =>
269
+ Effect.gen(function* () {
270
+ const eventTake: EventTake<_["Event"], ClientError> = {
271
+ seq: takeCount++,
272
+ take,
273
+ }
274
+ if (replay && replayable) {
275
+ yield* Ref.update(replayState, (state) => {
276
+ if (replay.mode === "startup" && !state.startupOpen) {
277
+ return state
278
+ }
279
+ const buffer =
280
+ replay.limit === undefined
281
+ ? [...state.buffer, eventTake]
282
+ : [...(state.buffer.length >= replay.limit ? state.buffer.slice(1) : state.buffer), eventTake]
283
+ const { startupOpen } = state
284
+ return { startupOpen, buffer }
285
+ })
286
+ }
287
+ yield* PubSub.publish(pubsub, eventTake)
288
+ })
289
+
290
+ const events: Stream.Stream<_["Event"], ClientError> = Effect.gen(function* () {
291
+ const queue = yield* PubSub.subscribe(pubsub)
292
+ const live = (replayCount: number): Stream.Stream<_["Event"], ClientError> =>
293
+ Stream.fromQueue(queue).pipe(
294
+ Stream.filter((entry) => entry.seq > replayCount),
295
+ Stream.map((entry) => entry.take),
296
+ Stream.flattenTake,
297
+ )
298
+ if (!replay) {
299
+ return live(-1)
300
+ }
301
+ const buffer =
302
+ replay.mode === "all-subscribers"
303
+ ? (yield* Ref.get(replayState)).buffer
304
+ : yield* Ref.modify(replayState, (state) =>
305
+ state.startupOpen
306
+ ? [
307
+ state.buffer,
308
+ {
309
+ startupOpen: false,
310
+ buffer: [],
311
+ },
312
+ ]
313
+ : [[], state],
314
+ )
315
+ const replayCount = Array.get(buffer, buffer.length - 1).pipe(
316
+ Option.map(({ seq }) => seq),
317
+ Option.getOrElse(() => -1),
318
+ )
319
+ return buffer.length === 0
320
+ ? live(replayCount)
321
+ : Stream.concat(
322
+ Stream.fromIterable(buffer).pipe(
323
+ Stream.map((entry) => entry.take),
324
+ Stream.flattenTake,
325
+ ),
326
+ live(replayCount),
327
+ )
328
+ }).pipe(Stream.unwrapScoped)
329
+
330
+ yield* listen(
331
+ Effect.fnUntraced(function* (message) {
332
+ switch (message._tag) {
333
+ case "Audition.Success": {
334
+ yield* Deferred.succeed(audition, void 0)
335
+ break
336
+ }
337
+ case "Event": {
338
+ const { event } = message
339
+ yield* publishTake(Take.of(event), true)
340
+ break
341
+ }
342
+ case "Call.Success":
343
+ case "Call.Failure": {
344
+ const { id } = message
345
+ const deferred = inflights[id]
346
+ if (deferred) {
347
+ delete inflights[id]
348
+ switch (message._tag) {
349
+ case "Call.Success": {
350
+ yield* Deferred.succeed(deferred, message.value.value)
351
+ break
352
+ }
353
+ case "Call.Failure": {
354
+ yield* Deferred.fail(deferred, message.cause.value)
355
+ break
356
+ }
357
+ }
358
+ }
359
+ break
360
+ }
361
+ case "Audition.Failure": {
362
+ const { actual, expected } = message
363
+ finalError = AuditionError.make({ value: { actual, expected } })
364
+ yield* Deferred.fail(audition, finalError)
365
+ break
366
+ }
367
+ case "Disconnect":
368
+ case "TransportFailure": {
369
+ yield* Deferred.succeed(audition, void 0)
370
+ switch (message._tag) {
371
+ case "Disconnect": {
372
+ finalError = UnresolvedError.make()
373
+ yield* publishTake(Take.end)
374
+ break
375
+ }
376
+ case "TransportFailure": {
377
+ const { cause } = message
378
+ yield* publishTake(Take.fail((finalError = ConnectionError.make({ cause }))))
379
+ break
380
+ }
381
+ }
382
+ break
383
+ }
384
+ }
385
+ }),
386
+ ).pipe(
387
+ Effect.ensuring(
388
+ Effect.all(
389
+ [
390
+ PubSub.shutdown(pubsub),
391
+ Effect.forEach(
392
+ Record.values(inflights),
393
+ (deferred) => Deferred.fail(deferred, finalError ?? UnresolvedError.make()),
394
+ {
395
+ concurrency: "unbounded",
396
+ },
397
+ ),
398
+ RcRef.invalidate(rcr),
399
+ ],
400
+ { concurrency: "unbounded" },
401
+ ),
402
+ ),
403
+ Effect.forkScoped,
404
+ )
405
+
406
+ yield* Deferred.await(audition)
407
+
408
+ const f: F<ClientSelf, MethodDefinitions> = (_tag) =>
409
+ Effect.fnUntraced(function* (value) {
410
+ if (finalError) return yield* finalError
411
+ const id = callId++
412
+ const inflight = yield* Deferred.make<_["Call"]["Success"], FError<MethodDefinitions>>()
413
+ inflights[id] = inflight
414
+ yield* send({
415
+ _tag: "Call.Payload",
416
+ id,
417
+ payload: { _tag, value },
418
+ })
419
+ return yield* Deferred.await(inflight)
420
+ }, Effect.scoped)
421
+
422
+ const endEvents = publishTake(Take.end)
423
+
424
+ return { events, f, endEvents }
425
+ }),
426
+ })
427
+
428
+ return rcr
429
+ }).pipe(Layer.scoped(client))
430
+
431
+ export const layerSocket = <
432
+ ClientSelf,
433
+ ClientId extends string,
434
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
435
+ EventDefinitions extends FieldsRecord,
436
+ >({
437
+ client,
438
+ url,
439
+ protocols,
440
+ replay,
441
+ }: {
442
+ readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
443
+ readonly url?: string | undefined
444
+ readonly protocols?: string | Array<string> | undefined
445
+ readonly replay?: ReplayConfig | undefined
446
+ }): Layer.Layer<ClientSelf, never, Socket.WebSocketConstructor> =>
447
+ make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Socket.WebSocketConstructor>(
448
+ client,
449
+ Effect.gen(function* () {
450
+ const socket = yield* Socket.makeWebSocket(url ?? "/", {
451
+ protocols: ["liminal", Encoding.encodeBase64Url(client.key), ...(protocols ? Array.ensure(protocols) : [])],
452
+ })
453
+ return {
454
+ listen: Effect.fnUntraced(function* (publish) {
455
+ yield* socket
456
+ .runRaw(
457
+ Effect.fnUntraced(function* (raw) {
458
+ const message = yield* S.decodeUnknown(S.parseJson(client.schema.actor))(
459
+ raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw,
460
+ )
461
+ yield* publish(message)
462
+ }),
463
+ )
464
+ .pipe(
465
+ Effect.catchTag("ParseError", (cause) => publish({ _tag: "TransportFailure", cause })),
466
+ Effect.catchTag(
467
+ "SocketError",
468
+ Effect.fnUntraced(function* (cause) {
469
+ switch (cause.reason) {
470
+ case "Read":
471
+ case "Write":
472
+ case "Open":
473
+ case "OpenTimeout": {
474
+ // TODO
475
+ console.log(cause)
476
+ return yield* publish({ _tag: "TransportFailure", cause })
477
+ }
478
+ case "Close": {
479
+ const { code, closeReason } = cause
480
+ switch (code) {
481
+ case 1000: {
482
+ return yield* publish({ _tag: "Disconnect" })
483
+ }
484
+ case 4003: {
485
+ const parsed = S.decodeUnknownOption(S.parseJson(Protocol.Audition.Failure))(closeReason)
486
+ if (parsed._tag === "None") {
487
+ return yield* publish({ _tag: "TransportFailure", cause })
488
+ }
489
+ const { actual, expected } = parsed.value
490
+ return yield* publish({
491
+ _tag: "Audition.Failure",
492
+ actual,
493
+ expected,
494
+ })
495
+ }
496
+ }
497
+ return yield* publish({ _tag: "TransportFailure", cause })
498
+ }
499
+ }
500
+ }),
501
+ ),
502
+ )
503
+ }),
504
+ send: Effect.fnUntraced(function* (v) {
505
+ const write = yield* socket.writer
506
+ const message = yield* S.encode(S.parseJson(client.schema.call.payload))(v).pipe(
507
+ Effect.mapError((cause) => ConnectionError.make({ cause })),
508
+ )
509
+ yield* write(message).pipe(Effect.catchTag("SocketError", (cause) => ConnectionError.make({ cause })))
510
+ }, Effect.scoped),
511
+ }
512
+ }),
513
+ replay,
514
+ )
515
+
516
+ export const layerWorker = <
517
+ ClientSelf,
518
+ ClientId extends string,
519
+ MethodDefinitions extends Record<string, MethodDefinition.Any>,
520
+ EventDefinitions extends FieldsRecord,
521
+ >({
522
+ client,
523
+ replay,
524
+ }: {
525
+ readonly client: Client<ClientSelf, ClientId, MethodDefinitions, EventDefinitions>
526
+ readonly replay?: ReplayConfig | undefined
527
+ }): Layer.Layer<ClientSelf, never, Worker.PlatformWorker | Worker.Spawner> =>
528
+ make<ClientSelf, ClientId, MethodDefinitions, EventDefinitions, Worker.PlatformWorker | Worker.Spawner>(
529
+ client,
530
+ Effect.gen(function* () {
531
+ const manager = yield* Worker.makeManager
532
+ const worker = yield* manager
533
+ .spawn<
534
+ Protocol.Call.Payload.Type<MethodDefinitions> | string,
535
+ Protocol.Actor.Type<MethodDefinitions, EventDefinitions>,
536
+ never
537
+ >({})
538
+ .pipe(Effect.catchTag("WorkerError", (cause) => ConnectionError.make({ cause })))
539
+
540
+ const send = (message: Protocol.Call.Payload.Type<MethodDefinitions>) =>
541
+ worker.executeEffect(message).pipe(Effect.catchTag("WorkerError", (cause) => ConnectionError.make({ cause })))
542
+
543
+ return {
544
+ listen: Effect.fnUntraced(function* (publish) {
545
+ yield* worker.execute(client.key).pipe(
546
+ Stream.catchTag("WorkerError", (cause) =>
547
+ Effect.gen(function* () {
548
+ yield* publish({ _tag: "TransportFailure", cause })
549
+ return Stream.empty
550
+ }).pipe(Stream.unwrap),
551
+ ),
552
+ Stream.takeUntil((message) => message._tag === "Disconnect" || message._tag === "Audition.Failure"),
553
+ Stream.runForEach(publish),
554
+ )
555
+ }),
556
+ send,
557
+ }
558
+ }),
559
+ replay,
560
+ )
@@ -0,0 +1,39 @@
1
+ import { Schema as S, Effect, ParseResult } from "effect"
2
+
3
+ import type { Fields, FieldsRecord } from "./_types.ts"
4
+ import type { Send } from "./Send.ts"
5
+
6
+ const TypeId = "~liminal/ClientHandle" as const
7
+
8
+ export interface ClientHandle<ActorSelf, AttachmentFields extends Fields, EventDefinitions extends FieldsRecord> {
9
+ readonly [TypeId]: typeof TypeId
10
+
11
+ readonly send: Send<ActorSelf, EventDefinitions>
12
+
13
+ readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
14
+
15
+ readonly save: (attachments: S.Struct<AttachmentFields>["Type"]) => Effect.Effect<void, ParseResult.ParseError>
16
+
17
+ readonly disconnect: Effect.Effect<void, never, ActorSelf>
18
+ }
19
+
20
+ export const make = <ActorSelf, AttachmentFields extends Fields, EventDefinitions extends FieldsRecord>({
21
+ send,
22
+ attachments,
23
+ save,
24
+ disconnect,
25
+ }: {
26
+ readonly send: Send<ActorSelf, EventDefinitions>
27
+
28
+ readonly attachments: Effect.Effect<S.Struct<AttachmentFields>["Type"]>
29
+
30
+ readonly save: (attachments: S.Struct<AttachmentFields>["Type"]) => Effect.Effect<void, ParseResult.ParseError>
31
+
32
+ readonly disconnect: Effect.Effect<void, never, ActorSelf>
33
+ }): ClientHandle<ActorSelf, AttachmentFields, EventDefinitions> => ({
34
+ [TypeId]: TypeId,
35
+ send,
36
+ attachments,
37
+ save,
38
+ disconnect,
39
+ })
package/F.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Record, Effect, Schema as S } from "effect"
2
+
3
+ import type { ClientError } from "./errors.ts"
4
+ import type { MethodDefinition } from "./Method.ts"
5
+
6
+ export class UnresolvedError extends S.TaggedError<UnresolvedError>()("UnresolvedError", {}) {}
7
+
8
+ export type FError<MethodDefinitions extends Record<string, MethodDefinition.Any>> = [
9
+ MethodDefinitions[keyof MethodDefinitions]["failure"]["Type"] | ClientError | UnresolvedError,
10
+ ][0]
11
+
12
+ export type F<ClientSelf, MethodDefinitions extends Record<string, MethodDefinition.Any>> = <
13
+ Method extends keyof MethodDefinitions,
14
+ >(
15
+ method: Method,
16
+ ) => (
17
+ payload: S.Struct<MethodDefinitions[Method]["payload"]>["Type"],
18
+ ) => Effect.Effect<MethodDefinitions[Method]["success"]["Type"], FError<MethodDefinitions>, ClientSelf>
package/Method.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { Schema as S, Effect } from "effect"
2
+
3
+ import type { Fields } from "./_types.ts"
4
+
5
+ export interface MethodDefinition<P extends Fields, AA, AI, EA, EI> {
6
+ readonly payload: P
7
+ readonly success: S.Schema<AA, AI>
8
+ readonly failure: S.Schema<EA, EI>
9
+ }
10
+
11
+ export declare namespace MethodDefinition {
12
+ export type Any = MethodDefinition<Fields, any, any, any, any> | MethodDefinition<Fields, any, any, never, never>
13
+
14
+ export type Merge<T, U> = [T] extends [never]
15
+ ? U
16
+ : {
17
+ [K in keyof T & keyof U]: T[K] extends U[K] ? (U[K] extends T[K] ? T[K] : never) : never
18
+ }
19
+ }
20
+
21
+ export const define = <const P extends Fields, AA, AI, EA, EI>({
22
+ payload,
23
+ success,
24
+ failure,
25
+ }: {
26
+ readonly payload: P
27
+ readonly success: S.Schema<AA, AI>
28
+ readonly failure: S.Schema<EA, EI>
29
+ }): MethodDefinition<P, AA, AI, EA, EI> => ({ payload, success, failure })
30
+
31
+ export type Handler<MethodDefinition extends MethodDefinition.Any, R> = (
32
+ payload: S.Struct<MethodDefinition["payload"]>["Type"],
33
+ ) => Effect.Effect<MethodDefinition["success"]["Type"], MethodDefinition["failure"]["Type"], R>
34
+
35
+ export type Handlers<MethodDefinitions extends Record<string, MethodDefinition.Any>, R> = {
36
+ [K in keyof MethodDefinitions]: Handler<MethodDefinitions[K], R>
37
+ }
38
+
39
+ export const handler = <M extends MethodDefinition.Any, R>(_method: M, f: Handler<M, R>): Handler<M, R> => f