liminal 0.17.14 → 0.17.16
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.
- package/Actor.ts +22 -34
- package/ActorHandle.ts +34 -0
- package/ActorNamespace.ts +188 -0
- package/ActorRuntime.ts +449 -0
- package/ActorTransport.ts +8 -6
- package/Audition.ts +87 -40
- package/BrowserActorNamespace.ts +257 -0
- package/CHANGELOG.md +17 -0
- package/Client.ts +374 -197
- package/ClientDirectory.ts +71 -49
- package/ClientHandle.ts +9 -7
- package/ClientHandleEncoders.ts +15 -0
- package/Fn.ts +94 -0
- package/Method.ts +11 -21
- package/Protocol.ts +44 -36
- package/Reducer.ts +22 -0
- package/Tracing.ts +45 -0
- package/dist/Actor.d.ts +3 -5
- package/dist/Actor.js +5 -9
- package/dist/Actor.js.map +1 -1
- package/dist/ActorHandle.d.ts +12 -0
- package/dist/ActorHandle.js +4 -0
- package/dist/ActorHandle.js.map +1 -0
- package/dist/ActorNamespace.d.ts +25 -0
- package/dist/ActorNamespace.js +60 -0
- package/dist/ActorNamespace.js.map +1 -0
- package/dist/ActorRuntime.d.ts +20 -0
- package/dist/ActorRuntime.js +210 -0
- package/dist/ActorRuntime.js.map +1 -0
- package/dist/ActorTransport.d.ts +5 -4
- package/dist/Audition.d.ts +16 -9
- package/dist/Audition.js +25 -9
- package/dist/Audition.js.map +1 -1
- package/dist/BrowserActorNamespace.d.ts +39 -0
- package/dist/BrowserActorNamespace.js +134 -0
- package/dist/BrowserActorNamespace.js.map +1 -0
- package/dist/Client.d.ts +26 -16
- package/dist/Client.js +186 -109
- package/dist/Client.js.map +1 -1
- package/dist/ClientDirectory.d.ts +15 -7
- package/dist/ClientDirectory.js +32 -23
- package/dist/ClientDirectory.js.map +1 -1
- package/dist/ClientHandle.d.ts +5 -4
- package/dist/ClientHandleEncoders.d.ts +7 -0
- package/dist/ClientHandleEncoders.js +2 -0
- package/dist/ClientHandleEncoders.js.map +1 -0
- package/dist/Fn.d.ts +24 -0
- package/dist/Fn.js +2 -0
- package/dist/Fn.js.map +1 -0
- package/dist/Method.d.ts +9 -14
- package/dist/Method.js +0 -1
- package/dist/Method.js.map +1 -1
- package/dist/Protocol.d.ts +19 -22
- package/dist/Protocol.js +20 -15
- package/dist/Protocol.js.map +1 -1
- package/dist/Reducer.d.ts +11 -0
- package/dist/Reducer.js +2 -0
- package/dist/Reducer.js.map +1 -0
- package/dist/Tracing.d.ts +37 -0
- package/dist/Tracing.js +33 -0
- package/dist/Tracing.js.map +1 -0
- package/dist/errors.d.ts +0 -4
- package/dist/errors.js.map +1 -1
- package/dist/experimental/L/append.js +1 -1
- package/dist/experimental/L/append.js.map +1 -1
- package/dist/experimental/L/history.js +1 -1
- package/dist/experimental/L/history.js.map +1 -1
- package/dist/experimental/TaggedTemplateFunction.js +1 -1
- package/dist/experimental/TaggedTemplateFunction.js.map +1 -1
- package/dist/index.common.d.ts +12 -0
- package/dist/index.common.js +13 -0
- package/dist/index.common.js.map +1 -0
- package/dist/index.d.ts +4 -11
- package/dist/index.js +4 -11
- package/dist/index.js.map +1 -1
- package/dist/index.non-workerd.d.ts +1 -0
- package/dist/index.non-workerd.js +2 -0
- package/dist/index.non-workerd.js.map +1 -0
- package/dist/package.json +20 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/errors.ts +0 -6
- package/experimental/L/append.ts +1 -1
- package/experimental/L/history.ts +1 -1
- package/experimental/TaggedTemplateFunction.ts +1 -1
- package/index.common.ts +12 -0
- package/index.non-workerd.ts +1 -0
- package/index.ts +4 -11
- package/package.json +11 -23
- package/tsconfig.json +1 -1
- package/vitest.config.ts +7 -0
- package/Accumulator.ts +0 -103
- package/F.ts +0 -10
- package/_diagnostic.ts +0 -3
- package/_util/Mutex.ts +0 -13
- package/_util/schema.ts +0 -7
- package/browser/BrowserActorNamespace.ts +0 -213
- package/browser/index.ts +0 -1
- package/dist/Accumulator.d.ts +0 -22
- package/dist/Accumulator.js +0 -37
- package/dist/Accumulator.js.map +0 -1
- package/dist/F.d.ts +0 -4
- package/dist/F.js +0 -2
- package/dist/F.js.map +0 -1
- package/dist/_diagnostic.d.ts +0 -4
- package/dist/_diagnostic.js +0 -3
- package/dist/_diagnostic.js.map +0 -1
- package/dist/_util/Mutex.d.ts +0 -7
- package/dist/_util/Mutex.js +0 -9
- package/dist/_util/Mutex.js.map +0 -1
- package/dist/_util/schema.d.ts +0 -4
- package/dist/_util/schema.js +0 -5
- package/dist/_util/schema.js.map +0 -1
- package/dist/browser/BrowserActorNamespace.d.ts +0 -16
- package/dist/browser/BrowserActorNamespace.js +0 -112
- package/dist/browser/BrowserActorNamespace.js.map +0 -1
- package/dist/browser/index.d.ts +0 -1
- package/dist/browser/index.js +0 -2
- package/dist/browser/index.js.map +0 -1
- package/dist/workerd/WorkerdActorNamespace.d.ts +0 -25
- package/dist/workerd/WorkerdActorNamespace.js +0 -146
- package/dist/workerd/WorkerdActorNamespace.js.map +0 -1
- package/dist/workerd/index.d.ts +0 -1
- package/dist/workerd/index.js +0 -2
- package/dist/workerd/index.js.map +0 -1
- package/workerd/WorkerdActorNamespace.ts +0 -362
- package/workerd/index.ts +0 -1
package/Client.ts
CHANGED
|
@@ -15,23 +15,25 @@ import {
|
|
|
15
15
|
Take,
|
|
16
16
|
Schema as S,
|
|
17
17
|
Array,
|
|
18
|
-
Struct,
|
|
19
18
|
Fiber,
|
|
20
19
|
Exit,
|
|
21
20
|
Cause,
|
|
22
21
|
Result,
|
|
23
22
|
flow,
|
|
23
|
+
Tracer,
|
|
24
|
+
identity,
|
|
25
|
+
Semaphore,
|
|
24
26
|
} from "effect"
|
|
25
27
|
import { Socket } from "effect/unstable/socket"
|
|
26
28
|
import { Worker } from "effect/unstable/workers"
|
|
29
|
+
import * as Boundary from "liminal-util/Boundary"
|
|
30
|
+
import { decodeJsonString, encodeJsonString } from "liminal-util/schema"
|
|
27
31
|
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { type ClientError, AuditionError, ConnectionError, type FError, UnresolvedError } from "./errors.ts"
|
|
31
|
-
import { type F } from "./F.ts"
|
|
32
|
+
import { type ClientError, AuditionError, ConnectionError, UnresolvedError } from "./errors.ts"
|
|
33
|
+
import type { Fn, FnError, FnNoSelf } from "./Fn.ts"
|
|
32
34
|
import { Protocol, type ProtocolDefinition } from "./Protocol.ts"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
import * as Reducer from "./Reducer.ts"
|
|
36
|
+
import * as Tracing from "./Tracing.ts"
|
|
35
37
|
|
|
36
38
|
export const TypeId = "~liminal/Client" as const
|
|
37
39
|
|
|
@@ -47,21 +49,29 @@ interface EventTake<A, E> {
|
|
|
47
49
|
readonly take: Take.Take<A, E>
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
+
export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<
|
|
53
|
+
{
|
|
54
|
+
readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError>
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
readonly fnRaw: <K extends keyof D["external"], M extends D["external"][K]>(
|
|
59
|
+
tag: K,
|
|
60
|
+
payload: M["payload"]["Type"],
|
|
61
|
+
) => Effect.Effect<M["success"]["Type"], M["failure"]["Type"], ClientSelf>
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
readonly end: Effect.Effect<void>
|
|
64
|
+
},
|
|
65
|
+
ClientError
|
|
66
|
+
>
|
|
59
67
|
|
|
60
|
-
export interface Client<Self,
|
|
68
|
+
export interface Client<Self, Id extends string, D extends ProtocolDefinition> extends Context.Service<
|
|
61
69
|
Self,
|
|
62
70
|
Service<Self, D>
|
|
63
71
|
> {
|
|
64
|
-
new (_: never): Context.ServiceClass.Shape<
|
|
72
|
+
new (_: never): Context.ServiceClass.Shape<Id, Service<Self, D>> & {
|
|
73
|
+
readonly State: S.Struct<D["state"]>["Type"]
|
|
74
|
+
}
|
|
65
75
|
|
|
66
76
|
readonly [TypeId]: typeof TypeId
|
|
67
77
|
|
|
@@ -69,17 +79,36 @@ export interface Client<Self, ClientId extends string, D extends ProtocolDefinit
|
|
|
69
79
|
|
|
70
80
|
readonly protocol: Protocol<D>
|
|
71
81
|
|
|
82
|
+
readonly state: Stream.Stream<
|
|
83
|
+
S.Struct<D["state"]>["Type"],
|
|
84
|
+
ClientError | S.SchemaError,
|
|
85
|
+
Self | S.Struct<D["state"]>["DecodingServices"]
|
|
86
|
+
>
|
|
87
|
+
|
|
72
88
|
readonly events: Stream.Stream<
|
|
73
89
|
ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"],
|
|
74
90
|
ClientError | S.SchemaError,
|
|
75
91
|
Self
|
|
76
92
|
>
|
|
77
93
|
|
|
78
|
-
readonly
|
|
94
|
+
readonly fn: Fn<Self, D["external"]>
|
|
79
95
|
|
|
80
96
|
readonly invalidate: Effect.Effect<void, never, Self>
|
|
97
|
+
|
|
98
|
+
readonly reducer: <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_tag: K, f: R) => R
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
export const fn = <ClientSelf, D extends ProtocolDefinition>(service: Service<ClientSelf, D>) =>
|
|
102
|
+
((_tag: keyof D["external"], ...f: Array<any>) =>
|
|
103
|
+
Effect.fnUntraced(
|
|
104
|
+
function* (payload: any) {
|
|
105
|
+
const { fnRaw: fn } = yield* RcRef.get(service)
|
|
106
|
+
return yield* fn(_tag, payload)
|
|
107
|
+
},
|
|
108
|
+
Effect.scoped,
|
|
109
|
+
...(f as [any]),
|
|
110
|
+
)) as FnNoSelf<D["external"]>
|
|
111
|
+
|
|
83
112
|
export const Service =
|
|
84
113
|
<Self>() =>
|
|
85
114
|
<Id extends string, D extends ProtocolDefinition>(id: Id, definition: D): Client<Self, Id, D> => {
|
|
@@ -87,15 +116,29 @@ export const Service =
|
|
|
87
116
|
|
|
88
117
|
const protocol = Protocol(definition)
|
|
89
118
|
|
|
90
|
-
const
|
|
119
|
+
const state = tag.pipe(
|
|
120
|
+
Effect.flatMap(RcRef.get),
|
|
121
|
+
Effect.map(({ state }) => state),
|
|
122
|
+
Stream.unwrap,
|
|
123
|
+
)
|
|
91
124
|
|
|
92
|
-
const
|
|
93
|
-
Effect.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
const events = tag.pipe(
|
|
126
|
+
Effect.flatMap(RcRef.get),
|
|
127
|
+
Effect.map(({ events }) => events),
|
|
128
|
+
Stream.unwrap,
|
|
129
|
+
)
|
|
97
130
|
|
|
98
|
-
const
|
|
131
|
+
const fn = ((_tag: keyof D["external"], ...f: Array<any>) =>
|
|
132
|
+
Effect.fnUntraced(
|
|
133
|
+
function* (payload: any) {
|
|
134
|
+
const { fnRaw: fn } = yield* tag.pipe(Effect.flatMap(RcRef.get))
|
|
135
|
+
return yield* fn(_tag, payload)
|
|
136
|
+
},
|
|
137
|
+
Effect.scoped,
|
|
138
|
+
...(f as [any]),
|
|
139
|
+
)) as Fn<Self, D["external"]>
|
|
140
|
+
|
|
141
|
+
const invalidate = tag.pipe(
|
|
99
142
|
Effect.flatMap((rc) =>
|
|
100
143
|
RcRef.get(rc).pipe(
|
|
101
144
|
Effect.flatMap(({ end }) => end),
|
|
@@ -106,47 +149,69 @@ export const Service =
|
|
|
106
149
|
Effect.ignore,
|
|
107
150
|
)
|
|
108
151
|
|
|
109
|
-
|
|
152
|
+
const reducer = <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_event: K, f: R) => f
|
|
153
|
+
|
|
154
|
+
return Object.assign(tag satisfies Context.ServiceClass.Shape<Id, Service<Self, D>> as never, {
|
|
110
155
|
[TypeId]: TypeId,
|
|
111
156
|
definition,
|
|
112
157
|
protocol,
|
|
158
|
+
state,
|
|
113
159
|
events,
|
|
114
|
-
|
|
160
|
+
fn,
|
|
115
161
|
invalidate,
|
|
162
|
+
reducer,
|
|
116
163
|
})
|
|
117
164
|
}
|
|
118
165
|
|
|
119
|
-
export interface ClientTransport<D extends ProtocolDefinition> {
|
|
166
|
+
export interface ClientTransport<D extends ProtocolDefinition, R> {
|
|
120
167
|
readonly listen: (
|
|
121
|
-
publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError>,
|
|
122
|
-
) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"]>
|
|
168
|
+
publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError, R>,
|
|
169
|
+
) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"] | R>
|
|
123
170
|
|
|
124
171
|
readonly send: (
|
|
125
172
|
message: Protocol<D>["F"]["Payload"]["Type"],
|
|
126
173
|
) => Effect.Effect<void, ClientError | S.SchemaError, Protocol<D>["F"]["Payload"]["EncodingServices"]>
|
|
127
174
|
}
|
|
128
175
|
|
|
129
|
-
const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
176
|
+
const make = <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, R, CR>({
|
|
177
|
+
build,
|
|
178
|
+
client,
|
|
179
|
+
reducers,
|
|
180
|
+
onConnect,
|
|
181
|
+
replay,
|
|
182
|
+
}: {
|
|
183
|
+
readonly client: Client<Self, Id, D>
|
|
184
|
+
readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
|
|
185
|
+
readonly reducers: Reducers
|
|
186
|
+
readonly build: Effect.Effect<
|
|
187
|
+
ClientTransport<D, Reducer.Reducers.Services<Self, Reducers> | CR>,
|
|
188
|
+
ClientError,
|
|
189
|
+
R | Scope.Scope
|
|
190
|
+
>
|
|
191
|
+
readonly replay?: ReplayConfig | undefined
|
|
192
|
+
}) =>
|
|
134
193
|
Effect.gen(function* () {
|
|
135
|
-
const rcr:
|
|
194
|
+
const rcr: Service<Self, D> = yield* RcRef.make({
|
|
136
195
|
acquire: Effect.gen(function* () {
|
|
137
196
|
type _ = typeof client.protocol
|
|
138
197
|
type Event = ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"]
|
|
139
198
|
|
|
140
|
-
yield* debug("AcquisitionStarted")
|
|
141
|
-
|
|
142
199
|
const { listen, send } = yield* build
|
|
143
200
|
|
|
144
201
|
const audition = yield* Deferred.make<void>()
|
|
202
|
+
const stateDeferred = yield* Deferred.make<Ref.Ref<S.Struct<D["state"]>["Type"]>>()
|
|
145
203
|
|
|
146
|
-
const inflights: Record<
|
|
204
|
+
const inflights: Record<
|
|
205
|
+
string,
|
|
206
|
+
{
|
|
207
|
+
readonly deferred: Deferred.Deferred<_["F"]["Success"]["Type"], FnError<D["external"], keyof D["external"]>>
|
|
208
|
+
readonly span?: Tracer.AnySpan | undefined
|
|
209
|
+
}
|
|
210
|
+
> = {}
|
|
147
211
|
let callId = 0
|
|
148
212
|
let takeCount = 0
|
|
149
|
-
const
|
|
213
|
+
const eventsPubsub = yield* PubSub.unbounded<EventTake<Event, ClientError>>()
|
|
214
|
+
const statePubsub = yield* PubSub.unbounded<S.Struct<D["state"]>["Type"]>({ replay: 1 })
|
|
150
215
|
|
|
151
216
|
const replayState = yield* Ref.make<{
|
|
152
217
|
readonly startupOpen: boolean
|
|
@@ -175,57 +240,87 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
|
|
|
175
240
|
return { startupOpen, buffer }
|
|
176
241
|
})
|
|
177
242
|
}
|
|
178
|
-
yield* PubSub.publish(
|
|
243
|
+
yield* PubSub.publish(eventsPubsub, eventTake)
|
|
179
244
|
})
|
|
180
245
|
|
|
181
246
|
const outer = yield* Scope.Scope
|
|
182
247
|
const scope = yield* Scope.fork(outer, "sequential")
|
|
183
248
|
const end = Scope.close(scope, Exit.void)
|
|
249
|
+
const reduceMutex = yield* Semaphore.make(1)
|
|
250
|
+
const reduceTask = Semaphore.withPermits(reduceMutex, 1)
|
|
184
251
|
|
|
185
252
|
const fiber = yield* listen(
|
|
186
253
|
Effect.fnUntraced(function* (message) {
|
|
187
254
|
switch (message._tag) {
|
|
188
255
|
case "Audition.Success": {
|
|
189
|
-
|
|
256
|
+
const { initial } = message
|
|
257
|
+
yield* PubSub.publish(statePubsub, initial)
|
|
258
|
+
const state = yield* Ref.make(initial)
|
|
259
|
+
yield* Deferred.succeed(stateDeferred, state)
|
|
190
260
|
yield* Deferred.succeed(audition, void 0)
|
|
261
|
+
yield* onConnect?.(initial) ?? Effect.void
|
|
191
262
|
return
|
|
192
263
|
}
|
|
193
264
|
case "Audition.Failure": {
|
|
194
265
|
const { expected, actual } = message
|
|
195
|
-
yield* debug("Audition.Failed", { expected, actual })
|
|
196
266
|
return yield* new AuditionError({ value: { expected, actual } })
|
|
197
267
|
}
|
|
198
268
|
case "Event": {
|
|
199
269
|
const { event } = message
|
|
200
|
-
|
|
201
|
-
|
|
270
|
+
const { _tag } = event as never
|
|
271
|
+
const reducer = reducers[_tag]!
|
|
272
|
+
const state = yield* Deferred.await(stateDeferred)
|
|
273
|
+
yield* Effect.gen(function* () {
|
|
274
|
+
const current = yield* Ref.get(state)
|
|
275
|
+
const reduced = yield* reducer(event as never)(current).pipe(
|
|
276
|
+
Effect.provideService(client, rcr),
|
|
277
|
+
// TODO: rework error-handling
|
|
278
|
+
Effect.catchDefect(() => Effect.succeed(undefined)),
|
|
279
|
+
) as Effect.Effect<
|
|
280
|
+
S.Struct<D["state"]>["Type"] | undefined,
|
|
281
|
+
never,
|
|
282
|
+
Reducer.Reducers.Services<Self, Reducers>
|
|
283
|
+
>
|
|
284
|
+
if (reduced) {
|
|
285
|
+
yield* PubSub.publish(statePubsub, reduced)
|
|
286
|
+
yield* Ref.set(state, reduced)
|
|
287
|
+
}
|
|
288
|
+
}).pipe(reduceTask)
|
|
289
|
+
const parent = message.trace ? Tracer.externalSpan(message.trace) : undefined
|
|
290
|
+
yield* publishTake([event], true).pipe(
|
|
291
|
+
Boundary.span("enqueue-event", import.meta.url, {
|
|
292
|
+
attributes: { _tag },
|
|
293
|
+
kind: "consumer",
|
|
294
|
+
parent,
|
|
295
|
+
}),
|
|
296
|
+
)
|
|
202
297
|
return
|
|
203
298
|
}
|
|
204
299
|
case "F.Success":
|
|
205
300
|
case "F.Failure": {
|
|
206
301
|
const { id } = message
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
302
|
+
const inflight = inflights[id]
|
|
303
|
+
if (inflight) {
|
|
209
304
|
delete inflights[id]
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
305
|
+
return yield* Effect.gen(function* () {
|
|
306
|
+
switch (message._tag) {
|
|
307
|
+
case "F.Success": {
|
|
308
|
+
const { value } = message.success as never
|
|
309
|
+
yield* Deferred.succeed(inflight.deferred, value)
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
case "F.Failure": {
|
|
313
|
+
const { _tag, value } = message.failure as never
|
|
314
|
+
yield* Effect.annotateLogs(Effect.logDebug("Call.Failed"), { id, _tag })
|
|
315
|
+
yield* Deferred.fail(inflight.deferred, value)
|
|
316
|
+
return
|
|
317
|
+
}
|
|
216
318
|
}
|
|
217
|
-
|
|
218
|
-
const { _tag, value } = message.failure as never
|
|
219
|
-
yield* debug("Call.Failed", { id, _tag, value })
|
|
220
|
-
yield* Deferred.fail(deferred, value)
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
}
|
|
319
|
+
}).pipe(inflight.span ? Effect.withParentSpan(inflight.span, { captureStackTrace: false }) : identity)
|
|
224
320
|
}
|
|
225
321
|
return
|
|
226
322
|
}
|
|
227
323
|
case "Disconnect": {
|
|
228
|
-
yield* debug("Disconnected")
|
|
229
324
|
return
|
|
230
325
|
}
|
|
231
326
|
}
|
|
@@ -234,7 +329,13 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
|
|
|
234
329
|
Effect.ensuring(
|
|
235
330
|
Effect.all(
|
|
236
331
|
[
|
|
237
|
-
|
|
332
|
+
Effect.sync(() => Record.keys(inflights).length).pipe(
|
|
333
|
+
Effect.flatMap((unresolved) =>
|
|
334
|
+
unresolved === 0
|
|
335
|
+
? Effect.void
|
|
336
|
+
: Effect.annotateLogs(Effect.logDebug("Client.Closed"), { unresolved }),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
238
339
|
Deferred.succeed(audition, void 0),
|
|
239
340
|
RcRef.invalidate(rcr),
|
|
240
341
|
],
|
|
@@ -245,8 +346,25 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
|
|
|
245
346
|
Effect.provideService(Scope.Scope, scope),
|
|
246
347
|
)
|
|
247
348
|
|
|
349
|
+
const interrupt = Stream.interruptWhen(
|
|
350
|
+
Fiber.await(fiber).pipe(
|
|
351
|
+
Effect.flatMap(
|
|
352
|
+
Exit.match({
|
|
353
|
+
onSuccess: () => Effect.void,
|
|
354
|
+
onFailure: flow(
|
|
355
|
+
Cause.findError,
|
|
356
|
+
Result.match({
|
|
357
|
+
onSuccess: Effect.fail,
|
|
358
|
+
onFailure: () => Effect.void,
|
|
359
|
+
}),
|
|
360
|
+
),
|
|
361
|
+
}),
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
|
|
248
366
|
const events = Effect.gen(function* () {
|
|
249
|
-
const queue = yield* PubSub.subscribe(
|
|
367
|
+
const queue = yield* PubSub.subscribe(eventsPubsub)
|
|
250
368
|
const live = (replayCount: number) =>
|
|
251
369
|
Stream.fromSubscription(queue).pipe(
|
|
252
370
|
Stream.filter((entry) => entry.seq > replayCount),
|
|
@@ -283,206 +401,265 @@ const make = <Self, Id extends string, D extends ProtocolDefinition, R>(
|
|
|
283
401
|
),
|
|
284
402
|
live(replayCount),
|
|
285
403
|
)
|
|
286
|
-
}).pipe(
|
|
287
|
-
Stream.unwrap,
|
|
288
|
-
Stream.interruptWhen(
|
|
289
|
-
Fiber.await(fiber).pipe(
|
|
290
|
-
Effect.flatMap(
|
|
291
|
-
Exit.match({
|
|
292
|
-
onSuccess: () => Effect.void,
|
|
293
|
-
onFailure: flow(
|
|
294
|
-
Cause.findError,
|
|
295
|
-
Result.match({
|
|
296
|
-
onSuccess: Effect.fail,
|
|
297
|
-
onFailure: () => Effect.void,
|
|
298
|
-
}),
|
|
299
|
-
),
|
|
300
|
-
}),
|
|
301
|
-
),
|
|
302
|
-
),
|
|
303
|
-
),
|
|
304
|
-
)
|
|
404
|
+
}).pipe(Stream.unwrap, interrupt)
|
|
305
405
|
|
|
306
|
-
|
|
406
|
+
const state = Stream.fromPubSub(statePubsub).pipe(interrupt)
|
|
307
407
|
|
|
308
408
|
const encodingServices = yield* Effect.context<_["F"]["Payload"]["EncodingServices"]>()
|
|
309
409
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
),
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
const id = callId++
|
|
327
|
-
const inflight = yield* Deferred.make<_["F"]["Success"]["Type"], FError<D>>()
|
|
328
|
-
inflights[id] = inflight
|
|
329
|
-
yield* send({
|
|
330
|
-
_tag: "F.Payload",
|
|
331
|
-
id,
|
|
332
|
-
payload: { _tag, value } as never,
|
|
410
|
+
yield* Deferred.await(audition)
|
|
411
|
+
|
|
412
|
+
const fnRaw = <K extends keyof D["external"]>(_tag: K, value: D["external"][K]["payload"]["Type"]) =>
|
|
413
|
+
Effect.gen(function* () {
|
|
414
|
+
const exit = fiber.pollUnsafe()
|
|
415
|
+
if (exit) {
|
|
416
|
+
return yield* Exit.match(exit, {
|
|
417
|
+
onSuccess: () => new UnresolvedError(),
|
|
418
|
+
onFailure: flow(
|
|
419
|
+
Cause.findError,
|
|
420
|
+
Result.match({
|
|
421
|
+
onSuccess: Effect.fail,
|
|
422
|
+
onFailure: () => new UnresolvedError(),
|
|
423
|
+
}),
|
|
424
|
+
),
|
|
333
425
|
})
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
426
|
+
}
|
|
427
|
+
const id = callId++
|
|
428
|
+
const deferred = yield* Deferred.make<
|
|
429
|
+
_["F"]["Success"]["Type"],
|
|
430
|
+
FnError<D["external"], keyof D["external"]>
|
|
431
|
+
>()
|
|
432
|
+
const span = yield* Tracing.current
|
|
433
|
+
const trace = span ? Tracing.toTraceEnvelope(span) : undefined
|
|
434
|
+
inflights[id] = { deferred, span }
|
|
435
|
+
yield* send({
|
|
436
|
+
_tag: "F.Payload",
|
|
437
|
+
id,
|
|
438
|
+
payload: { _tag, value } as never,
|
|
439
|
+
...(trace && { trace }),
|
|
440
|
+
})
|
|
441
|
+
return yield* Effect.raceFirst(
|
|
442
|
+
Deferred.await(deferred),
|
|
443
|
+
Fiber.await(fiber).pipe(
|
|
444
|
+
Effect.flatMap(
|
|
445
|
+
(exit): Effect.Effect<never, ClientError | UnresolvedError | S.SchemaError> =>
|
|
446
|
+
Exit.match(exit, {
|
|
447
|
+
onSuccess: () => new UnresolvedError(),
|
|
448
|
+
onFailure: flow(
|
|
449
|
+
Cause.findError,
|
|
450
|
+
Result.match({
|
|
451
|
+
onSuccess: Effect.fail,
|
|
452
|
+
onFailure: () => new UnresolvedError(),
|
|
453
|
+
}),
|
|
454
|
+
),
|
|
455
|
+
}),
|
|
350
456
|
),
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
}).pipe(
|
|
460
|
+
Boundary.span("fn", import.meta.url, {
|
|
461
|
+
kind: "client",
|
|
462
|
+
attributes: { _tag },
|
|
463
|
+
}),
|
|
355
464
|
Effect.provide(encodingServices),
|
|
356
465
|
)
|
|
357
466
|
|
|
358
|
-
return { events,
|
|
359
|
-
}).pipe(
|
|
467
|
+
return { state, events, fnRaw, end }
|
|
468
|
+
}).pipe(
|
|
469
|
+
Boundary.span("acquire", import.meta.url, {
|
|
470
|
+
attributes: { client: client.key },
|
|
471
|
+
}),
|
|
472
|
+
Effect.annotateLogs("client", client.key),
|
|
473
|
+
),
|
|
360
474
|
})
|
|
361
475
|
|
|
362
476
|
return rcr
|
|
363
477
|
}).pipe(Layer.effect(client))
|
|
364
478
|
|
|
365
|
-
|
|
479
|
+
let clientId_: string | undefined
|
|
480
|
+
const clientId = () => {
|
|
481
|
+
if (!clientId_) {
|
|
482
|
+
clientId_ = crypto.randomUUID()
|
|
483
|
+
}
|
|
484
|
+
return clientId_
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export const layerSocket = <
|
|
488
|
+
Self,
|
|
489
|
+
Id extends string,
|
|
490
|
+
D extends ProtocolDefinition,
|
|
491
|
+
Reducers extends Reducer.Reducers<D>,
|
|
492
|
+
CR = never,
|
|
493
|
+
>({
|
|
366
494
|
client,
|
|
495
|
+
reducers,
|
|
367
496
|
url,
|
|
368
497
|
protocols,
|
|
369
498
|
replay,
|
|
499
|
+
onConnect,
|
|
370
500
|
}: {
|
|
371
501
|
readonly client: Client<Self, Id, D>
|
|
372
|
-
readonly
|
|
373
|
-
readonly protocols?: string | Array<string> | undefined
|
|
502
|
+
readonly reducers: Reducers
|
|
374
503
|
readonly replay?: ReplayConfig | undefined
|
|
504
|
+
readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
|
|
505
|
+
readonly protocols?: string | Array<string> | undefined
|
|
506
|
+
readonly url?: string | undefined
|
|
375
507
|
}): Layer.Layer<
|
|
376
508
|
Self,
|
|
377
509
|
never,
|
|
378
510
|
| Socket.WebSocketConstructor
|
|
379
511
|
| Protocol<D>["Actor"]["DecodingServices"]
|
|
380
512
|
| Protocol<D>["F"]["Payload"]["EncodingServices"]
|
|
513
|
+
| Reducer.Reducers.Services<Self, Reducers>
|
|
514
|
+
| CR
|
|
381
515
|
> => {
|
|
382
516
|
const { F, Actor } = client.protocol
|
|
383
517
|
const encodeFPayload = encodeJsonString(F.Payload)
|
|
384
518
|
const decodeActor = decodeJsonString(Actor)
|
|
385
519
|
|
|
386
|
-
return make<Self, Id, D, Socket.WebSocketConstructor>(
|
|
520
|
+
return make<Self, Id, D, Reducers, Socket.WebSocketConstructor, CR>({
|
|
387
521
|
client,
|
|
388
|
-
|
|
522
|
+
reducers,
|
|
523
|
+
onConnect,
|
|
524
|
+
replay,
|
|
525
|
+
build: Effect.gen(function* () {
|
|
389
526
|
const socket = yield* Socket.makeWebSocket(url ?? "/", {
|
|
390
|
-
protocols: [
|
|
527
|
+
protocols: [
|
|
528
|
+
"liminal",
|
|
529
|
+
clientId(),
|
|
530
|
+
Encoding.encodeBase64Url(client.key),
|
|
531
|
+
...(protocols ? Array.ensure(protocols) : []),
|
|
532
|
+
],
|
|
391
533
|
})
|
|
392
534
|
return {
|
|
393
|
-
listen: Effect.fnUntraced(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
535
|
+
listen: Effect.fnUntraced(
|
|
536
|
+
function* (publish) {
|
|
537
|
+
yield* socket
|
|
538
|
+
.runRaw((raw) =>
|
|
539
|
+
pipe(
|
|
540
|
+
raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw,
|
|
541
|
+
decodeActor,
|
|
542
|
+
Effect.andThen(publish),
|
|
543
|
+
),
|
|
544
|
+
)
|
|
545
|
+
.pipe(
|
|
546
|
+
Effect.catchIf(
|
|
547
|
+
Socket.isSocketError,
|
|
548
|
+
Effect.fnUntraced(function* (cause) {
|
|
549
|
+
const { reason } = cause
|
|
550
|
+
if (reason._tag === "SocketCloseError" && reason.code === 1000) {
|
|
551
|
+
return yield* publish({ _tag: "Disconnect" })
|
|
552
|
+
}
|
|
553
|
+
yield* Effect.annotateLogs(Effect.logDebug(`SocketErrored.${reason._tag}`), { cause })
|
|
554
|
+
return yield* new ConnectionError({ cause })
|
|
555
|
+
}),
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
},
|
|
559
|
+
Boundary.span("listen", import.meta.url),
|
|
560
|
+
),
|
|
417
561
|
send: Effect.fnUntraced(
|
|
418
562
|
function* (v) {
|
|
419
563
|
const write = yield* socket.writer
|
|
420
564
|
const message = yield* encodeFPayload(v)
|
|
421
565
|
yield* write(message).pipe(
|
|
422
|
-
Effect.
|
|
566
|
+
Effect.catchTags({
|
|
567
|
+
SocketError: (cause) => new ConnectionError({ cause }),
|
|
568
|
+
}),
|
|
423
569
|
)
|
|
424
570
|
},
|
|
425
|
-
span("send"),
|
|
571
|
+
Boundary.span("send", import.meta.url),
|
|
426
572
|
Effect.scoped,
|
|
427
573
|
),
|
|
428
574
|
}
|
|
429
575
|
}),
|
|
430
|
-
|
|
431
|
-
)
|
|
576
|
+
})
|
|
432
577
|
}
|
|
433
578
|
|
|
434
|
-
export const layerWorker = <
|
|
579
|
+
export const layerWorker = <
|
|
580
|
+
Self,
|
|
581
|
+
Id extends string,
|
|
582
|
+
D extends ProtocolDefinition,
|
|
583
|
+
Reducers extends Reducer.Reducers<D>,
|
|
584
|
+
T extends Protocol<D>,
|
|
585
|
+
CR = never,
|
|
586
|
+
>({
|
|
435
587
|
client,
|
|
588
|
+
reducers,
|
|
436
589
|
replay,
|
|
590
|
+
onConnect,
|
|
437
591
|
}: {
|
|
438
592
|
readonly client: Client<Self, Id, D>
|
|
593
|
+
readonly reducers: Reducers
|
|
439
594
|
readonly replay?: ReplayConfig | undefined
|
|
595
|
+
readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>)
|
|
440
596
|
}): Layer.Layer<
|
|
441
597
|
Self,
|
|
442
598
|
never,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
599
|
+
| Worker.WorkerPlatform
|
|
600
|
+
| Worker.Spawner
|
|
601
|
+
| T["Actor"]["DecodingServices"]
|
|
602
|
+
| T["Client"]["EncodingServices"]
|
|
603
|
+
| Reducer.Reducers.Services<Self, Reducers>
|
|
604
|
+
> => {
|
|
605
|
+
const { Actor, Client: ClientM } = client.protocol
|
|
606
|
+
const encodeClient = encodeJsonString(ClientM)
|
|
607
|
+
const decodeActor = decodeJsonString(Actor)
|
|
608
|
+
|
|
609
|
+
return make<Self, Id, D, Reducers, Worker.WorkerPlatform | Worker.Spawner, CR>({
|
|
446
610
|
client,
|
|
447
|
-
|
|
611
|
+
reducers,
|
|
612
|
+
onConnect,
|
|
613
|
+
replay,
|
|
614
|
+
build: Effect.gen(function* () {
|
|
448
615
|
const platform = yield* Worker.WorkerPlatform
|
|
449
|
-
const backing = yield* platform
|
|
450
|
-
.
|
|
451
|
-
|
|
616
|
+
const backing = yield* platform.spawn<string, string>(0).pipe(
|
|
617
|
+
Effect.catchTags({
|
|
618
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
619
|
+
}),
|
|
620
|
+
)
|
|
452
621
|
|
|
453
622
|
const send = (message: T["Client"]["Type"]) =>
|
|
454
|
-
|
|
455
|
-
Effect.
|
|
456
|
-
|
|
623
|
+
encodeClient(message).pipe(
|
|
624
|
+
Effect.flatMap((encoded) => backing.send(encoded)),
|
|
625
|
+
Effect.catchTags({
|
|
626
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
627
|
+
}),
|
|
628
|
+
Boundary.span("send", import.meta.url),
|
|
457
629
|
)
|
|
458
630
|
|
|
459
631
|
return {
|
|
460
|
-
listen: Effect.fnUntraced(
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
632
|
+
listen: Effect.fnUntraced(
|
|
633
|
+
function* (publish) {
|
|
634
|
+
const stop = yield* Deferred.make<void>()
|
|
635
|
+
const audition = yield* encodeClient({
|
|
636
|
+
_tag: "Audition.Payload",
|
|
637
|
+
client: client.key,
|
|
638
|
+
})
|
|
639
|
+
yield* backing
|
|
640
|
+
.run(
|
|
641
|
+
Effect.fnUntraced(function* (raw) {
|
|
642
|
+
const message = yield* decodeActor(raw)
|
|
643
|
+
yield* publish(message)
|
|
644
|
+
if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
|
|
645
|
+
yield* Deferred.succeed(stop, void 0)
|
|
646
|
+
}
|
|
647
|
+
}),
|
|
648
|
+
{
|
|
649
|
+
onSpawn: backing.send(audition).pipe(Effect.orDie),
|
|
650
|
+
},
|
|
651
|
+
)
|
|
652
|
+
.pipe(
|
|
653
|
+
Effect.raceFirst(Deferred.await(stop)),
|
|
654
|
+
Effect.catchTags({
|
|
655
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
656
|
+
}),
|
|
657
|
+
)
|
|
658
|
+
},
|
|
659
|
+
Boundary.span("listen", import.meta.url),
|
|
660
|
+
),
|
|
484
661
|
send,
|
|
485
662
|
}
|
|
486
663
|
}),
|
|
487
|
-
|
|
488
|
-
|
|
664
|
+
})
|
|
665
|
+
}
|