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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { BrowserWorkerRunner } from "@effect/platform-browser";
|
|
2
|
+
import { Effect, Exit, Layer, Option, Ref, Schema as S, Scope, Semaphore, Stream, Tracer } from "effect";
|
|
3
|
+
import { WorkerRunner } from "effect/unstable/workers";
|
|
4
|
+
import * as Boundary from "liminal-util/Boundary";
|
|
5
|
+
import { encodeJsonString, decodeJsonString } from "liminal-util/schema";
|
|
6
|
+
import * as ClientDirectory from "./ClientDirectory.js";
|
|
7
|
+
import * as Method from "./Method.js";
|
|
8
|
+
import * as Tracing from "./Tracing.js";
|
|
9
|
+
export const make = Effect.fnUntraced(function* ({ actor, handlers, hydrate, introductions, }) {
|
|
10
|
+
const { definition: { client: { protocol: P, key: expected }, name: Name, }, } = actor;
|
|
11
|
+
const { Client: ClientM } = P;
|
|
12
|
+
const decodeClient = decodeJsonString(ClientM);
|
|
13
|
+
const encodeAuditionSuccess = encodeJsonString(P.Audition.Success);
|
|
14
|
+
const encodeAuditionFailure = encodeJsonString(P.Audition.Failure);
|
|
15
|
+
const encodeFSuccess = encodeJsonString(P.F.Success);
|
|
16
|
+
const encodeFFailure = encodeJsonString(P.F.Failure);
|
|
17
|
+
const encodeEvent = encodeJsonString(P.Event);
|
|
18
|
+
const entries = {};
|
|
19
|
+
const transport = {
|
|
20
|
+
key: ({ port }) => port,
|
|
21
|
+
send: ({ backing }, event) => {
|
|
22
|
+
const { _tag } = event.event;
|
|
23
|
+
return Effect.gen(function* () {
|
|
24
|
+
const trace = yield* Tracing.currentTrace;
|
|
25
|
+
yield* backing.send(0, yield* encodeEvent({ ...event, ...(trace && { trace }) }).pipe(Effect.catchTags({
|
|
26
|
+
SchemaError: Effect.die,
|
|
27
|
+
})));
|
|
28
|
+
}).pipe(Boundary.span("send", import.meta.url, { attributes: { _tag }, kind: "producer" }));
|
|
29
|
+
},
|
|
30
|
+
close: ({ close }) => close,
|
|
31
|
+
snapshot: () => Effect.void,
|
|
32
|
+
};
|
|
33
|
+
const useEntries = yield* Semaphore.make(1).pipe(Effect.map((v) => v.withPermits(1)));
|
|
34
|
+
const getEntry = Effect.fnUntraced(function* (key) {
|
|
35
|
+
const existing = entries[key];
|
|
36
|
+
if (existing)
|
|
37
|
+
return existing;
|
|
38
|
+
const directory = ClientDirectory.make(actor, { transport });
|
|
39
|
+
const semaphore = yield* Semaphore.make(1);
|
|
40
|
+
const fresh = {
|
|
41
|
+
directory,
|
|
42
|
+
mutex: semaphore.withPermits(1),
|
|
43
|
+
};
|
|
44
|
+
entries[key] = fresh;
|
|
45
|
+
return fresh;
|
|
46
|
+
}, useEntries);
|
|
47
|
+
const outerScope = yield* Scope.Scope;
|
|
48
|
+
yield* introductions.pipe(Stream.runForEach(Effect.fnUntraced(function* ({ name, port, attachments }) {
|
|
49
|
+
const stateRef = yield* Ref.make(Option.none());
|
|
50
|
+
const scope = yield* Scope.fork(outerScope, "sequential");
|
|
51
|
+
const closeScope = Scope.close(scope, Exit.void);
|
|
52
|
+
const backing = yield* BrowserWorkerRunner.make(port).start();
|
|
53
|
+
yield* Scope.addFinalizer(scope, Effect.gen(function* () {
|
|
54
|
+
const state = yield* Ref.get(stateRef);
|
|
55
|
+
if (state._tag === "Some") {
|
|
56
|
+
const { key, entry: { directory }, } = state.value;
|
|
57
|
+
yield* directory.unregister(port);
|
|
58
|
+
if (directory.handles.size === 0) {
|
|
59
|
+
delete entries[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}).pipe(useEntries));
|
|
63
|
+
yield* backing
|
|
64
|
+
.run(Effect.fnUntraced(function* (_portId, raw) {
|
|
65
|
+
const state = yield* Ref.get(stateRef);
|
|
66
|
+
const message = yield* decodeClient(raw);
|
|
67
|
+
if (state._tag === "None") {
|
|
68
|
+
if (message._tag !== "Audition.Payload") {
|
|
69
|
+
return yield* Effect.die(undefined);
|
|
70
|
+
}
|
|
71
|
+
const { client: actual } = message;
|
|
72
|
+
if (actual !== expected) {
|
|
73
|
+
yield* backing.send(0, yield* encodeAuditionFailure({
|
|
74
|
+
_tag: "Audition.Failure",
|
|
75
|
+
expected,
|
|
76
|
+
actual,
|
|
77
|
+
}));
|
|
78
|
+
return yield* closeScope;
|
|
79
|
+
}
|
|
80
|
+
const key = yield* S.encodeEffect(Name)(name);
|
|
81
|
+
const entry = yield* getEntry(key);
|
|
82
|
+
const currentClient = yield* entry.directory.register({ port, backing, close: closeScope }, attachments);
|
|
83
|
+
const ActorLive = Layer.succeed(actor, {
|
|
84
|
+
name,
|
|
85
|
+
clients: entry.directory.handles,
|
|
86
|
+
currentClient,
|
|
87
|
+
});
|
|
88
|
+
yield* Ref.set(stateRef, Option.some({ key, entry, currentClient, ActorLive }));
|
|
89
|
+
const initial = yield* hydrate.pipe(entry.mutex, Effect.scoped, Boundary.span("onConnect", import.meta.url), Effect.provide(ActorLive));
|
|
90
|
+
return yield* backing.send(0, yield* encodeAuditionSuccess({ _tag: "Audition.Success", initial }));
|
|
91
|
+
}
|
|
92
|
+
const { entry, ActorLive } = state.value;
|
|
93
|
+
if (message._tag === "Audition.Payload") {
|
|
94
|
+
return yield* Effect.die(undefined);
|
|
95
|
+
}
|
|
96
|
+
if (message._tag === "Disconnect") {
|
|
97
|
+
return yield* closeScope;
|
|
98
|
+
}
|
|
99
|
+
const { id, payload } = message;
|
|
100
|
+
const { _tag, value } = payload;
|
|
101
|
+
const parent = message.trace && Tracer.externalSpan(message.trace);
|
|
102
|
+
const transportSpan = yield* Tracing.parent;
|
|
103
|
+
yield* handlers[_tag](value).pipe(Effect.matchEffect({
|
|
104
|
+
onSuccess: (value) => encodeFSuccess({
|
|
105
|
+
_tag: "F.Success",
|
|
106
|
+
id,
|
|
107
|
+
success: { _tag, value },
|
|
108
|
+
}),
|
|
109
|
+
onFailure: (value) => encodeFFailure({
|
|
110
|
+
_tag: "F.Failure",
|
|
111
|
+
id,
|
|
112
|
+
failure: { _tag, value },
|
|
113
|
+
}),
|
|
114
|
+
}), Effect.andThen((v) => backing.send(0, v)), Boundary.span("handle", import.meta.url, {
|
|
115
|
+
attributes: { _tag },
|
|
116
|
+
kind: "server",
|
|
117
|
+
parent,
|
|
118
|
+
links: parent && transportSpan
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
span: transportSpan,
|
|
122
|
+
attributes: {
|
|
123
|
+
"liminal.link": "transport",
|
|
124
|
+
"liminal.transport": "worker",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
: undefined,
|
|
129
|
+
}), Effect.scoped, Effect.provide(ActorLive), entry.mutex);
|
|
130
|
+
}, Boundary.span("message", import.meta.url)))
|
|
131
|
+
.pipe(Effect.andThen(closeScope), Effect.catchCause((cause) => Effect.logError(cause).pipe(Effect.andThen(closeScope))), Boundary.span("backing", import.meta.url), Effect.forkScoped, Scope.provide(scope));
|
|
132
|
+
})));
|
|
133
|
+
}, Boundary.span("make", import.meta.url));
|
|
134
|
+
//# sourceMappingURL=BrowserActorNamespace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserActorNamespace.js","sourceRoot":"","sources":["../BrowserActorNamespace.ts"],"names":[],"mappings":"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"}
|
package/dist/Client.d.ts
CHANGED
|
@@ -2,40 +2,50 @@ import { Context, Effect, Layer, RcRef, Scope, Stream, Schema as S } from "effec
|
|
|
2
2
|
import { Socket } from "effect/unstable/socket";
|
|
3
3
|
import { Worker } from "effect/unstable/workers";
|
|
4
4
|
import { type ClientError } from "./errors.ts";
|
|
5
|
-
import {
|
|
5
|
+
import type { Fn, FnNoSelf } from "./Fn.ts";
|
|
6
6
|
import { Protocol, type ProtocolDefinition } from "./Protocol.ts";
|
|
7
|
+
import * as Reducer from "./Reducer.ts";
|
|
7
8
|
export declare const TypeId: "~liminal/Client";
|
|
8
9
|
export interface ReplayConfig {
|
|
9
10
|
readonly mode: "startup" | "all-subscribers";
|
|
10
11
|
readonly limit?: number | undefined;
|
|
11
12
|
}
|
|
12
|
-
export
|
|
13
|
+
export type Service<ClientSelf, D extends ProtocolDefinition> = RcRef.RcRef<{
|
|
14
|
+
readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError>;
|
|
13
15
|
readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError>;
|
|
14
|
-
readonly
|
|
16
|
+
readonly fnRaw: <K extends keyof D["external"], M extends D["external"][K]>(tag: K, payload: M["payload"]["Type"]) => Effect.Effect<M["success"]["Type"], M["failure"]["Type"], ClientSelf>;
|
|
15
17
|
readonly end: Effect.Effect<void>;
|
|
16
|
-
}
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
}, ClientError>;
|
|
19
|
+
export interface Client<Self, Id extends string, D extends ProtocolDefinition> extends Context.Service<Self, Service<Self, D>> {
|
|
20
|
+
new (_: never): Context.ServiceClass.Shape<Id, Service<Self, D>> & {
|
|
21
|
+
readonly State: S.Struct<D["state"]>["Type"];
|
|
22
|
+
};
|
|
20
23
|
readonly [TypeId]: typeof TypeId;
|
|
21
24
|
readonly definition: D;
|
|
22
25
|
readonly protocol: Protocol<D>;
|
|
26
|
+
readonly state: Stream.Stream<S.Struct<D["state"]>["Type"], ClientError | S.SchemaError, Self | S.Struct<D["state"]>["DecodingServices"]>;
|
|
23
27
|
readonly events: Stream.Stream<ReturnType<typeof S.TaggedUnion<D["events"]>>["Type"], ClientError | S.SchemaError, Self>;
|
|
24
|
-
readonly
|
|
28
|
+
readonly fn: Fn<Self, D["external"]>;
|
|
25
29
|
readonly invalidate: Effect.Effect<void, never, Self>;
|
|
30
|
+
readonly reducer: <K extends keyof D["events"], R extends Reducer.Reducer<D, K>>(_tag: K, f: R) => R;
|
|
26
31
|
}
|
|
32
|
+
export declare const fn: <ClientSelf, D extends ProtocolDefinition>(service: Service<ClientSelf, D>) => FnNoSelf<D["external"]>;
|
|
27
33
|
export declare const Service: <Self>() => <Id extends string, D extends ProtocolDefinition>(id: Id, definition: D) => Client<Self, Id, D>;
|
|
28
|
-
export interface ClientTransport<D extends ProtocolDefinition> {
|
|
29
|
-
readonly listen: (publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError>) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"]>;
|
|
34
|
+
export interface ClientTransport<D extends ProtocolDefinition, R> {
|
|
35
|
+
readonly listen: (publish: (message: Protocol<D>["Actor"]["Type"]) => Effect.Effect<void, ClientError, R>) => Effect.Effect<void, ClientError | S.SchemaError, Scope.Scope | Protocol<D>["Actor"]["DecodingServices"] | R>;
|
|
30
36
|
readonly send: (message: Protocol<D>["F"]["Payload"]["Type"]) => Effect.Effect<void, ClientError | S.SchemaError, Protocol<D>["F"]["Payload"]["EncodingServices"]>;
|
|
31
37
|
}
|
|
32
|
-
export declare const layerSocket: <Self, Id extends string, D extends ProtocolDefinition>({ client, url, protocols, replay, }: {
|
|
38
|
+
export declare const layerSocket: <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, CR = never>({ client, reducers, url, protocols, replay, onConnect, }: {
|
|
33
39
|
readonly client: Client<Self, Id, D>;
|
|
34
|
-
readonly
|
|
35
|
-
readonly protocols?: string | Array<string> | undefined;
|
|
40
|
+
readonly reducers: Reducers;
|
|
36
41
|
readonly replay?: ReplayConfig | undefined;
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>);
|
|
43
|
+
readonly protocols?: string | Array<string> | undefined;
|
|
44
|
+
readonly url?: string | undefined;
|
|
45
|
+
}) => Layer.Layer<Self, never, Socket.WebSocketConstructor | Protocol<D>["Actor"]["DecodingServices"] | Protocol<D>["F"]["Payload"]["EncodingServices"] | Reducer.Reducers.Services<Self, Reducers> | CR>;
|
|
46
|
+
export declare const layerWorker: <Self, Id extends string, D extends ProtocolDefinition, Reducers extends Reducer.Reducers<D>, T extends Protocol<D>, CR = never>({ client, reducers, replay, onConnect, }: {
|
|
39
47
|
readonly client: Client<Self, Id, D>;
|
|
48
|
+
readonly reducers: Reducers;
|
|
40
49
|
readonly replay?: ReplayConfig | undefined;
|
|
41
|
-
|
|
50
|
+
readonly onConnect?: undefined | ((state: S.Struct<D["state"]>["Type"]) => Effect.Effect<void, never, CR>);
|
|
51
|
+
}) => Layer.Layer<Self, never, Worker.WorkerPlatform | Worker.Spawner | T["Actor"]["DecodingServices"] | T["Client"]["EncodingServices"] | Reducer.Reducers.Services<Self, Reducers>>;
|
package/dist/Client.js
CHANGED
|
@@ -1,41 +1,50 @@
|
|
|
1
|
-
import { Context, Encoding, Deferred, Effect, Layer, Option, PubSub, RcRef, Record, pipe, Ref, Scope, Stream, Take, Schema as S, Array,
|
|
1
|
+
import { Context, Encoding, Deferred, Effect, Layer, Option, PubSub, RcRef, Record, pipe, Ref, Scope, Stream, Take, Schema as S, Array, Fiber, Exit, Cause, Result, flow, Tracer, identity, Semaphore, } from "effect";
|
|
2
2
|
import { Socket } from "effect/unstable/socket";
|
|
3
3
|
import { Worker } from "effect/unstable/workers";
|
|
4
|
-
import
|
|
5
|
-
import { decodeJsonString, encodeJsonString } from "
|
|
4
|
+
import * as Boundary from "liminal-util/Boundary";
|
|
5
|
+
import { decodeJsonString, encodeJsonString } from "liminal-util/schema";
|
|
6
6
|
import { AuditionError, ConnectionError, UnresolvedError } from "./errors.js";
|
|
7
|
-
import {} from "./F.js";
|
|
8
7
|
import { Protocol } from "./Protocol.js";
|
|
9
|
-
|
|
8
|
+
import * as Reducer from "./Reducer.js";
|
|
9
|
+
import * as Tracing from "./Tracing.js";
|
|
10
10
|
export const TypeId = "~liminal/Client";
|
|
11
|
+
export const fn = (service) => ((_tag, ...f) => Effect.fnUntraced(function* (payload) {
|
|
12
|
+
const { fnRaw: fn } = yield* RcRef.get(service);
|
|
13
|
+
return yield* fn(_tag, payload);
|
|
14
|
+
}, Effect.scoped, ...f));
|
|
11
15
|
export const Service = () => (id, definition) => {
|
|
12
16
|
const tag = Context.Service()(id);
|
|
13
17
|
const protocol = Protocol(definition);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const state = tag.pipe(Effect.flatMap(RcRef.get), Effect.map(({ state }) => state), Stream.unwrap);
|
|
19
|
+
const events = tag.pipe(Effect.flatMap(RcRef.get), Effect.map(({ events }) => events), Stream.unwrap);
|
|
20
|
+
const fn = ((_tag, ...f) => Effect.fnUntraced(function* (payload) {
|
|
21
|
+
const { fnRaw: fn } = yield* tag.pipe(Effect.flatMap(RcRef.get));
|
|
22
|
+
return yield* fn(_tag, payload);
|
|
23
|
+
}, Effect.scoped, ...f));
|
|
24
|
+
const invalidate = tag.pipe(Effect.flatMap((rc) => RcRef.get(rc).pipe(Effect.flatMap(({ end }) => end), Effect.andThen(RcRef.invalidate(rc)))), Effect.scoped, Effect.ignore);
|
|
25
|
+
const reducer = (_event, f) => f;
|
|
20
26
|
return Object.assign(tag, {
|
|
21
27
|
[TypeId]: TypeId,
|
|
22
28
|
definition,
|
|
23
29
|
protocol,
|
|
30
|
+
state,
|
|
24
31
|
events,
|
|
25
|
-
|
|
32
|
+
fn,
|
|
26
33
|
invalidate,
|
|
34
|
+
reducer,
|
|
27
35
|
});
|
|
28
36
|
};
|
|
29
|
-
const make = (client,
|
|
37
|
+
const make = ({ build, client, reducers, onConnect, replay, }) => Effect.gen(function* () {
|
|
30
38
|
const rcr = yield* RcRef.make({
|
|
31
39
|
acquire: Effect.gen(function* () {
|
|
32
|
-
yield* debug("AcquisitionStarted");
|
|
33
40
|
const { listen, send } = yield* build;
|
|
34
41
|
const audition = yield* Deferred.make();
|
|
42
|
+
const stateDeferred = yield* Deferred.make();
|
|
35
43
|
const inflights = {};
|
|
36
44
|
let callId = 0;
|
|
37
45
|
let takeCount = 0;
|
|
38
|
-
const
|
|
46
|
+
const eventsPubsub = yield* PubSub.unbounded();
|
|
47
|
+
const statePubsub = yield* PubSub.unbounded({ replay: 1 });
|
|
39
48
|
const replayState = yield* Ref.make({
|
|
40
49
|
startupOpen: true,
|
|
41
50
|
buffer: [],
|
|
@@ -57,64 +66,95 @@ const make = (client, build, replay) => Effect.gen(function* () {
|
|
|
57
66
|
return { startupOpen, buffer };
|
|
58
67
|
});
|
|
59
68
|
}
|
|
60
|
-
yield* PubSub.publish(
|
|
69
|
+
yield* PubSub.publish(eventsPubsub, eventTake);
|
|
61
70
|
});
|
|
62
71
|
const outer = yield* Scope.Scope;
|
|
63
72
|
const scope = yield* Scope.fork(outer, "sequential");
|
|
64
73
|
const end = Scope.close(scope, Exit.void);
|
|
74
|
+
const reduceMutex = yield* Semaphore.make(1);
|
|
75
|
+
const reduceTask = Semaphore.withPermits(reduceMutex, 1);
|
|
65
76
|
const fiber = yield* listen(Effect.fnUntraced(function* (message) {
|
|
66
77
|
switch (message._tag) {
|
|
67
78
|
case "Audition.Success": {
|
|
68
|
-
|
|
79
|
+
const { initial } = message;
|
|
80
|
+
yield* PubSub.publish(statePubsub, initial);
|
|
81
|
+
const state = yield* Ref.make(initial);
|
|
82
|
+
yield* Deferred.succeed(stateDeferred, state);
|
|
69
83
|
yield* Deferred.succeed(audition, void 0);
|
|
84
|
+
yield* onConnect?.(initial) ?? Effect.void;
|
|
70
85
|
return;
|
|
71
86
|
}
|
|
72
87
|
case "Audition.Failure": {
|
|
73
88
|
const { expected, actual } = message;
|
|
74
|
-
yield* debug("Audition.Failed", { expected, actual });
|
|
75
89
|
return yield* new AuditionError({ value: { expected, actual } });
|
|
76
90
|
}
|
|
77
91
|
case "Event": {
|
|
78
92
|
const { event } = message;
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
const { _tag } = event;
|
|
94
|
+
const reducer = reducers[_tag];
|
|
95
|
+
const state = yield* Deferred.await(stateDeferred);
|
|
96
|
+
yield* Effect.gen(function* () {
|
|
97
|
+
const current = yield* Ref.get(state);
|
|
98
|
+
const reduced = yield* reducer(event)(current).pipe(Effect.provideService(client, rcr),
|
|
99
|
+
// TODO: rework error-handling
|
|
100
|
+
Effect.catchDefect(() => Effect.succeed(undefined)));
|
|
101
|
+
if (reduced) {
|
|
102
|
+
yield* PubSub.publish(statePubsub, reduced);
|
|
103
|
+
yield* Ref.set(state, reduced);
|
|
104
|
+
}
|
|
105
|
+
}).pipe(reduceTask);
|
|
106
|
+
const parent = message.trace ? Tracer.externalSpan(message.trace) : undefined;
|
|
107
|
+
yield* publishTake([event], true).pipe(Boundary.span("enqueue-event", import.meta.url, {
|
|
108
|
+
attributes: { _tag },
|
|
109
|
+
kind: "consumer",
|
|
110
|
+
parent,
|
|
111
|
+
}));
|
|
81
112
|
return;
|
|
82
113
|
}
|
|
83
114
|
case "F.Success":
|
|
84
115
|
case "F.Failure": {
|
|
85
116
|
const { id } = message;
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
117
|
+
const inflight = inflights[id];
|
|
118
|
+
if (inflight) {
|
|
88
119
|
delete inflights[id];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
return yield* Effect.gen(function* () {
|
|
121
|
+
switch (message._tag) {
|
|
122
|
+
case "F.Success": {
|
|
123
|
+
const { value } = message.success;
|
|
124
|
+
yield* Deferred.succeed(inflight.deferred, value);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
case "F.Failure": {
|
|
128
|
+
const { _tag, value } = message.failure;
|
|
129
|
+
yield* Effect.annotateLogs(Effect.logDebug("Call.Failed"), { id, _tag });
|
|
130
|
+
yield* Deferred.fail(inflight.deferred, value);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
101
133
|
}
|
|
102
|
-
}
|
|
134
|
+
}).pipe(inflight.span ? Effect.withParentSpan(inflight.span, { captureStackTrace: false }) : identity);
|
|
103
135
|
}
|
|
104
136
|
return;
|
|
105
137
|
}
|
|
106
138
|
case "Disconnect": {
|
|
107
|
-
yield* debug("Disconnected");
|
|
108
139
|
return;
|
|
109
140
|
}
|
|
110
141
|
}
|
|
111
142
|
})).pipe(Effect.ensuring(Effect.all([
|
|
112
|
-
|
|
143
|
+
Effect.sync(() => Record.keys(inflights).length).pipe(Effect.flatMap((unresolved) => unresolved === 0
|
|
144
|
+
? Effect.void
|
|
145
|
+
: Effect.annotateLogs(Effect.logDebug("Client.Closed"), { unresolved }))),
|
|
113
146
|
Deferred.succeed(audition, void 0),
|
|
114
147
|
RcRef.invalidate(rcr),
|
|
115
148
|
], { concurrency: "unbounded" })), Effect.forkScoped, Effect.provideService(Scope.Scope, scope));
|
|
149
|
+
const interrupt = Stream.interruptWhen(Fiber.await(fiber).pipe(Effect.flatMap(Exit.match({
|
|
150
|
+
onSuccess: () => Effect.void,
|
|
151
|
+
onFailure: flow(Cause.findError, Result.match({
|
|
152
|
+
onSuccess: Effect.fail,
|
|
153
|
+
onFailure: () => Effect.void,
|
|
154
|
+
})),
|
|
155
|
+
}))));
|
|
116
156
|
const events = Effect.gen(function* () {
|
|
117
|
-
const queue = yield* PubSub.subscribe(
|
|
157
|
+
const queue = yield* PubSub.subscribe(eventsPubsub);
|
|
118
158
|
const live = (replayCount) => Stream.fromSubscription(queue).pipe(Stream.filter((entry) => entry.seq > replayCount), Stream.map((entry) => entry.take), Stream.flattenTake);
|
|
119
159
|
if (!replay) {
|
|
120
160
|
return live(-1);
|
|
@@ -134,16 +174,11 @@ const make = (client, build, replay) => Effect.gen(function* () {
|
|
|
134
174
|
return buffer.length === 0
|
|
135
175
|
? live(replayCount)
|
|
136
176
|
: Stream.concat(Stream.fromIterable(buffer).pipe(Stream.map((entry) => entry.take), Stream.flattenTake), live(replayCount));
|
|
137
|
-
}).pipe(Stream.unwrap,
|
|
138
|
-
|
|
139
|
-
onFailure: flow(Cause.findError, Result.match({
|
|
140
|
-
onSuccess: Effect.fail,
|
|
141
|
-
onFailure: () => Effect.void,
|
|
142
|
-
})),
|
|
143
|
-
})))));
|
|
144
|
-
yield* Deferred.await(audition);
|
|
177
|
+
}).pipe(Stream.unwrap, interrupt);
|
|
178
|
+
const state = Stream.fromPubSub(statePubsub).pipe(interrupt);
|
|
145
179
|
const encodingServices = yield* Effect.context();
|
|
146
|
-
|
|
180
|
+
yield* Deferred.await(audition);
|
|
181
|
+
const fnRaw = (_tag, value) => Effect.gen(function* () {
|
|
147
182
|
const exit = fiber.pollUnsafe();
|
|
148
183
|
if (exit) {
|
|
149
184
|
return yield* Exit.match(exit, {
|
|
@@ -155,82 +190,124 @@ const make = (client, build, replay) => Effect.gen(function* () {
|
|
|
155
190
|
});
|
|
156
191
|
}
|
|
157
192
|
const id = callId++;
|
|
158
|
-
const
|
|
159
|
-
|
|
193
|
+
const deferred = yield* Deferred.make();
|
|
194
|
+
const span = yield* Tracing.current;
|
|
195
|
+
const trace = span ? Tracing.toTraceEnvelope(span) : undefined;
|
|
196
|
+
inflights[id] = { deferred, span };
|
|
160
197
|
yield* send({
|
|
161
198
|
_tag: "F.Payload",
|
|
162
199
|
id,
|
|
163
200
|
payload: { _tag, value },
|
|
201
|
+
...(trace && { trace }),
|
|
164
202
|
});
|
|
165
|
-
return yield* Effect.raceFirst(Deferred.await(
|
|
166
|
-
onSuccess: () => new UnresolvedError()
|
|
203
|
+
return yield* Effect.raceFirst(Deferred.await(deferred), Fiber.await(fiber).pipe(Effect.flatMap((exit) => Exit.match(exit, {
|
|
204
|
+
onSuccess: () => new UnresolvedError(),
|
|
167
205
|
onFailure: flow(Cause.findError, Result.match({
|
|
168
206
|
onSuccess: Effect.fail,
|
|
169
|
-
onFailure: () => new UnresolvedError()
|
|
207
|
+
onFailure: () => new UnresolvedError(),
|
|
170
208
|
})),
|
|
171
209
|
}))));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
210
|
+
}).pipe(Boundary.span("fn", import.meta.url, {
|
|
211
|
+
kind: "client",
|
|
212
|
+
attributes: { _tag },
|
|
213
|
+
}), Effect.provide(encodingServices));
|
|
214
|
+
return { state, events, fnRaw, end };
|
|
215
|
+
}).pipe(Boundary.span("acquire", import.meta.url, {
|
|
216
|
+
attributes: { client: client.key },
|
|
217
|
+
}), Effect.annotateLogs("client", client.key)),
|
|
175
218
|
});
|
|
176
219
|
return rcr;
|
|
177
220
|
}).pipe(Layer.effect(client));
|
|
178
|
-
|
|
221
|
+
let clientId_;
|
|
222
|
+
const clientId = () => {
|
|
223
|
+
if (!clientId_) {
|
|
224
|
+
clientId_ = crypto.randomUUID();
|
|
225
|
+
}
|
|
226
|
+
return clientId_;
|
|
227
|
+
};
|
|
228
|
+
export const layerSocket = ({ client, reducers, url, protocols, replay, onConnect, }) => {
|
|
179
229
|
const { F, Actor } = client.protocol;
|
|
180
230
|
const encodeFPayload = encodeJsonString(F.Payload);
|
|
181
231
|
const decodeActor = decodeJsonString(Actor);
|
|
182
|
-
return make(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
return make({
|
|
233
|
+
client,
|
|
234
|
+
reducers,
|
|
235
|
+
onConnect,
|
|
236
|
+
replay,
|
|
237
|
+
build: Effect.gen(function* () {
|
|
238
|
+
const socket = yield* Socket.makeWebSocket(url ?? "/", {
|
|
239
|
+
protocols: [
|
|
240
|
+
"liminal",
|
|
241
|
+
clientId(),
|
|
242
|
+
Encoding.encodeBase64Url(client.key),
|
|
243
|
+
...(protocols ? Array.ensure(protocols) : []),
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
listen: Effect.fnUntraced(function* (publish) {
|
|
248
|
+
yield* socket
|
|
249
|
+
.runRaw((raw) => pipe(raw instanceof Uint8Array ? new TextDecoder().decode(raw) : raw, decodeActor, Effect.andThen(publish)))
|
|
250
|
+
.pipe(Effect.catchIf(Socket.isSocketError, Effect.fnUntraced(function* (cause) {
|
|
251
|
+
const { reason } = cause;
|
|
252
|
+
if (reason._tag === "SocketCloseError" && reason.code === 1000) {
|
|
253
|
+
return yield* publish({ _tag: "Disconnect" });
|
|
254
|
+
}
|
|
255
|
+
yield* Effect.annotateLogs(Effect.logDebug(`SocketErrored.${reason._tag}`), { cause });
|
|
256
|
+
return yield* new ConnectionError({ cause });
|
|
257
|
+
})));
|
|
258
|
+
}, Boundary.span("listen", import.meta.url)),
|
|
259
|
+
send: Effect.fnUntraced(function* (v) {
|
|
260
|
+
const write = yield* socket.writer;
|
|
261
|
+
const message = yield* encodeFPayload(v);
|
|
262
|
+
yield* write(message).pipe(Effect.catchTags({
|
|
263
|
+
SocketError: (cause) => new ConnectionError({ cause }),
|
|
264
|
+
}));
|
|
265
|
+
}, Boundary.span("send", import.meta.url), Effect.scoped),
|
|
266
|
+
};
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
export const layerWorker = ({ client, reducers, replay, onConnect, }) => {
|
|
271
|
+
const { Actor, Client: ClientM } = client.protocol;
|
|
272
|
+
const encodeClient = encodeJsonString(ClientM);
|
|
273
|
+
const decodeActor = decodeJsonString(Actor);
|
|
274
|
+
return make({
|
|
275
|
+
client,
|
|
276
|
+
reducers,
|
|
277
|
+
onConnect,
|
|
278
|
+
replay,
|
|
279
|
+
build: Effect.gen(function* () {
|
|
280
|
+
const platform = yield* Worker.WorkerPlatform;
|
|
281
|
+
const backing = yield* platform.spawn(0).pipe(Effect.catchTags({
|
|
282
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
283
|
+
}));
|
|
284
|
+
const send = (message) => encodeClient(message).pipe(Effect.flatMap((encoded) => backing.send(encoded)), Effect.catchTags({
|
|
285
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
286
|
+
}), Boundary.span("send", import.meta.url));
|
|
287
|
+
return {
|
|
288
|
+
listen: Effect.fnUntraced(function* (publish) {
|
|
289
|
+
const stop = yield* Deferred.make();
|
|
290
|
+
const audition = yield* encodeClient({
|
|
291
|
+
_tag: "Audition.Payload",
|
|
292
|
+
client: client.key,
|
|
293
|
+
});
|
|
294
|
+
yield* backing
|
|
295
|
+
.run(Effect.fnUntraced(function* (raw) {
|
|
296
|
+
const message = yield* decodeActor(raw);
|
|
297
|
+
yield* publish(message);
|
|
298
|
+
if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
|
|
299
|
+
yield* Deferred.succeed(stop, void 0);
|
|
300
|
+
}
|
|
301
|
+
}), {
|
|
302
|
+
onSpawn: backing.send(audition).pipe(Effect.orDie),
|
|
303
|
+
})
|
|
304
|
+
.pipe(Effect.raceFirst(Deferred.await(stop)), Effect.catchTags({
|
|
305
|
+
WorkerError: (cause) => new ConnectionError({ cause }),
|
|
306
|
+
}));
|
|
307
|
+
}, Boundary.span("listen", import.meta.url)),
|
|
308
|
+
send,
|
|
309
|
+
};
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
207
312
|
};
|
|
208
|
-
export const layerWorker = ({ client, replay, }) => make(client, Effect.gen(function* () {
|
|
209
|
-
const platform = yield* Worker.WorkerPlatform;
|
|
210
|
-
const backing = yield* platform
|
|
211
|
-
.spawn(0)
|
|
212
|
-
.pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
|
|
213
|
-
const send = (message) => backing.send(message).pipe(Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()), span("send"));
|
|
214
|
-
return {
|
|
215
|
-
listen: Effect.fnUntraced(function* (publish) {
|
|
216
|
-
const stop = yield* Deferred.make();
|
|
217
|
-
yield* backing
|
|
218
|
-
.run(Effect.fnUntraced(function* (message) {
|
|
219
|
-
yield* publish(message);
|
|
220
|
-
if (message._tag === "Disconnect" || message._tag === "Audition.Failure") {
|
|
221
|
-
yield* Deferred.succeed(stop, void 0);
|
|
222
|
-
}
|
|
223
|
-
}), {
|
|
224
|
-
onSpawn: backing
|
|
225
|
-
.send({
|
|
226
|
-
_tag: "Audition.Payload",
|
|
227
|
-
client: client.key,
|
|
228
|
-
})
|
|
229
|
-
.pipe(Effect.orDie),
|
|
230
|
-
})
|
|
231
|
-
.pipe(Effect.raceFirst(Deferred.await(stop)), Effect.catchTag("WorkerError", (cause) => new ConnectionError({ cause }).asEffect()));
|
|
232
|
-
}, span("listen")),
|
|
233
|
-
send,
|
|
234
|
-
};
|
|
235
|
-
}), replay);
|
|
236
313
|
//# sourceMappingURL=Client.js.map
|