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