@synapse-sdk/client 0.3.4 → 0.3.6
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/dist/calls.d.ts +5 -5
- package/dist/calls.js +19 -21
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/scene.d.ts +22 -11
- package/dist/scene.js +297 -218
- package/dist/session.js +1 -1
- package/dist/store.d.ts +12 -4
- package/dist/store.js +99 -69
- package/dist/synapse.d.ts +3 -6
- package/dist/synapse.js +2 -5
- package/dist/types.d.ts +35 -24
- package/package.json +2 -2
package/dist/session.js
CHANGED
|
@@ -429,7 +429,7 @@ export async function withRoot(url, signal) {
|
|
|
429
429
|
throw timeout.aborted ? new SynapseError("connection_lost", { message: `root helper at ${addr} did not answer`, source: "transport" }) : lost(describeError(e));
|
|
430
430
|
}
|
|
431
431
|
if (!isRuntimeRoot(root)) {
|
|
432
|
-
throw new SynapseError("invalid_input", { message: `${url} names no runtime (its /root helper returned ${JSON.stringify(root.slice(0, 80))}); give the full path, e.g. https
|
|
432
|
+
throw new SynapseError("invalid_input", { message: `${url} names no runtime (its /root helper returned ${JSON.stringify(root.slice(0, 80))}); give the full path, e.g. https://<relay>/cluster/<cluster>/runtimes/<runtime_id>`, source: "sdk" });
|
|
433
433
|
}
|
|
434
434
|
u.pathname = `/${root}`;
|
|
435
435
|
return u.toString();
|
package/dist/store.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare const ENDED_CONNECTION_LOST = "connection_lost";
|
|
|
9
9
|
export declare const ENDED_CLIENT = "client";
|
|
10
10
|
/** A watcher this many batches behind is ended, never grown. */
|
|
11
11
|
export declare const STORE_FEED_CAP = 256;
|
|
12
|
+
/** How long an incarnation the open's reply named has to show up on the announcement cursor (no "live" marker in @moq/net's JS cursor). */
|
|
13
|
+
export declare const ANNOUNCE_WITHIN_MS = 1000;
|
|
12
14
|
/** The mirror and what the runtime said about the namespace; written by the task and its reader only. */
|
|
13
15
|
export declare class State {
|
|
14
16
|
#private;
|
|
@@ -24,7 +26,8 @@ export declare class State {
|
|
|
24
26
|
onDeath: StoreOnDeath | undefined;
|
|
25
27
|
absent: boolean;
|
|
26
28
|
loaded: boolean;
|
|
27
|
-
|
|
29
|
+
/** A diff skipped a version (not applied; the track is read again from its snapshot): the highest version seen then. Cleared once the copy is back at it (PLAN v0.34 §2.2). */
|
|
30
|
+
gap: number | undefined;
|
|
28
31
|
pending: number;
|
|
29
32
|
ended: string | undefined;
|
|
30
33
|
subscribe(l: () => void): () => void;
|
|
@@ -32,7 +35,11 @@ export declare class State {
|
|
|
32
35
|
/** A feed its reader has left: it is closed and gets nothing more. */
|
|
33
36
|
unwatch(q: AsyncQueue<StoreChange[]>): void;
|
|
34
37
|
applySnapshot(s: StoreSnapshot): void;
|
|
35
|
-
|
|
38
|
+
/**
|
|
39
|
+
* One diff, applied when it is the next version. Otherwise it is not applied (`false`): a gap, `synced` false until the
|
|
40
|
+
* copy is back at the version seen; the reader reads the track again from its snapshot (PLAN v0.34 §2.2).
|
|
41
|
+
*/
|
|
42
|
+
applyDiff(d: StoreDiff, warn: Warn): boolean;
|
|
36
43
|
/** Nothing of the incarnation is known until the runtime answers the next open. */
|
|
37
44
|
reset(): void;
|
|
38
45
|
opened(a: StoreAccess | undefined): void;
|
|
@@ -140,11 +147,12 @@ export declare class StoreHandleImpl implements StoreHandle {
|
|
|
140
147
|
destroy(): Promise<void>;
|
|
141
148
|
/** `for await (const value of store.watch())`: the whole value after each change; `store.watch(key)`: that key's value each time it changes. Ends with the handle; leaving the loop removes the feed. */
|
|
142
149
|
watch(key?: string): AsyncGenerator<unknown, void, undefined>;
|
|
143
|
-
/** The whole value after its next change (`store_gone` once this handle is closed
|
|
150
|
+
/** The whole value after its next change; `changed(key)`: that key's value after its next change (`undefined` after a delete), as `watch(key)` yields it. `store_gone` once this handle is closed. Armed at the call. */
|
|
144
151
|
changed(): Promise<Record<string, unknown>>;
|
|
152
|
+
changed(key: string): Promise<unknown>;
|
|
145
153
|
/** `cb` with the whole value on every change of the value or the status (the framework bridge, React's `useStore`); returns the remover. */
|
|
146
154
|
onChange(cb: (value: Record<string, unknown>) => void): () => void;
|
|
147
|
-
/** Waits for the namespace to be in (the
|
|
155
|
+
/** Waits for the namespace to be in: the value current as of the open (the version the runtime answered applied), or the namespace absent. */
|
|
148
156
|
ready(): Promise<void>;
|
|
149
157
|
close(): void;
|
|
150
158
|
[Symbol.asyncDispose](): Promise<void>;
|
package/dist/store.js
CHANGED
|
@@ -21,7 +21,7 @@ const ENDED_STORE_GONE = "store_gone";
|
|
|
21
21
|
/** A watcher this many batches behind is ended, never grown. */
|
|
22
22
|
export const STORE_FEED_CAP = 256;
|
|
23
23
|
/** How long an incarnation the open's reply named has to show up on the announcement cursor (no "live" marker in @moq/net's JS cursor). */
|
|
24
|
-
const ANNOUNCE_WITHIN_MS = 1000;
|
|
24
|
+
export const ANNOUNCE_WITHIN_MS = 1000;
|
|
25
25
|
const isFinal = (reason) => reason === ENDED_DESTROYED;
|
|
26
26
|
const gone = (name, why) => new SynapseError("store_gone", `store ${JSON.stringify(name)}: ${why}`);
|
|
27
27
|
// ------------------------------------------------------------------------------------- state
|
|
@@ -39,7 +39,8 @@ export class State {
|
|
|
39
39
|
onDeath;
|
|
40
40
|
absent = false;
|
|
41
41
|
loaded = false;
|
|
42
|
-
|
|
42
|
+
/** A diff skipped a version (not applied; the track is read again from its snapshot): the highest version seen then. Cleared once the copy is back at it (PLAN v0.34 §2.2). */
|
|
43
|
+
gap;
|
|
43
44
|
pending = 0;
|
|
44
45
|
ended;
|
|
45
46
|
#listeners = new Set();
|
|
@@ -92,13 +93,22 @@ export class State {
|
|
|
92
93
|
this.onDeath = s.on_death;
|
|
93
94
|
this.absent = false;
|
|
94
95
|
this.loaded = true;
|
|
95
|
-
this
|
|
96
|
+
this.#heal();
|
|
96
97
|
this.#emit(records);
|
|
97
98
|
}
|
|
99
|
+
/** A gap is over once the copy is back at the highest version seen when it opened. */
|
|
100
|
+
#heal() { if (this.gap !== undefined && this.version >= this.gap)
|
|
101
|
+
this.gap = undefined; }
|
|
102
|
+
/**
|
|
103
|
+
* One diff, applied when it is the next version. Otherwise it is not applied (`false`): a gap, `synced` false until the
|
|
104
|
+
* copy is back at the version seen; the reader reads the track again from its snapshot (PLAN v0.34 §2.2).
|
|
105
|
+
*/
|
|
98
106
|
applyDiff(d, warn) {
|
|
99
107
|
if (d.version !== this.version + 1) {
|
|
100
|
-
warn(`store: diff out of sequence (have ${this.version}, got ${d.version})
|
|
101
|
-
this.gap =
|
|
108
|
+
warn(`store: diff out of sequence (have ${this.version}, got ${d.version}): not applied; reading the track again from its snapshot`);
|
|
109
|
+
this.gap = Math.max(this.gap ?? d.version, d.version);
|
|
110
|
+
this.#notify();
|
|
111
|
+
return false;
|
|
102
112
|
}
|
|
103
113
|
const records = [];
|
|
104
114
|
const flat = new Map(this.flat);
|
|
@@ -119,7 +129,9 @@ export class State {
|
|
|
119
129
|
}
|
|
120
130
|
this.flat = flat;
|
|
121
131
|
this.version = d.version;
|
|
132
|
+
this.#heal();
|
|
122
133
|
this.#emit(records);
|
|
134
|
+
return true;
|
|
123
135
|
}
|
|
124
136
|
#clear() {
|
|
125
137
|
const records = [...this.rows].map(([key, r]) => ({ kind: "delete", key, previousValue: r.v, by: undefined }));
|
|
@@ -135,7 +147,7 @@ export class State {
|
|
|
135
147
|
this.epoch = undefined;
|
|
136
148
|
this.version = 0;
|
|
137
149
|
this.loaded = false;
|
|
138
|
-
this.gap =
|
|
150
|
+
this.gap = undefined;
|
|
139
151
|
this.mode = undefined;
|
|
140
152
|
this.owner = undefined;
|
|
141
153
|
this.mine = false;
|
|
@@ -151,7 +163,7 @@ export class State {
|
|
|
151
163
|
this.epoch = undefined;
|
|
152
164
|
this.version = 0;
|
|
153
165
|
this.loaded = false;
|
|
154
|
-
this.gap =
|
|
166
|
+
this.gap = undefined;
|
|
155
167
|
this.mode = a?.mode;
|
|
156
168
|
this.owner = a?.owner ?? undefined;
|
|
157
169
|
this.mine = a?.mine ?? false;
|
|
@@ -177,7 +189,7 @@ export class State {
|
|
|
177
189
|
status(nodeConnected, closed = false) {
|
|
178
190
|
const ended = closed ? ENDED_CLIENT : this.ended;
|
|
179
191
|
const connected = ended === undefined && nodeConnected;
|
|
180
|
-
return { synced: connected && this.pending === 0 &&
|
|
192
|
+
return { synced: connected && this.pending === 0 && this.gap === undefined && (this.loaded || this.absent), connected, pending: this.pending, mode: this.mode, owner: this.owner, mine: this.mine, minRole: this.minRole, onDeath: this.onDeath, absent: this.absent, version: this.version, epoch: this.epoch, ended };
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
function sameValue(a, b) {
|
|
@@ -686,7 +698,8 @@ function announcedEpoch(base, prefix) {
|
|
|
686
698
|
/**
|
|
687
699
|
* Reads the incarnation the open bound (`want`; `undefined`: absent, the first one announced), writing
|
|
688
700
|
* the mirror and telling the task of each frame applied; returns how the track ended (`undefined`:
|
|
689
|
-
* no final frame — the session, or the broker, went; the task replays).
|
|
701
|
+
* no final frame — the session, or the broker, went; the task replays). A diff out of sequence is not applied: the
|
|
702
|
+
* subscription is let go and the track subscribed again, which delivers the open group from its snapshot (PLAN v0.34 §2.2).
|
|
690
703
|
*/
|
|
691
704
|
async function follow(ns, session, announced, want, signal) {
|
|
692
705
|
const { state, warn, name } = ns;
|
|
@@ -739,74 +752,86 @@ async function follow(ns, session, announced, want, signal) {
|
|
|
739
752
|
return undefined;
|
|
740
753
|
}
|
|
741
754
|
announced.close();
|
|
742
|
-
|
|
743
|
-
const s = sub;
|
|
755
|
+
const track = bc.track(STORE_TRACK);
|
|
744
756
|
// Closing the subscriber ends `recvGroup`, not a read on the group it already handed us: that group is closed too.
|
|
745
757
|
let g;
|
|
746
|
-
signal.addEventListener("abort", () => {
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
|
|
758
|
+
signal.addEventListener("abort", () => { sub?.close(); g?.close(); }, { once: true });
|
|
759
|
+
// A gap: this subscription is let go and the track subscribed again, which delivers the open group from its
|
|
760
|
+
// snapshot (PLAN v0.34 §2.2; no counter, no timer).
|
|
761
|
+
subscribe: for (;;) {
|
|
762
|
+
if (signal.aborted)
|
|
763
|
+
return undefined;
|
|
764
|
+
try {
|
|
765
|
+
sub?.close();
|
|
766
|
+
}
|
|
767
|
+
catch { /* closed */ }
|
|
768
|
+
const s = sub = track.subscribe(latestSubscription(moqPriority(1)));
|
|
769
|
+
// The first group read is the open one, from its start: a snapshot, then the diffs.
|
|
752
770
|
for (;;) {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
f = await g.readFrame();
|
|
756
|
-
}
|
|
757
|
-
catch {
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
760
|
-
if (!f)
|
|
771
|
+
g = await s.recvGroup();
|
|
772
|
+
if (!g)
|
|
761
773
|
break;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
catch {
|
|
767
|
-
warn(`store ${name}: frame not decodable`);
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
let body;
|
|
771
|
-
try {
|
|
772
|
-
body = decodeBody(f.payload.subarray(hlen));
|
|
773
|
-
}
|
|
774
|
-
catch {
|
|
775
|
-
warn(`store ${name}: frame body not decodable`);
|
|
776
|
-
continue;
|
|
777
|
-
}
|
|
778
|
-
if (header.flags & FLAGS.END) {
|
|
779
|
-
const fin = body;
|
|
780
|
-
return fin && typeof fin.reason === "string" ? fin.reason : ENDED_STORE_GONE;
|
|
781
|
-
}
|
|
782
|
-
// A stopped reader no longer writes the mirror: it belongs to the next incarnation (or to none).
|
|
783
|
-
if (header.flags & FLAGS.KEY) {
|
|
784
|
-
const snap = body;
|
|
785
|
-
if (!snap || snap.epoch !== epoch) {
|
|
786
|
-
warn(`store ${name}: snapshot of another epoch dropped`);
|
|
787
|
-
continue;
|
|
774
|
+
for (;;) {
|
|
775
|
+
let f;
|
|
776
|
+
try {
|
|
777
|
+
f = await g.readFrame();
|
|
788
778
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
779
|
+
catch {
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
if (!f)
|
|
783
|
+
break;
|
|
784
|
+
let header, hlen;
|
|
785
|
+
try {
|
|
786
|
+
({ header, hlen } = decodeHeader(f.payload));
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
warn(`store ${name}: frame not decodable`);
|
|
799
790
|
continue;
|
|
800
791
|
}
|
|
801
|
-
|
|
792
|
+
let body;
|
|
793
|
+
try {
|
|
794
|
+
body = decodeBody(f.payload.subarray(hlen));
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
warn(`store ${name}: frame body not decodable`);
|
|
802
798
|
continue;
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
799
|
+
}
|
|
800
|
+
if (header.flags & FLAGS.END) {
|
|
801
|
+
const fin = body;
|
|
802
|
+
return fin && typeof fin.reason === "string" ? fin.reason : ENDED_STORE_GONE;
|
|
803
|
+
}
|
|
804
|
+
// A stopped reader no longer writes the mirror: it belongs to the next incarnation (or to none).
|
|
805
|
+
if (header.flags & FLAGS.KEY) {
|
|
806
|
+
const snap = body;
|
|
807
|
+
if (!snap || snap.epoch !== epoch) {
|
|
808
|
+
warn(`store ${name}: snapshot of another epoch dropped`);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (signal.aborted)
|
|
812
|
+
continue;
|
|
813
|
+
if (state.ended === undefined)
|
|
814
|
+
state.applySnapshot(snap);
|
|
815
|
+
ns.post({ kind: "applied", epoch, version: snap.version });
|
|
816
|
+
}
|
|
817
|
+
else if (header.flags & FLAGS.DELTA) {
|
|
818
|
+
const diff = body;
|
|
819
|
+
if (!diff || typeof diff.version !== "number") {
|
|
820
|
+
warn(`store ${name}: diff not decodable`);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (signal.aborted)
|
|
824
|
+
continue;
|
|
825
|
+
if (state.ended === undefined && state.loaded && !state.applyDiff(diff, warn)) {
|
|
826
|
+
g.close();
|
|
827
|
+
continue subscribe;
|
|
828
|
+
}
|
|
829
|
+
ns.post({ kind: "applied", epoch, version: diff.version });
|
|
830
|
+
}
|
|
806
831
|
}
|
|
807
832
|
}
|
|
833
|
+
return undefined; // the track ended with no final frame: the session (or the broker) went
|
|
808
834
|
}
|
|
809
|
-
return undefined; // the track ended with no final frame: the session (or the broker) went
|
|
810
835
|
}
|
|
811
836
|
catch {
|
|
812
837
|
return undefined;
|
|
@@ -902,10 +927,15 @@ export class StoreHandleImpl {
|
|
|
902
927
|
this.ns.state.unwatch(q);
|
|
903
928
|
}
|
|
904
929
|
}
|
|
905
|
-
|
|
906
|
-
changed() {
|
|
930
|
+
changed(key) {
|
|
907
931
|
if (this.#closed)
|
|
908
932
|
return Promise.reject(this.#ended());
|
|
933
|
+
if (key !== undefined) {
|
|
934
|
+
// the first step of `watch(key)`, started now (its feed opens at the first `next()`), then left
|
|
935
|
+
const it = this.watch(key)[Symbol.asyncIterator]();
|
|
936
|
+
return it.next().then(r => { void it.return(undefined); if (r.done)
|
|
937
|
+
throw this.#ended(); return r.value; });
|
|
938
|
+
}
|
|
909
939
|
const from = this.get();
|
|
910
940
|
const d = deferred();
|
|
911
941
|
const un = this.ns.state.subscribe(() => { const v = this.get(); if (v !== from) {
|
|
@@ -919,7 +949,7 @@ export class StoreHandleImpl {
|
|
|
919
949
|
}
|
|
920
950
|
/** `cb` with the whole value on every change of the value or the status (the framework bridge, React's `useStore`); returns the remover. */
|
|
921
951
|
onChange(cb) { return this.ns.state.subscribe(() => cb(this.get())); }
|
|
922
|
-
/** Waits for the namespace to be in (the
|
|
952
|
+
/** Waits for the namespace to be in: the value current as of the open (the version the runtime answered applied), or the namespace absent. */
|
|
923
953
|
ready() { return this.ns.request(reply => ({ kind: "join", reply })); }
|
|
924
954
|
close() {
|
|
925
955
|
if (this.#closed)
|
package/dist/synapse.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Age, type ErrorBody, type ProjectSnapshot, type ServiceContract, t
|
|
|
2
2
|
import { Node } from "./node.ts";
|
|
3
3
|
import { StoreHandleImpl } from "./store.ts";
|
|
4
4
|
import { type Warn } from "./topics.ts";
|
|
5
|
-
import type {
|
|
5
|
+
import type { CallOptions, Catalog, ConnectOptions, ConnectTarget, ConnectionState, Envelope, Info, Metrics, Publisher, Registration, Run, ServeHandler, ServeOptions, SceneHandle, StoreHandle, StoreOptions, SubscribeOptions, Subscription, Tasks, View, ViewOptions } from "./types.ts";
|
|
6
6
|
import { type StateValue } from "./util.ts";
|
|
7
7
|
export { FIRST_CONNECT_GIVE_UP_MS } from "./node.ts";
|
|
8
8
|
export declare class Synapse implements AsyncDisposable {
|
|
@@ -74,18 +74,15 @@ export declare class Synapse implements AsyncDisposable {
|
|
|
74
74
|
/** The topic's latest value as a view (SDK §4.3); subscribed while someone listens. */
|
|
75
75
|
view<TIn, TOut, S = TOut>(topic: TopicContract<TIn, TOut>, opts?: ViewOptions<TOut, S>): View<S>;
|
|
76
76
|
view<TOut = unknown, S = TOut>(topic: string, opts?: ViewOptions<TOut, S>): View<S>;
|
|
77
|
-
/** Starts a call on the current session (SDK §5.2): `await` it for the result,
|
|
77
|
+
/** Starts a call on the current session (SDK §5.2): `await` it for the result, read its updates (`for await (const u of run)`, `await run.next()`), or pass `onUpdate`. With no session (before the first, between two) the Run is already failed `not_connected`: nothing was sent. */
|
|
78
78
|
call<TArgs, TInput, U, R>(svc: ServiceContract<TArgs, TInput, U, R>, args: TArgs, opts?: CallOptions<U>): Run<U, R>;
|
|
79
79
|
call<U = unknown, R = unknown>(svc: string, args?: unknown, opts?: CallOptions<U>): Run<U, R>;
|
|
80
|
-
/** The call's updates and result as one ordered stream; `break` cancels. */
|
|
81
|
-
callStream<TArgs, TInput, U, R>(svc: ServiceContract<TArgs, TInput, U, R>, args: TArgs, opts?: CallOptions<U>): AsyncGenerator<CallEvent<U, R>, void, undefined>;
|
|
82
|
-
callStream<U = unknown, R = unknown>(svc: string, args?: unknown, opts?: CallOptions<U>): AsyncGenerator<CallEvent<U, R>, void, undefined>;
|
|
83
80
|
/** Registers `handler` as the provider of `svc`; resolves once the runtime lists us on the current session. One provider per service path per node. */
|
|
84
81
|
serve<TArgs, TInput, U, R>(svc: ServiceContract<TArgs, TInput, U, R>, handler: ServeHandler<TInput, U, R>, opts?: ServeOptions): Promise<Registration>;
|
|
85
82
|
serve<I = unknown, U = unknown, R = unknown>(svc: string, handler: ServeHandler<I, U, R>, opts?: ServeOptions): Promise<Registration>;
|
|
86
83
|
/** Opens namespace `name` (SDK §9); resolves once the mirror is in (or the namespace is known absent). */
|
|
87
84
|
store(name: string, opts?: StoreOptions): Promise<StoreHandle>;
|
|
88
|
-
/** The active project's scene (PLAN v0.24 §5); resolves attached, or at once `detached` when no project is active. */
|
|
85
|
+
/** The active project's scene (PLAN v0.24 §5); resolves attached (current as of the attach: at the version the runtime answered), or at once `detached` when no project is active. */
|
|
89
86
|
scene(): Promise<SceneHandle>;
|
|
90
87
|
/** The runtime's catalogs as the current session last read them (empty between sessions), a state value: `get()` now, `onChange` for each change, `changed()` for the next one, `watch()` for each one in a loop. */
|
|
91
88
|
readonly catalog: StateValue<Catalog>;
|
package/dist/synapse.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// own tasks, told about sessions by the loop; every fact about the world is read from the current
|
|
4
4
|
// `Session` (the runtime's statement) at the moment it is asked for, never copied here.
|
|
5
5
|
import { SynapseError, ageOf, fecSymbolSizeFor, isServiceContract, nowUs, resolveTopic, serviceName, } from "@synapse-sdk/core";
|
|
6
|
-
import {
|
|
6
|
+
import { failedRun, startOn } from "./calls.js";
|
|
7
7
|
import { Node, createNode, endpointDial, resolveTarget } from "./node.js";
|
|
8
8
|
import { ServiceRegistration } from "./serve.js";
|
|
9
9
|
import { openScene } from "./scene.js";
|
|
@@ -240,9 +240,6 @@ export class Synapse {
|
|
|
240
240
|
const contract = isServiceContract(svc) ? svc : undefined;
|
|
241
241
|
return startOn(this.node, s, serviceName(svc), args, opts, contract);
|
|
242
242
|
}
|
|
243
|
-
callStream(svc, args = {}, opts = {}) {
|
|
244
|
-
return callStream(this.call(svc, args, opts));
|
|
245
|
-
}
|
|
246
243
|
async serve(svc, handler, opts = {}) {
|
|
247
244
|
const n = this.node;
|
|
248
245
|
const name = serviceName(svc);
|
|
@@ -273,7 +270,7 @@ export class Synapse {
|
|
|
273
270
|
store(name, opts = {}) { return openStore(this.node, this.#stores, name, opts, this.warn); }
|
|
274
271
|
// ---------------------------------------------------------------------------------- scene
|
|
275
272
|
#scenes = { current: undefined };
|
|
276
|
-
/** The active project's scene (PLAN v0.24 §5); resolves attached, or at once `detached` when no project is active. */
|
|
273
|
+
/** The active project's scene (PLAN v0.24 §5); resolves attached (current as of the attach: at the version the runtime answered), or at once `detached` when no project is active. */
|
|
277
274
|
scene() { return openScene(this.node, this.#scenes, this.warn); }
|
|
278
275
|
// -------------------------------------------------------------------------------- catalog
|
|
279
276
|
/** The runtime's catalogs as the current session last read them (empty between sessions), a state value: `get()` now, `onChange` for each change, `changed()` for the next one, `watch()` for each one in a loop. */
|
package/dist/types.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface ConnectOptions {
|
|
|
40
40
|
export interface ConnectTarget {
|
|
41
41
|
/**
|
|
42
42
|
* The runtime, `https://host:4470` (the path to dial is learned from the runtime at every attempt). Through a relay: the relay with
|
|
43
|
-
* the runtime's path, `https
|
|
43
|
+
* the runtime's path, `https://<relay>/cluster/<cluster>/runtimes/<runtime_id>`, and `token`. A `jwt=` in the URL is taken as the token.
|
|
44
44
|
*/
|
|
45
45
|
url: string;
|
|
46
46
|
/** The type to declare there; left out keeps the node's current one. */
|
|
@@ -230,27 +230,32 @@ export interface CallOptions<U = unknown> {
|
|
|
230
230
|
/** Local: fail `idle_timeout` (and cancel) when no frame arrives for this long. */
|
|
231
231
|
idleTimeoutMs?: number;
|
|
232
232
|
signal?: AbortSignal;
|
|
233
|
+
/** The callback form: the SDK reads the updates and calls `onUpdate(u)` for each (the run's loop then has none); a throwing `onUpdate` is rethrown later and the reading goes on. */
|
|
233
234
|
onUpdate?: (u: U) => void;
|
|
234
235
|
}
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
/**
|
|
237
|
+
* A started call (WP §8.3). Its progress is a stream, the run itself a promise:
|
|
238
|
+
* `for await (const u of run)` the updates until the run ended, `await run.next()` one update
|
|
239
|
+
* (`undefined` once the run ended), or `onUpdate` in the call options (the SDK reads them);
|
|
240
|
+
* `await run` (or `.then`/`.catch`) the result, rejecting with the `SynapseError` of a
|
|
241
|
+
* `failed` or `canceled` run; `cancel()`.
|
|
242
|
+
*/
|
|
243
|
+
export interface Run<U, R> extends AsyncDisposable, PromiseLike<R>, AsyncIterable<U> {
|
|
238
244
|
readonly id: string;
|
|
239
245
|
readonly status: RunStatus;
|
|
240
|
-
/** The next progress update; `undefined` once the
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
/** The next progress update; `undefined` once the run ended (then `await run` settles at once). */
|
|
247
|
+
next(): Promise<U | undefined>;
|
|
248
|
+
/** `await run` / `.then` as on a promise (returns a `Promise`, so `.then(...).catch(...)` chains). */
|
|
249
|
+
then<A = R, B = never>(onfulfilled?: ((value: R) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<A | B>;
|
|
250
|
+
/** The result as a plain promise (what `await run` waits for). */
|
|
243
251
|
readonly result: Promise<R>;
|
|
252
|
+
/** `.catch` as on a promise: `onRejected` gets the run's `SynapseError` when it failed or was canceled. */
|
|
253
|
+
catch<B = never>(onRejected?: ((e: unknown) => B | PromiseLike<B>) | null): Promise<R | B>;
|
|
254
|
+
/** `.finally` as on a promise: `onFinally` runs once the run ended, whichever way. */
|
|
255
|
+
finally(onFinally?: (() => void) | null): Promise<R>;
|
|
244
256
|
/** Cooperative cancel: tells the broker, resolves on its `canceled` frame. */
|
|
245
257
|
cancel(reason?: string): Promise<void>;
|
|
246
258
|
}
|
|
247
|
-
export type CallEvent<U, R> = {
|
|
248
|
-
type: "update";
|
|
249
|
-
data: U;
|
|
250
|
-
} | {
|
|
251
|
-
type: "result";
|
|
252
|
-
data: R;
|
|
253
|
-
};
|
|
254
259
|
export interface ServeContext<U> {
|
|
255
260
|
readonly call: string;
|
|
256
261
|
readonly caller: string;
|
|
@@ -332,7 +337,7 @@ export interface StoreHandle extends AsyncDisposable {
|
|
|
332
337
|
readonly synced: boolean;
|
|
333
338
|
/** The handle's own state, a state value: `get()` now (the same object while unchanged), `onChange` for each change, `changed()` for the next. */
|
|
334
339
|
readonly status: StateValue<StoreStatus>;
|
|
335
|
-
/** Resolves once the
|
|
340
|
+
/** Resolves once the value is in: current as of the open (the version the runtime answered then applied), or the namespace absent. */
|
|
336
341
|
ready(): Promise<void>;
|
|
337
342
|
/** `set(key, value)`, or `set({ ... })` for several keys as one atomic write; resolves once `get()` shows it. */
|
|
338
343
|
set(key: string, value: unknown): Promise<void>;
|
|
@@ -343,8 +348,9 @@ export interface StoreHandle extends AsyncDisposable {
|
|
|
343
348
|
destroy(): Promise<void>;
|
|
344
349
|
/** The whole value after each change; with `key`, that key's value each time it changes (`undefined` after a delete). */
|
|
345
350
|
watch(key?: string): AsyncIterable<unknown>;
|
|
346
|
-
/** The whole value after its next change; `store_gone` once this handle is closed. */
|
|
351
|
+
/** The whole value after its next change; with `key`, that key's value after its next change (`undefined` after a delete). `store_gone` once this handle is closed. */
|
|
347
352
|
changed(): Promise<Record<string, unknown>>;
|
|
353
|
+
changed(key: string): Promise<unknown>;
|
|
348
354
|
/** `cb` with the whole value on every change of the value or the status (the framework bridge); returns the remover. */
|
|
349
355
|
onChange(cb: (value: Record<string, unknown>) => void): () => void;
|
|
350
356
|
close(): void;
|
|
@@ -441,7 +447,7 @@ export interface SceneEntity {
|
|
|
441
447
|
kind: SceneKind;
|
|
442
448
|
/**
|
|
443
449
|
* Unique across every plane (authored, calibration, live): a write that would give an entity a name another entity
|
|
444
|
-
* holds is refused `name_taken`, its `data.holder` the id holding the name.
|
|
450
|
+
* holds is refused `name_taken`, its `data.holder` the id holding the name (`data.kind`, `data.layer`: the holder's).
|
|
445
451
|
*/
|
|
446
452
|
name?: string;
|
|
447
453
|
/** The parent's id; `null` is the world root. */
|
|
@@ -550,6 +556,9 @@ export interface SceneEntityView extends SceneView<SceneEntity | undefined> {
|
|
|
550
556
|
*/
|
|
551
557
|
watch(): AsyncIterable<SceneEntity | undefined>;
|
|
552
558
|
watch<K extends keyof SceneEntity & string>(component: K): AsyncIterable<SceneEntity[K] | undefined>;
|
|
559
|
+
/** The entity after its next change; with `component`, that component after its next change (`undefined` once gone). The value now once the handle ends. */
|
|
560
|
+
changed(): Promise<SceneEntity | undefined>;
|
|
561
|
+
changed<K extends keyof SceneEntity & string>(component: K): Promise<SceneEntity[K] | undefined>;
|
|
553
562
|
}
|
|
554
563
|
/** An entity view's authored plane (`view.authored`). Reads as the view does: nothing once the handle is detached or closed. */
|
|
555
564
|
export interface EntityAuthored {
|
|
@@ -743,12 +752,14 @@ export interface SceneAuthored {
|
|
|
743
752
|
patch(entity: string | SceneEntityView, set: Partial<SceneEntity>): Promise<void>;
|
|
744
753
|
/**
|
|
745
754
|
* A builder's one verb, keyed by `name`; resolves with the entity's view (by id: that entity) once the entities show
|
|
746
|
-
* it.
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
755
|
+
* it. The id is the runtime's answer, never the copy's: `spawn(kind, { name, parent, ...components })` (a fresh id) is
|
|
756
|
+
* sent first; the runtime refusing it `name_taken` names the holder and its kind (`data.holder`, `data.kind`,
|
|
757
|
+
* `data.layer`). A holder of this kind in the authored or calibration layer is patched with the components that differ
|
|
758
|
+
* from what the copy shows of it (one-level merge; live overrides never count; all of them when the copy does not show
|
|
759
|
+
* it yet; equal, nothing more is sent): the copy only trims the patch, it never decides. A holder of another kind, a
|
|
760
|
+
* live one, or an answer without them: the `name_taken` as it came. `parent` is used by the spawn only: an existing
|
|
761
|
+
* entity is not reparented. Any other refusal as it came (`not_found` on the patch: the entity went meanwhile; upsert
|
|
762
|
+
* again). An `id` component: `invalid_input` (`data: { component: "id" }`), nothing sent.
|
|
752
763
|
*/
|
|
753
764
|
upsert(kind: SceneKind, opts: SceneSpawnOptions & {
|
|
754
765
|
id?: never;
|
|
@@ -817,7 +828,7 @@ export interface SceneHandle extends AsyncDisposable {
|
|
|
817
828
|
readonly status: StateValue<SceneStatus>;
|
|
818
829
|
/** The one boolean a node author checks: `status.get().synced`. */
|
|
819
830
|
readonly synced: boolean;
|
|
820
|
-
/** Resolves once the handle is attached (at once if it is); `not_connected` once the handle is closed. No timeout: pass an `AbortSignal` to what awaits it, or race it. */
|
|
831
|
+
/** Resolves once the handle is attached (at once if it is): the copy current as of the attach, at the version the runtime answered when the scene was opened, or past it; `not_connected` once the handle is closed. No timeout: pass an `AbortSignal` to what awaits it, or race it. */
|
|
821
832
|
ready(): Promise<void>;
|
|
822
833
|
/** The entities after their next change (state, not history); `not_connected` once the handle is closed. */
|
|
823
834
|
changed(): Promise<Readonly<Record<string, SceneEntity>>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapse-sdk/client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Synapse SDK client: connect a node to the runtime; topics, services, calls, store, video.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@synapse-sdk/core": "0.3.
|
|
17
|
+
"@synapse-sdk/core": "0.3.6",
|
|
18
18
|
"@moq/net": "0.4.2"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|