@synapse-sdk/client 0.3.0 → 0.3.4
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 +7 -1
- package/dist/calls.js +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node.d.ts +62 -24
- package/dist/node.js +208 -68
- package/dist/scene.d.ts +150 -56
- package/dist/scene.js +995 -163
- package/dist/scene_frames.d.ts +2 -2
- package/dist/scene_frames.js +1 -1
- package/dist/session.d.ts +74 -11
- package/dist/session.js +279 -59
- package/dist/store.d.ts +31 -10
- package/dist/store.js +140 -28
- package/dist/synapse.d.ts +77 -46
- package/dist/synapse.js +193 -114
- package/dist/tasks.d.ts +37 -0
- package/dist/tasks.js +146 -0
- package/dist/topics.d.ts +23 -6
- package/dist/topics.js +59 -22
- package/dist/types.d.ts +405 -93
- package/dist/util.d.ts +43 -0
- package/dist/util.js +97 -1
- package/dist/view.d.ts +5 -3
- package/dist/view.js +21 -10
- package/package.json +2 -2
package/dist/util.d.ts
CHANGED
|
@@ -5,6 +5,19 @@ export interface Deferred<T> {
|
|
|
5
5
|
settled: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function deferred<T>(): Deferred<T>;
|
|
8
|
+
/**
|
|
9
|
+
* A state value (`node.status`, `node.catalog`, `node.project`, the `status` of the store, scene and tasks handles):
|
|
10
|
+
* `get()` the value now (the same object while it is unchanged), `onChange` for each change (the framework bridge;
|
|
11
|
+
* `cb` gets the value; returns the remover), `changed()` for the value after the next change (at once with the value
|
|
12
|
+
* now once the node has ended), `watch()` for the value after each change in a loop (ends when the node, or the
|
|
13
|
+
* handle, ends). Not exported from the package: no user builds one.
|
|
14
|
+
*/
|
|
15
|
+
export interface StateValue<T> {
|
|
16
|
+
get(): T;
|
|
17
|
+
onChange(cb: (value: T) => void): () => void;
|
|
18
|
+
changed(): Promise<T>;
|
|
19
|
+
watch(): AsyncIterable<T>;
|
|
20
|
+
}
|
|
8
21
|
/** A value plus listeners. `changed()` resolves on the next `set`. */
|
|
9
22
|
export declare class Cell<T> {
|
|
10
23
|
#private;
|
|
@@ -16,6 +29,8 @@ export declare class Cell<T> {
|
|
|
16
29
|
changed(): Promise<T>;
|
|
17
30
|
get listenerCount(): number;
|
|
18
31
|
}
|
|
32
|
+
/** A callback's throw, rethrown on its own microtask: the caller's loop goes on and the error still surfaces. */
|
|
33
|
+
export declare const rethrowLater: (e: unknown) => void;
|
|
19
34
|
export declare const DONE: unique symbol;
|
|
20
35
|
/**
|
|
21
36
|
* Bounded FIFO with one consumer. `push` never blocks: past `capacity` the oldest item is dropped
|
|
@@ -65,3 +80,31 @@ export declare class Ring {
|
|
|
65
80
|
push(v: number): void;
|
|
66
81
|
percentile(p: number): number | undefined;
|
|
67
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* The loop form of a state value (`watch()`), built from its `onChange` and `get`: the value after each change, compared
|
|
85
|
+
* by reference (a state value keeps its object while unchanged), changes in between folded into the latest (state, not
|
|
86
|
+
* history). It ends when it is told of a change once `ended()` holds; each state value tells its listeners when it ends.
|
|
87
|
+
* Leaving the loop removes the listener.
|
|
88
|
+
*/
|
|
89
|
+
export declare function watchState<T>(onChange: (cb: () => void) => () => void, get: () => T, ended: () => boolean): AsyncGenerator<T, void, undefined>;
|
|
90
|
+
/**
|
|
91
|
+
* A handle's own state as a state value (`store.status`, `scene.status`; the `StateValue` shape): `get()` is `read()`
|
|
92
|
+
* kept the same object while every field is equal (`shallowEqual`), `onChange` calls back only when that object
|
|
93
|
+
* changes, `changed()` resolves with the next one (at once with the value now once `ended()`), `watch()` is the loop
|
|
94
|
+
* form. `source` is what tells of a possible change: the handle's own notifications.
|
|
95
|
+
*/
|
|
96
|
+
export declare class StatusCell<T> {
|
|
97
|
+
#private;
|
|
98
|
+
private readonly read;
|
|
99
|
+
private readonly source;
|
|
100
|
+
private readonly ended;
|
|
101
|
+
constructor(read: () => T, source: (l: () => void) => () => void, ended: () => boolean);
|
|
102
|
+
get(): T;
|
|
103
|
+
/** `cb` with the value on each change; once the handle has ended (or this state value was disposed) nothing will change: a no-op remover. */
|
|
104
|
+
onChange(cb: (value: T) => void): () => void;
|
|
105
|
+
/** The handle closed: every upstream registration is removed (after the listeners were told of the close); new subscriptions are refused. */
|
|
106
|
+
dispose(): void;
|
|
107
|
+
changed(): Promise<T>;
|
|
108
|
+
/** The value after each change; ends with the handle. */
|
|
109
|
+
watch(): AsyncIterable<T>;
|
|
110
|
+
}
|
package/dist/util.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Small primitives shared by the client: a value
|
|
1
|
+
// Small primitives shared by the client: a value kept current with change notification, a bounded async
|
|
2
2
|
// queue, deferreds, sleeps that honour AbortSignals, and TanStack-style structural sharing.
|
|
3
3
|
export function deferred() {
|
|
4
4
|
const d = { settled: false };
|
|
@@ -34,6 +34,8 @@ export class Cell {
|
|
|
34
34
|
changed() { this.#waiters ??= deferred(); return this.#waiters.promise; }
|
|
35
35
|
get listenerCount() { return this.#listeners.size; }
|
|
36
36
|
}
|
|
37
|
+
/** A callback's throw, rethrown on its own microtask: the caller's loop goes on and the error still surfaces. */
|
|
38
|
+
export const rethrowLater = (e) => { queueMicrotask(() => { throw e; }); };
|
|
37
39
|
export const DONE = Symbol("done");
|
|
38
40
|
/**
|
|
39
41
|
* Bounded FIFO with one consumer. `push` never blocks: past `capacity` the oldest item is dropped
|
|
@@ -225,3 +227,97 @@ export class Ring {
|
|
|
225
227
|
return s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))];
|
|
226
228
|
}
|
|
227
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* The loop form of a state value (`watch()`), built from its `onChange` and `get`: the value after each change, compared
|
|
232
|
+
* by reference (a state value keeps its object while unchanged), changes in between folded into the latest (state, not
|
|
233
|
+
* history). It ends when it is told of a change once `ended()` holds; each state value tells its listeners when it ends.
|
|
234
|
+
* Leaving the loop removes the listener.
|
|
235
|
+
*/
|
|
236
|
+
export async function* watchState(onChange, get, ended) {
|
|
237
|
+
if (ended())
|
|
238
|
+
return;
|
|
239
|
+
let changed = false;
|
|
240
|
+
let wake;
|
|
241
|
+
const un = onChange(() => { changed = true; wake?.resolve(); });
|
|
242
|
+
let last = get();
|
|
243
|
+
try {
|
|
244
|
+
for (;;) {
|
|
245
|
+
while (!changed) {
|
|
246
|
+
wake = deferred();
|
|
247
|
+
await wake.promise;
|
|
248
|
+
wake = undefined;
|
|
249
|
+
}
|
|
250
|
+
changed = false;
|
|
251
|
+
if (ended())
|
|
252
|
+
return;
|
|
253
|
+
const v = get();
|
|
254
|
+
if (v === last)
|
|
255
|
+
continue;
|
|
256
|
+
last = v;
|
|
257
|
+
yield v;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
un();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* A handle's own state as a state value (`store.status`, `scene.status`; the `StateValue` shape): `get()` is `read()`
|
|
266
|
+
* kept the same object while every field is equal (`shallowEqual`), `onChange` calls back only when that object
|
|
267
|
+
* changes, `changed()` resolves with the next one (at once with the value now once `ended()`), `watch()` is the loop
|
|
268
|
+
* form. `source` is what tells of a possible change: the handle's own notifications.
|
|
269
|
+
*/
|
|
270
|
+
export class StatusCell {
|
|
271
|
+
read;
|
|
272
|
+
source;
|
|
273
|
+
ended;
|
|
274
|
+
#last;
|
|
275
|
+
/** The upstream registrations of the listeners now subscribed: `dispose()` removes them all. */
|
|
276
|
+
#subs = new Set();
|
|
277
|
+
#disposed = false;
|
|
278
|
+
constructor(read, source, ended) {
|
|
279
|
+
this.read = read;
|
|
280
|
+
this.source = source;
|
|
281
|
+
this.ended = ended;
|
|
282
|
+
}
|
|
283
|
+
get() {
|
|
284
|
+
const v = this.read();
|
|
285
|
+
if (this.#last === undefined || !shallowEqual(this.#last, v))
|
|
286
|
+
this.#last = v;
|
|
287
|
+
return this.#last;
|
|
288
|
+
}
|
|
289
|
+
/** `cb` with the value on each change; once the handle has ended (or this state value was disposed) nothing will change: a no-op remover. */
|
|
290
|
+
onChange(cb) {
|
|
291
|
+
if (this.#disposed || this.ended())
|
|
292
|
+
return () => { };
|
|
293
|
+
let from = this.get();
|
|
294
|
+
const un = this.source(() => {
|
|
295
|
+
const v = this.get();
|
|
296
|
+
if (v === from)
|
|
297
|
+
return;
|
|
298
|
+
from = v;
|
|
299
|
+
cb(v);
|
|
300
|
+
});
|
|
301
|
+
this.#subs.add(un);
|
|
302
|
+
return () => { if (this.#subs.delete(un))
|
|
303
|
+
un(); };
|
|
304
|
+
}
|
|
305
|
+
/** The handle closed: every upstream registration is removed (after the listeners were told of the close); new subscriptions are refused. */
|
|
306
|
+
dispose() {
|
|
307
|
+
this.#disposed = true;
|
|
308
|
+
const subs = [...this.#subs];
|
|
309
|
+
this.#subs.clear();
|
|
310
|
+
for (const un of subs)
|
|
311
|
+
un();
|
|
312
|
+
}
|
|
313
|
+
changed() {
|
|
314
|
+
const from = this.get();
|
|
315
|
+
if (this.#disposed || this.ended())
|
|
316
|
+
return Promise.resolve(from);
|
|
317
|
+
return new Promise(resolve => {
|
|
318
|
+
const un = this.onChange(v => { un(); resolve(v); });
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** The value after each change; ends with the handle. */
|
|
322
|
+
watch() { return watchState(cb => this.onChange(cb), () => this.get(), () => this.#disposed || this.ended()); }
|
|
323
|
+
}
|
package/dist/view.d.ts
CHANGED
|
@@ -11,9 +11,11 @@ export declare class TopicView<T, S> implements View<S> {
|
|
|
11
11
|
ready(): Promise<void>;
|
|
12
12
|
/** Resolves with the state after the next change; a closed view resolves with its final state. */
|
|
13
13
|
changed(): Promise<ViewState<S>>;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
14
|
+
/** `cb` with the state on every change; the returned function removes it. Told once more when the view closes. */
|
|
15
|
+
onChange(cb: (state: ViewState<S>) => void): () => void;
|
|
16
|
+
/** The state after each change; ends when the view is closed or the node ends. */
|
|
17
|
+
watch(): AsyncIterable<ViewState<S>>;
|
|
18
|
+
/** Ends the view for good: the subscription is closed, listeners are told once and dropped, waiters settle, the registry entry is released. */
|
|
17
19
|
close(): void;
|
|
18
20
|
[Symbol.asyncDispose](): Promise<void>;
|
|
19
21
|
}
|
package/dist/view.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// A view (SDK §4.3): a topic's value now, as a
|
|
2
|
-
//
|
|
1
|
+
// A view (SDK §4.3): a topic's value now, as a state value — `get()` any time, `onChange` for
|
|
2
|
+
// changes, `changed()` for the next one, `watch()` for each one in a loop, `ready()` for the first. State, not history: of every
|
|
3
3
|
// batch the subscription returns the view keeps the newest envelope (by its publisher's stamp),
|
|
4
4
|
// compared with structural sharing so `get()` is reference-stable while the selected value is
|
|
5
5
|
// deep-equal. The view is subscribed from its creation until `close()`, like the Rust `View`.
|
|
6
6
|
import { SynapseError } from "@synapse-sdk/core";
|
|
7
|
-
import { deferred, replaceEqualDeep } from "./util.js";
|
|
7
|
+
import { deferred, replaceEqualDeep, watchState } from "./util.js";
|
|
8
8
|
export class TopicView {
|
|
9
9
|
node;
|
|
10
10
|
opts;
|
|
@@ -43,7 +43,7 @@ export class TopicView {
|
|
|
43
43
|
const s = this.#state;
|
|
44
44
|
if (s.status === "live" || s.status === "stale")
|
|
45
45
|
d.resolve();
|
|
46
|
-
else if (s.connection === "closed" ||
|
|
46
|
+
else if (s.connection === "closed" || this.#closed)
|
|
47
47
|
d.reject(new SynapseError("not_connected", "the node ended before the view got a value"));
|
|
48
48
|
}
|
|
49
49
|
/** Resolves with the state after the next change; a closed view resolves with its final state. */
|
|
@@ -58,13 +58,15 @@ export class TopicView {
|
|
|
58
58
|
this.#changed = undefined;
|
|
59
59
|
c?.resolve(this.#state);
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
61
|
+
/** `cb` with the state on every change; the returned function removes it. Told once more when the view closes. */
|
|
62
|
+
onChange(cb) {
|
|
63
63
|
if (this.#closed)
|
|
64
64
|
return () => { };
|
|
65
|
-
this.#listeners.add(
|
|
66
|
-
return () => { this.#listeners.delete(
|
|
65
|
+
this.#listeners.add(cb);
|
|
66
|
+
return () => { this.#listeners.delete(cb); };
|
|
67
67
|
}
|
|
68
|
+
/** The state after each change; ends when the view is closed or the node ends. */
|
|
69
|
+
watch() { return watchState(cb => this.onChange(cb), () => this.#state, () => this.#closed || this.node.isEnded); }
|
|
68
70
|
async #read() {
|
|
69
71
|
for (;;) {
|
|
70
72
|
const batch = await this.#sub.next();
|
|
@@ -121,11 +123,11 @@ export class TopicView {
|
|
|
121
123
|
return;
|
|
122
124
|
this.#state = { ...s, ...patch };
|
|
123
125
|
for (const l of [...this.#listeners])
|
|
124
|
-
l();
|
|
126
|
+
l(this.#state);
|
|
125
127
|
this.#settleChanged();
|
|
126
128
|
this.#settleReady();
|
|
127
129
|
}
|
|
128
|
-
/** Ends the view for good: the subscription is closed, listeners are dropped, waiters settle, the registry entry is released. */
|
|
130
|
+
/** Ends the view for good: the subscription is closed, listeners are told once and dropped, waiters settle, the registry entry is released. */
|
|
129
131
|
close() {
|
|
130
132
|
if (this.#closed)
|
|
131
133
|
return;
|
|
@@ -136,7 +138,16 @@ export class TopicView {
|
|
|
136
138
|
clearTimeout(this.#staleTimer);
|
|
137
139
|
this.#staleTimer = undefined;
|
|
138
140
|
}
|
|
141
|
+
const ls = [...this.#listeners];
|
|
139
142
|
this.#listeners.clear();
|
|
143
|
+
for (const l of ls) {
|
|
144
|
+
try {
|
|
145
|
+
l(this.#state);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
queueMicrotask(() => { throw e; });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
140
151
|
this.#settleChanged();
|
|
141
152
|
this.#settleReady();
|
|
142
153
|
this.onClose();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapse-sdk/client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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.4",
|
|
18
18
|
"@moq/net": "0.4.2"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|