@shivaedev/contract 0.19.0 → 0.20.0
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/CHANGELOG.md +19 -0
- package/README.md +149 -180
- package/dist/automaticLive.d.ts +8 -7
- package/dist/automaticLive.d.ts.map +1 -1
- package/dist/automaticLive.js +101 -51
- package/dist/automaticLive.js.map +1 -1
- package/dist/bind.d.ts +1 -0
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +4 -4
- package/dist/bind.js.map +1 -1
- package/dist/clientOptions.d.ts +6 -5
- package/dist/clientOptions.d.ts.map +1 -1
- package/dist/internal/clientOperations.d.ts +2 -2
- package/dist/internal/clientOperations.d.ts.map +1 -1
- package/dist/internal/clientOperations.js +10 -14
- package/dist/internal/clientOperations.js.map +1 -1
- package/dist/internal/queryReactivity.d.ts +1 -1
- package/dist/internal/queryReactivity.d.ts.map +1 -1
- package/dist/internal/queryReactivity.js +1 -2
- package/dist/internal/queryReactivity.js.map +1 -1
- package/dist/internal/queryResult.d.ts +3 -0
- package/dist/internal/queryResult.d.ts.map +1 -0
- package/dist/internal/queryResult.js +19 -0
- package/dist/internal/queryResult.js.map +1 -0
- package/dist/internal/{httpClient.d.ts → rpcClient.d.ts} +6 -4
- package/dist/internal/rpcClient.d.ts.map +1 -0
- package/dist/internal/rpcClient.js +31 -0
- package/dist/internal/rpcClient.js.map +1 -0
- package/dist/live.d.ts +1 -0
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +14 -1
- package/dist/live.js.map +1 -1
- package/dist/makeLiveServer.js +6 -4
- package/dist/makeLiveServer.js.map +1 -1
- package/package.json +3 -3
- package/src/automaticLive.ts +113 -70
- package/src/bind.ts +7 -6
- package/src/clientOptions.ts +6 -5
- package/src/internal/clientOperations.ts +14 -25
- package/src/internal/queryReactivity.ts +1 -7
- package/src/internal/queryResult.ts +19 -0
- package/src/internal/rpcClient.ts +53 -0
- package/src/live.ts +14 -1
- package/src/makeLiveServer.ts +6 -6
- package/dist/internal/httpClient.d.ts.map +0 -1
- package/dist/internal/httpClient.js +0 -13
- package/dist/internal/httpClient.js.map +0 -1
- package/src/internal/httpClient.ts +0 -21
package/dist/live.js
CHANGED
|
@@ -30,6 +30,9 @@ export function live(runtime, options) {
|
|
|
30
30
|
get.addFinalizer(() => {
|
|
31
31
|
active = false;
|
|
32
32
|
clear();
|
|
33
|
+
if (get.registry.getNodes().has(status)) {
|
|
34
|
+
update({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
|
|
35
|
+
}
|
|
33
36
|
});
|
|
34
37
|
function changed(changedKeys) {
|
|
35
38
|
const window = get.once(coalesceWindow);
|
|
@@ -73,6 +76,15 @@ export function live(runtime, options) {
|
|
|
73
76
|
get.once(options.resume);
|
|
74
77
|
get.subscribe(options.resume, resync);
|
|
75
78
|
}
|
|
79
|
+
let connected = false;
|
|
80
|
+
function reconcile() {
|
|
81
|
+
if (connected || options.initialResync !== false) {
|
|
82
|
+
resync();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
update({ needsResync: false });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
76
88
|
const attempt = Effect.suspend(() => {
|
|
77
89
|
let isFirst = true;
|
|
78
90
|
function observe(hint) {
|
|
@@ -81,11 +93,12 @@ export function live(runtime, options) {
|
|
|
81
93
|
update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
|
|
82
94
|
}
|
|
83
95
|
else if (isFirst || hint._tag === "Resync") {
|
|
84
|
-
|
|
96
|
+
reconcile();
|
|
85
97
|
}
|
|
86
98
|
else {
|
|
87
99
|
changed(hint.keys);
|
|
88
100
|
}
|
|
101
|
+
connected = true;
|
|
89
102
|
isFirst = false;
|
|
90
103
|
}
|
|
91
104
|
return options.stream.pipe(Stream.runForEach((hint) => Effect.sync(() => observe(hint))), Effect.scoped, Effect.catchCause((failure) => Effect.sync(() => update({ failure: Option.some(failure) }))), Effect.andThen(Effect.sync(() => update({ connection: "reconnecting", needsResync: true }))), Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second")));
|
package/dist/live.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9E,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAmB,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;IACvI,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;CACjD,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
|
package/dist/makeLiveServer.js
CHANGED
|
@@ -48,10 +48,12 @@ export const makeLiveServer = Effect.gen(function* () {
|
|
|
48
48
|
}));
|
|
49
49
|
yield* Queue.offer(queue, { _tag: "Resync" });
|
|
50
50
|
function authorizeHint(hint) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) => {
|
|
52
|
+
if (!allowed) {
|
|
53
|
+
return changedHint([], subscription?.scopes);
|
|
54
|
+
}
|
|
55
|
+
return hint._tag === "Resync" ? hint : changedHint(hint.keys, subscription?.scopes);
|
|
56
|
+
});
|
|
55
57
|
}
|
|
56
58
|
return Stream.fromQueue(queue).pipe(Stream.mapEffect(authorizeHint), Stream.filter((hint) => hint._tag === "Resync" || hint.keys.length > 0));
|
|
57
59
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Typed query and command contracts over native Effect RPC and reactivity",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@effect/platform-node": "4.0.5",
|
|
52
52
|
"@effect/vitest": "4.0.5",
|
|
53
|
-
"@shivaedev/quality": "0.
|
|
53
|
+
"@shivaedev/quality": "0.44.1",
|
|
54
54
|
"@shivaedev/test": "0.10.0",
|
|
55
55
|
"@shivaedev/test-sql": "0.10.2",
|
|
56
|
-
"@shivaedev/test-story": "0.
|
|
56
|
+
"@shivaedev/test-story": "0.23.0",
|
|
57
57
|
"@types/node": "24.19.2",
|
|
58
58
|
"effect": "4.0.5",
|
|
59
59
|
"esbuild": "0.28.2",
|
package/src/automaticLive.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { type Duration, Effect,
|
|
1
|
+
import { type Duration, Effect, Option, Stream } from "effect";
|
|
2
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
-
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
4
|
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
5
|
-
import {
|
|
5
|
+
import type { Key } from "@shivaedev/changes/keys.ts";
|
|
6
6
|
import type { BindingOwner } from "#internal/bindingOwner.ts";
|
|
7
7
|
import { localQueryKeys, queryHint } from "#internal/queryHints.ts";
|
|
8
|
+
import { queryResult } from "#internal/queryResult.ts";
|
|
9
|
+
import { socketIdentity } from "#internal/rpcClient.ts";
|
|
8
10
|
import { type LiveStatus, live } from "#live.ts";
|
|
11
|
+
import { queryCoalesceWindow } from "#queryCoalesceWindow.ts";
|
|
9
12
|
import { type ResumeSource, type ResumeWindow, resumeSignal } from "#resume.ts";
|
|
10
13
|
import type { changesRpc, QueryReference } from "#serverMetadata.ts";
|
|
11
14
|
|
|
@@ -16,83 +19,123 @@ export interface LiveBound {
|
|
|
16
19
|
readonly [LiveConnection]: {
|
|
17
20
|
readonly coalesceWindow: Atom.Writable<Duration.Input>;
|
|
18
21
|
readonly connection: Atom.Atom<unknown>;
|
|
22
|
+
readonly identity?: Atom.Writable<string | undefined>;
|
|
19
23
|
readonly status: Atom.Atom<LiveStatus<unknown>>;
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export function automaticLive(service: ChangesClient, name: string, owner?: BindingOwner, resume?: ResumeSource) {
|
|
31
|
-
const eager = owner === undefined ? Effect.runSync(SubscriptionRef.make<ReadonlyMap<object, Tracked>>(new Map())) : undefined;
|
|
32
|
-
const known = new Set<string>();
|
|
33
|
-
const tracked = Atom.keepAlive(Atom.make(() => eager ?? Effect.runSync(SubscriptionRef.make<ReadonlyMap<object, Tracked>>(new Map()))));
|
|
34
|
-
function track(registry: AtomRegistry.AtomRegistry | undefined, reference: QueryReference, keys: readonly Key[]) {
|
|
35
|
-
const fresh =
|
|
36
|
-
eager === undefined
|
|
37
|
-
? keys
|
|
38
|
-
: keys.filter((key) => {
|
|
39
|
-
const id = readKeys([key])[0] ?? "";
|
|
40
|
-
if (known.has(id)) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
known.add(id);
|
|
44
|
-
return true;
|
|
45
|
-
});
|
|
46
|
-
if (fresh.length === 0) {
|
|
47
|
-
return () => undefined;
|
|
48
|
-
}
|
|
49
|
-
const token = {};
|
|
50
|
-
const state = eager ?? registry?.get(tracked);
|
|
51
|
-
if (state === undefined) {
|
|
52
|
-
throw new Error("Session query tracking requires its owning registry");
|
|
53
|
-
}
|
|
54
|
-
Effect.runSync(SubscriptionRef.update(state, (current) => new Map([...current, [token, { keys: fresh, reference }]])));
|
|
55
|
-
if (eager !== undefined) {
|
|
56
|
-
return () => undefined;
|
|
29
|
+
export function automaticLive(service: ChangesClient, name: string, owner?: BindingOwner, resume?: ResumeSource, enabled = true) {
|
|
30
|
+
function assertOwner(get: Atom.AtomContext) {
|
|
31
|
+
if (owner !== undefined && owner.registry !== get.registry) {
|
|
32
|
+
throw new Error("A session-scoped query belongs to its binding registry");
|
|
57
33
|
}
|
|
58
|
-
return () =>
|
|
59
|
-
Effect.runSync(
|
|
60
|
-
SubscriptionRef.update(state, (current) => {
|
|
61
|
-
const next = new Map(current);
|
|
62
|
-
next.delete(token);
|
|
63
|
-
return next;
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
34
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
35
|
+
const coalesceWindow = Atom.keepAlive(Atom.make<Duration.Input>("20 millis"));
|
|
36
|
+
const statuses = Atom.make<ReadonlyMap<object, LiveStatus<unknown>>>(new Map());
|
|
37
|
+
const status = Atom.map(statuses, (current): LiveStatus<unknown> => {
|
|
38
|
+
const values = [...current.values()];
|
|
39
|
+
let connection: LiveStatus<unknown>["connection"] = "live";
|
|
40
|
+
if (values.some((value) => value.connection === "connecting")) {
|
|
41
|
+
connection = "connecting";
|
|
70
42
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
keyContext: owner?.context,
|
|
81
|
-
resume: signal,
|
|
82
|
-
resyncKeys: (registry) => [...Effect.runSync(SubscriptionRef.get(registry.get(tracked))).values()].flatMap(({ keys }) => localQueryKeys(keys)),
|
|
83
|
-
stream: Stream.unwrap(
|
|
84
|
-
Effect.map(AtomRegistry.AtomRegistry, (registry) =>
|
|
85
|
-
Stream.zipLatest(SubscriptionRef.changes(registry.get(tracked)), AtomRegistry.toStream(registry, signal)).pipe(
|
|
86
|
-
Stream.switchMap(([current]) => subscribe(current)),
|
|
87
|
-
),
|
|
88
|
-
),
|
|
89
|
-
),
|
|
43
|
+
if (values.some((value) => value.connection === "reconnecting")) {
|
|
44
|
+
connection = "reconnecting";
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
connection,
|
|
48
|
+
failure: values.find((value) => Option.isSome(value.failure))?.failure ?? Option.none(),
|
|
49
|
+
needsResync: values.some((value) => value.needsResync),
|
|
50
|
+
pending: values.reduce((sum, value) => sum + value.pending, 0),
|
|
51
|
+
};
|
|
90
52
|
});
|
|
53
|
+
const signal = resumeSignal({ ...(typeof window === "undefined" ? {} : { window }), ...(resume === undefined ? {} : { native: resume }) });
|
|
91
54
|
const connection = Atom.make((get) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return get(subscription.connection);
|
|
55
|
+
assertOwner(get);
|
|
56
|
+
get.mount(service.runtime);
|
|
57
|
+
return get(status);
|
|
96
58
|
}).pipe(Atom.setIdleTTL(0));
|
|
97
|
-
|
|
59
|
+
function query<A, E>(
|
|
60
|
+
atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>,
|
|
61
|
+
reference: QueryReference,
|
|
62
|
+
keys: readonly Key[],
|
|
63
|
+
equivalent: (left: A, right: A) => boolean,
|
|
64
|
+
) {
|
|
65
|
+
if (!enabled) {
|
|
66
|
+
return atom;
|
|
67
|
+
}
|
|
68
|
+
const ready = Atom.make(false);
|
|
69
|
+
const subscription = live(service.runtime, {
|
|
70
|
+
initialResync: false,
|
|
71
|
+
keyContext: owner?.context,
|
|
72
|
+
resume: signal,
|
|
73
|
+
resyncKeys: localQueryKeys(keys),
|
|
74
|
+
stream: Stream.unwrap(service.use((client) => Effect.succeed(client(`${name}.$changes`, { queries: [reference] })))).pipe(
|
|
75
|
+
Stream.map((hint) => queryHint(hint, [{ keys }])),
|
|
76
|
+
),
|
|
77
|
+
});
|
|
78
|
+
const gated = Atom.readable(
|
|
79
|
+
(get) => {
|
|
80
|
+
assertOwner(get);
|
|
81
|
+
const registry = get.registry;
|
|
82
|
+
get.set(ready, false);
|
|
83
|
+
get.once(queryCoalesceWindow(gated));
|
|
84
|
+
get.subscribe(queryCoalesceWindow(gated), (value) => get.set(queryCoalesceWindow(atom), value), { immediate: true });
|
|
85
|
+
const token = {};
|
|
86
|
+
function observe(value: LiveStatus<unknown>) {
|
|
87
|
+
registry.update(statuses, (current) => new Map([...current, [token, value]]));
|
|
88
|
+
if (value.connection === "live") {
|
|
89
|
+
registry.set(ready, true);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
get.addFinalizer(() => {
|
|
93
|
+
if (registry.getNodes().has(statuses)) {
|
|
94
|
+
registry.update(statuses, (current) => {
|
|
95
|
+
const next = new Map(current);
|
|
96
|
+
next.delete(token);
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
get.addFinalizer(registry.subscribe(subscription.status, observe, { immediate: true }));
|
|
102
|
+
get.addFinalizer(registry.subscribe(coalesceWindow, (value) => registry.set(subscription.coalesceWindow, value), { immediate: true }));
|
|
103
|
+
let started = false;
|
|
104
|
+
function start() {
|
|
105
|
+
if (started) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
started = true;
|
|
109
|
+
get.refresh(atom);
|
|
110
|
+
const publish = queryResult<A, E>(equivalent, (value) => get.setSelf(value));
|
|
111
|
+
get.subscribe(atom, publish);
|
|
112
|
+
publish(get.once(atom));
|
|
113
|
+
}
|
|
114
|
+
get.once(ready);
|
|
115
|
+
get.subscribe(ready, (value) => {
|
|
116
|
+
if (value) {
|
|
117
|
+
start();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
get.subscribe(subscription.status, (value) => {
|
|
121
|
+
if (!started && Option.isSome(value.failure)) {
|
|
122
|
+
get.setSelf(AsyncResult.failure(value.failure.value));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
get.mount(subscription.connection);
|
|
126
|
+
if (get.once(ready)) {
|
|
127
|
+
start();
|
|
128
|
+
return get.once(atom);
|
|
129
|
+
}
|
|
130
|
+
return AsyncResult.initial();
|
|
131
|
+
},
|
|
132
|
+
(refresh) => refresh(atom),
|
|
133
|
+
).pipe(Atom.setIdleTTL(0));
|
|
134
|
+
Object.assign(gated, { initialValueTarget: atom });
|
|
135
|
+
if (Atom.isSerializable(atom)) {
|
|
136
|
+
Object.assign(gated, { [Atom.SerializableTypeId]: atom[Atom.SerializableTypeId] });
|
|
137
|
+
}
|
|
138
|
+
return gated;
|
|
139
|
+
}
|
|
140
|
+
return { query, subscription: { coalesceWindow, connection, identity: socketIdentity, status } };
|
|
98
141
|
}
|
package/src/bind.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { ClientOptions, MadeClient } from "#clientOptions.ts";
|
|
|
12
12
|
import type { Contract, Declared, Tag } from "#contract.ts";
|
|
13
13
|
import { bindingOwner } from "#internal/bindingOwner.ts";
|
|
14
14
|
import { boundCommand, boundQuery, boundStream } from "#internal/clientOperations.ts";
|
|
15
|
-
import { type
|
|
15
|
+
import { type RpcSource, rpcClient } from "#internal/rpcClient.ts";
|
|
16
16
|
import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
|
|
17
17
|
import type { MissingReadPolicy } from "#readPolicyErrors.ts";
|
|
18
18
|
import type { ResumeSource } from "#resume.ts";
|
|
@@ -71,6 +71,7 @@ type MatchingClient<TRpcs extends Rpc.Any, TClientRpcs extends Rpc.Any> = [TRpcs
|
|
|
71
71
|
: "Client must implement the contract RPCs";
|
|
72
72
|
|
|
73
73
|
interface BindOptions {
|
|
74
|
+
readonly live?: boolean;
|
|
74
75
|
readonly registry?: AtomRegistry.AtomRegistry;
|
|
75
76
|
readonly resume?: ResumeSource;
|
|
76
77
|
}
|
|
@@ -105,10 +106,10 @@ function bindApi(
|
|
|
105
106
|
) {
|
|
106
107
|
const { name, queries, commands, streams, keyScope } = contract.declaration;
|
|
107
108
|
const owner = bindingOwner(keyScope, options?.registry);
|
|
108
|
-
const automatic = automaticLive(service, name, owner, options?.resume);
|
|
109
|
+
const automatic = automaticLive(service, name, owner, options?.resume, options?.live);
|
|
109
110
|
return Object.assign(
|
|
110
111
|
Object.fromEntries([
|
|
111
|
-
...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic
|
|
112
|
+
...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic, owner)] as const),
|
|
112
113
|
...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command, owner)] as const),
|
|
113
114
|
...streams.map((stream) => [stream.name, boundStream(service, `${name}.${stream.name}`)] as const),
|
|
114
115
|
]),
|
|
@@ -131,15 +132,15 @@ export function makeClient<
|
|
|
131
132
|
: { readonly registry?: AtomRegistry.AtomRegistry }),
|
|
132
133
|
): MadeClient<TName, TQueries, TCommands, TRpcs, TStreams, TExtra>;
|
|
133
134
|
export function makeClient(
|
|
134
|
-
contract:
|
|
135
|
+
contract: RpcSource & {
|
|
135
136
|
readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[], "session" | undefined>;
|
|
136
137
|
},
|
|
137
138
|
options: ClientOptions & {
|
|
138
|
-
readonly group?:
|
|
139
|
+
readonly group?: RpcSource;
|
|
139
140
|
readonly registry?: AtomRegistry.AtomRegistry;
|
|
140
141
|
},
|
|
141
142
|
): Layer.Layer<never> {
|
|
142
|
-
const connection =
|
|
143
|
+
const connection = rpcClient(contract.declaration.name, options.group ?? contract, options);
|
|
143
144
|
const api = bindApi(contract, connection.Client, options);
|
|
144
145
|
return Object.assign(connection, { api });
|
|
145
146
|
}
|
package/src/clientOptions.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { Context, Effect, Layer, Scope } from "effect";
|
|
2
|
-
import type { HttpClient } from "effect/http/HttpClient";
|
|
3
2
|
import type * as Atom from "effect/reactivity/Atom";
|
|
4
3
|
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
5
|
-
import type { Rpc, RpcClient
|
|
4
|
+
import type { Rpc, RpcClient } from "effect/rpc";
|
|
6
5
|
import type { RpcClientError } from "effect/rpc/RpcClientError";
|
|
6
|
+
import type * as Socket from "effect/socket/Socket";
|
|
7
7
|
import type { LiveBound } from "#automaticLive.ts";
|
|
8
8
|
import type { Bound } from "#bind.ts";
|
|
9
9
|
import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
|
|
10
10
|
|
|
11
11
|
export interface ClientOptions {
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
12
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
13
|
+
readonly live?: boolean;
|
|
14
|
+
readonly protocol?: Layer.Layer<RpcClient.Protocol>;
|
|
15
15
|
readonly url: string;
|
|
16
|
+
readonly webSocketConstructor?: Layer.Layer<Socket.WebSocketConstructor>;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export type ClientService<TName extends string, TRpcs extends Rpc.Any> = Context.ServiceClass.Shape<
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Option,
|
|
1
|
+
import { Effect, Option, Schema, Stream } from "effect";
|
|
2
2
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
3
3
|
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
4
4
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
@@ -13,9 +13,6 @@ import { queryReactivity } from "./queryReactivity.ts";
|
|
|
13
13
|
|
|
14
14
|
type ErasedClient = AtomRpc.AtomRpcClient<unknown, string, Rpc.Rpc<string, Schema.Top, Schema.Top, Schema.Top>>;
|
|
15
15
|
|
|
16
|
-
const queryIds = new WeakMap<object, number>();
|
|
17
|
-
let nextQueryId = 0;
|
|
18
|
-
|
|
19
16
|
type ErasedStreamingClient = AtomRpc.AtomRpcClient<
|
|
20
17
|
unknown,
|
|
21
18
|
string,
|
|
@@ -26,14 +23,9 @@ function run<A, E>(service: ErasedClient, effect: Effect.Effect<A, E, unknown>,
|
|
|
26
23
|
return owner === undefined ? effect : Effect.suspend(() => AtomRegistry.getResult(owner.registry, service.runtime.atom(effect)));
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
export function boundQuery(
|
|
30
|
-
service: ErasedClient,
|
|
31
|
-
tag: string,
|
|
32
|
-
query: QueryShape,
|
|
33
|
-
track: ReturnType<typeof automaticLive>["track"],
|
|
34
|
-
owner?: BindingOwner,
|
|
35
|
-
) {
|
|
26
|
+
export function boundQuery(service: ErasedClient, tag: string, query: QueryShape, automatic: ReturnType<typeof automaticLive>, owner?: BindingOwner) {
|
|
36
27
|
const reactive = queryReactivity(service.runtime);
|
|
28
|
+
const cached = new WeakMap<ReturnType<typeof service.query>, ReturnType<typeof service.query>>();
|
|
37
29
|
return {
|
|
38
30
|
query: (payload: unknown, options: QueryOptions = {}) => {
|
|
39
31
|
if (query.readPolicy === undefined) {
|
|
@@ -42,26 +34,23 @@ export function boundQuery(
|
|
|
42
34
|
);
|
|
43
35
|
}
|
|
44
36
|
const atom = service.query(tag, payload, options);
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
nextQueryId += 1;
|
|
49
|
-
queryIds.set(atom, id);
|
|
37
|
+
const existing = cached.get(atom);
|
|
38
|
+
if (existing !== undefined) {
|
|
39
|
+
return existing;
|
|
50
40
|
}
|
|
51
41
|
const local = query.reads(payload, Option.none());
|
|
52
|
-
const keys = [...local, { _tag: "List" as const, collection: `@query/${tag}/${
|
|
42
|
+
const keys = [...local, { _tag: "List" as const, collection: `@query/${tag}/${crypto.randomUUID()}` }];
|
|
53
43
|
const dependencies =
|
|
54
44
|
local.length === 0 ? [...keys, { _tag: "List" as const, collection: `@query/${tag.slice(0, tag.lastIndexOf("."))}` }] : keys;
|
|
55
45
|
const reference = { payload, query: query.name };
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
readKeys(dependencies, owner?.context),
|
|
62
|
-
(registry) => (owner === undefined ? () => undefined : track(registry, reference, keys)),
|
|
63
|
-
owner?.registry,
|
|
46
|
+
const result = automatic.query(
|
|
47
|
+
reactive(atom, readKeys(dependencies, owner?.context), owner?.registry),
|
|
48
|
+
reference,
|
|
49
|
+
keys,
|
|
50
|
+
Schema.toEquivalence(query.success),
|
|
64
51
|
);
|
|
52
|
+
cached.set(atom, result);
|
|
53
|
+
return result;
|
|
65
54
|
},
|
|
66
55
|
run: (payload: unknown) =>
|
|
67
56
|
run(
|
|
@@ -10,12 +10,7 @@ export function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>) {
|
|
|
10
10
|
const service = runtime.atom(Effect.service(Reactivity.Reactivity));
|
|
11
11
|
type Query = Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>;
|
|
12
12
|
const cached = new WeakMap<Query, Query>();
|
|
13
|
-
return (
|
|
14
|
-
atom: Query,
|
|
15
|
-
keys: readonly string[],
|
|
16
|
-
track: (registry: AtomRegistry.AtomRegistry) => () => void,
|
|
17
|
-
owner?: AtomRegistry.AtomRegistry,
|
|
18
|
-
): Query => {
|
|
13
|
+
return (atom: Query, keys: readonly string[], owner?: AtomRegistry.AtomRegistry): Query => {
|
|
19
14
|
const existing = cached.get(atom);
|
|
20
15
|
if (existing !== undefined) {
|
|
21
16
|
return existing;
|
|
@@ -35,7 +30,6 @@ export function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>) {
|
|
|
35
30
|
return get(source);
|
|
36
31
|
}
|
|
37
32
|
const reactivity = context.value;
|
|
38
|
-
get.addFinalizer(track(get.registry));
|
|
39
33
|
let pending: (() => boolean) | undefined;
|
|
40
34
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
41
35
|
function clear() {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
|
+
|
|
3
|
+
export function queryResult<A, E>(equivalent: (left: A, right: A) => boolean, publish: (value: AsyncResult.AsyncResult<A, E>) => void) {
|
|
4
|
+
let settled: AsyncResult.Success<A, E> | undefined;
|
|
5
|
+
return (value: AsyncResult.AsyncResult<A, E>) => {
|
|
6
|
+
if (!AsyncResult.isSuccess(value)) {
|
|
7
|
+
publish(value);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (settled !== undefined && equivalent(settled.value, value.value)) {
|
|
11
|
+
publish(value.waiting ? AsyncResult.waiting(settled) : settled);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!value.waiting) {
|
|
15
|
+
settled = value;
|
|
16
|
+
}
|
|
17
|
+
publish(value);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Context, Effect, Layer } from "effect";
|
|
2
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
4
|
+
import { type Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/rpc";
|
|
5
|
+
import * as Socket from "effect/socket/Socket";
|
|
6
|
+
import type { ClientOptions } from "#clientOptions.ts";
|
|
7
|
+
|
|
8
|
+
export interface RpcSource {
|
|
9
|
+
readonly annotations: Context.Context<never>;
|
|
10
|
+
readonly requests: ReadonlyMap<string, Rpc.Any>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function rpcClient(name: string, source: RpcSource, options: ClientOptions) {
|
|
14
|
+
const group = RpcGroup.make(...source.requests.values()).annotateMerge(source.annotations);
|
|
15
|
+
function sharedProtocol(identity?: string) {
|
|
16
|
+
return socketProtocol({
|
|
17
|
+
headers: options.headers,
|
|
18
|
+
identity,
|
|
19
|
+
url: options.url.replace(/^http/u, "ws"),
|
|
20
|
+
webSocketConstructor: options.webSocketConstructor,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const protocol = options.protocol ?? sharedProtocol();
|
|
24
|
+
const Client = AtomRpc.Service<unknown>()(`${name}/Client`, {
|
|
25
|
+
group,
|
|
26
|
+
protocol: options.protocol ?? ((get) => sharedProtocol(get(socketIdentity))),
|
|
27
|
+
});
|
|
28
|
+
const client = Effect.flatMap(Layer.build(protocol), (context) => RpcClient.make(group).pipe(Effect.provideContext(context)));
|
|
29
|
+
const layer = Layer.effect(Client, RpcClient.make(group, { flatten: true })).pipe(Layer.provide(protocol));
|
|
30
|
+
return Object.assign(layer, { Client, client, runtime: Client.runtime });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const socketIdentity = Atom.make<string | undefined>(undefined);
|
|
34
|
+
|
|
35
|
+
const socketMemoMap = Layer.makeMemoMapUnsafe();
|
|
36
|
+
const socketProtocol = Atom.family(
|
|
37
|
+
(options: {
|
|
38
|
+
readonly url: string;
|
|
39
|
+
readonly headers: ClientOptions["headers"];
|
|
40
|
+
readonly identity: string | undefined;
|
|
41
|
+
readonly webSocketConstructor: ClientOptions["webSocketConstructor"];
|
|
42
|
+
}) => {
|
|
43
|
+
const protocol = RpcClient.layerProtocolSocket().pipe(
|
|
44
|
+
Layer.provide(
|
|
45
|
+
Socket.layerWebSocket(options.url, { headers: options.headers }).pipe(
|
|
46
|
+
Layer.provide(options.webSocketConstructor ?? Socket.layerWebSocketConstructorGlobal),
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
Layer.provide(RpcSerialization.layerNdjson),
|
|
50
|
+
);
|
|
51
|
+
return Layer.effectContext(Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(protocol, socketMemoMap, scope)));
|
|
52
|
+
},
|
|
53
|
+
);
|
package/src/live.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface LiveStatus<E> {
|
|
|
18
18
|
}
|
|
19
19
|
export interface LiveOptions<E, R> {
|
|
20
20
|
readonly coalesceWindow?: Duration.Input;
|
|
21
|
+
readonly initialResync?: boolean;
|
|
21
22
|
readonly keyContext?: KeyContext | undefined;
|
|
22
23
|
readonly resume?: Atom.Atom<unknown>;
|
|
23
24
|
readonly resyncKeys: readonly Key[] | ((registry: AtomRegistry.AtomRegistry) => readonly Key[]);
|
|
@@ -50,6 +51,9 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
50
51
|
get.addFinalizer(() => {
|
|
51
52
|
active = false;
|
|
52
53
|
clear();
|
|
54
|
+
if (get.registry.getNodes().has(status)) {
|
|
55
|
+
update({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
|
|
56
|
+
}
|
|
53
57
|
});
|
|
54
58
|
function changed(changedKeys: readonly Key[]) {
|
|
55
59
|
const window = get.once(coalesceWindow);
|
|
@@ -93,6 +97,14 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
93
97
|
get.once(options.resume);
|
|
94
98
|
get.subscribe(options.resume, resync);
|
|
95
99
|
}
|
|
100
|
+
let connected = false;
|
|
101
|
+
function reconcile() {
|
|
102
|
+
if (connected || options.initialResync !== false) {
|
|
103
|
+
resync();
|
|
104
|
+
} else {
|
|
105
|
+
update({ needsResync: false });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
96
108
|
const attempt = Effect.suspend(() => {
|
|
97
109
|
let isFirst = true;
|
|
98
110
|
function observe(hint: LiveHint) {
|
|
@@ -100,10 +112,11 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
100
112
|
if (get.once(paused)) {
|
|
101
113
|
update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
|
|
102
114
|
} else if (isFirst || hint._tag === "Resync") {
|
|
103
|
-
|
|
115
|
+
reconcile();
|
|
104
116
|
} else {
|
|
105
117
|
changed(hint.keys);
|
|
106
118
|
}
|
|
119
|
+
connected = true;
|
|
107
120
|
isFirst = false;
|
|
108
121
|
}
|
|
109
122
|
return options.stream.pipe(
|
package/src/makeLiveServer.ts
CHANGED
|
@@ -56,12 +56,12 @@ export const makeLiveServer = Effect.gen(function* () {
|
|
|
56
56
|
);
|
|
57
57
|
yield* Queue.offer(queue, { _tag: "Resync" });
|
|
58
58
|
function authorizeHint(hint: LiveHint) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
59
|
+
return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) => {
|
|
60
|
+
if (!allowed) {
|
|
61
|
+
return changedHint([], subscription?.scopes);
|
|
62
|
+
}
|
|
63
|
+
return hint._tag === "Resync" ? hint : changedHint(hint.keys, subscription?.scopes);
|
|
64
|
+
});
|
|
65
65
|
}
|
|
66
66
|
return Stream.fromQueue(queue).pipe(
|
|
67
67
|
Stream.mapEffect(authorizeHint),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/internal/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,KAAK,GAAG,EAAyC,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;CAChD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa;;;;;;;;;;;EASjF"}
|