@soundworks/core 5.6.0 → 5.8.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 +17 -0
- package/package.json +1 -1
- package/src/common/BaseStateManager.js +246 -219
- package/src/common/SharedState.js +395 -260
- package/src/common/SharedStateCollection.js +79 -29
- package/src/common/shared-state-utils.js +102 -0
- package/src/common/version.js +1 -1
- package/src/server/ServerStateManager.js +234 -223
- package/src/server/SharedStatePrivate.js +100 -82
- package/types/common/BaseStateManager.d.ts +34 -47
- package/types/common/BaseStateManager.d.ts.map +1 -1
- package/types/common/SharedState.d.ts +50 -29
- package/types/common/SharedState.d.ts.map +1 -1
- package/types/common/SharedStateCollection.d.ts +26 -8
- package/types/common/SharedStateCollection.d.ts.map +1 -1
- package/types/common/shared-state-utils.d.ts +12 -0
- package/types/common/shared-state-utils.d.ts.map +1 -0
- package/types/common/version.d.ts +1 -1
- package/types/server/ServerStateManager.d.ts +10 -10
- package/types/server/ServerStateManager.d.ts.map +1 -1
- package/types/server/SharedStatePrivate.d.ts +2 -3
- package/types/server/SharedStatePrivate.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## v5.8.0 - 2026-10-01
|
|
4
|
+
|
|
5
|
+
- Feat: SharedState#WaitFor accept arbitrary function as condition
|
|
6
|
+
|
|
7
|
+
## v5.7.0 - 2026-10-01
|
|
8
|
+
|
|
9
|
+
- Refactor: SharedState callback and instantiation
|
|
10
|
+
- Feat: introduce blackList and whiteList filters on SharedState and SharedStateCollection (backward compatible)
|
|
11
|
+
- Feat: introduce SharedState#onUpdate(paramName, listener) and SharedStateCollection#onUpdate(paramName, listener) signature (backward compatible)
|
|
12
|
+
- Feat: introduce experimental SharedState#waitFor(condition, timeout) method
|
|
13
|
+
|
|
14
|
+
## v5.6.0 - 2026-09-23
|
|
15
|
+
|
|
16
|
+
- Feat: do not propagate values when acknowledge is false and state has no siblings
|
|
17
|
+
|
|
1
18
|
## v5.5.0 - 2026-06-10
|
|
2
19
|
|
|
3
20
|
- CI: move to node 24
|
package/package.json
CHANGED
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
OBSERVE_ERROR,
|
|
24
24
|
OBSERVE_NOTIFICATION,
|
|
25
25
|
UNOBSERVE_NOTIFICATION,
|
|
26
|
-
// DELETE_SHARED_STATE_CLASS,
|
|
27
26
|
GET_CLASS_DESCRIPTION_REQUEST,
|
|
28
27
|
GET_CLASS_DESCRIPTION_RESPONSE,
|
|
29
28
|
GET_CLASS_DESCRIPTION_ERROR,
|
|
@@ -32,9 +31,11 @@ import warnings from './logs/warnings.js';
|
|
|
32
31
|
|
|
33
32
|
export const kStateManagerInit = Symbol('soundworks:state-manager-init');
|
|
34
33
|
export const kStateManagerDeleteState = Symbol('soundworks:state-manager-delete-state');
|
|
34
|
+
export const kPendingSharedStateConstructionData = Symbol('soundworks:state-manager-pending-shared-state-construction-data');
|
|
35
35
|
// for testing purposes
|
|
36
36
|
export const kStateManagerClient = Symbol('soundworks:state-manager-client');
|
|
37
37
|
|
|
38
|
+
|
|
38
39
|
/**
|
|
39
40
|
* Callback executed when a state is created on the network.
|
|
40
41
|
*
|
|
@@ -66,21 +67,6 @@ class BaseStateManager {
|
|
|
66
67
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
/** @private */
|
|
70
|
-
#filterObserve(observedClassName, className, creatorId, options) {
|
|
71
|
-
let filter = true;
|
|
72
|
-
|
|
73
|
-
if (observedClassName === null || observedClassName === className) {
|
|
74
|
-
filter = false;
|
|
75
|
-
}
|
|
76
|
-
// filter state created by client if excludeLocal is true
|
|
77
|
-
if (options.excludeLocal === true && creatorId === this[kStateManagerClient].id) {
|
|
78
|
-
filter = true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return filter;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
70
|
/** @private */
|
|
85
71
|
[kStateManagerDeleteState](stateId) {
|
|
86
72
|
this.#statesById.delete(stateId);
|
|
@@ -98,116 +84,18 @@ class BaseStateManager {
|
|
|
98
84
|
const batchedTransport = new BatchedTransport(transport);
|
|
99
85
|
this[kStateManagerClient] = { id, transport: batchedTransport };
|
|
100
86
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// ---------------------------------------------
|
|
104
|
-
this[kStateManagerClient].transport.addListener(
|
|
105
|
-
CREATE_RESPONSE,
|
|
106
|
-
(reqId, stateId, instanceId, className, classDescription, initValues) => {
|
|
107
|
-
const state = new SharedState({
|
|
108
|
-
manager: this,
|
|
109
|
-
className,
|
|
110
|
-
classDescription,
|
|
111
|
-
stateId,
|
|
112
|
-
instanceId,
|
|
113
|
-
isOwner: true,
|
|
114
|
-
initValues,
|
|
115
|
-
filter: null, // owner cannot filter parameters
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
this.#statesById.set(state.id, state);
|
|
119
|
-
this.#promiseStore.resolve(reqId, state);
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
this[kStateManagerClient].transport.addListener(CREATE_ERROR, (reqId, msg) => {
|
|
124
|
-
msg = `Cannot execute 'create' on BaseStateManager: ${msg}`;
|
|
125
|
-
this.#promiseStore.reject(reqId, msg);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------
|
|
129
|
-
// ATTACH (when creator, is attached by default)
|
|
130
|
-
// ---------------------------------------------
|
|
131
|
-
this[kStateManagerClient].transport.addListener(
|
|
132
|
-
ATTACH_RESPONSE,
|
|
133
|
-
(reqId, stateId, instanceId, className, classDescription, currentValues, filter) => {
|
|
134
|
-
const state = new SharedState({
|
|
135
|
-
manager: this,
|
|
136
|
-
className,
|
|
137
|
-
classDescription,
|
|
138
|
-
stateId,
|
|
139
|
-
instanceId,
|
|
140
|
-
isOwner: false,
|
|
141
|
-
initValues: currentValues,
|
|
142
|
-
filter,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
this.#statesById.set(state.id, state);
|
|
146
|
-
this.#promiseStore.resolve(reqId, state);
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
this[kStateManagerClient].transport.addListener(ATTACH_ERROR, (reqId, msg) => {
|
|
151
|
-
msg = `Cannot execute 'attach' on BaseStateManager: ${msg}`;
|
|
152
|
-
this.#promiseStore.reject(reqId, msg);
|
|
153
|
-
});
|
|
87
|
+
this[kStateManagerClient].transport.addListener(CREATE_RESPONSE, this.#onCreateResponse);
|
|
88
|
+
this[kStateManagerClient].transport.addListener(CREATE_ERROR, this.#onCreateError);
|
|
154
89
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// ---------------------------------------------
|
|
158
|
-
this[kStateManagerClient].transport.addListener(OBSERVE_RESPONSE, async (reqId, ...list) => {
|
|
159
|
-
// retrieve the callback that have been stored in `observe()` to make sure
|
|
160
|
-
// we don't call another callback that may have been registered earlier.
|
|
161
|
-
const observeInfos = this.#observeRequestCallbacks.get(reqId);
|
|
162
|
-
const [observedClassName, callback, options] = observeInfos;
|
|
163
|
-
// move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
|
|
164
|
-
// to guarantee future order of execution
|
|
165
|
-
this.#observeRequestCallbacks.delete(reqId);
|
|
166
|
-
this.#observeListeners.add(observeInfos);
|
|
167
|
-
|
|
168
|
-
const promises = list.map(([className, stateId, nodeId]) => {
|
|
169
|
-
const filter = this.#filterObserve(observedClassName, className, nodeId, options);
|
|
90
|
+
this[kStateManagerClient].transport.addListener(ATTACH_RESPONSE, this.#onAttachResponse);
|
|
91
|
+
this[kStateManagerClient].transport.addListener(ATTACH_ERROR, this.#onAttachError);
|
|
170
92
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return Promise.resolve();
|
|
175
|
-
}
|
|
176
|
-
});
|
|
93
|
+
this[kStateManagerClient].transport.addListener(OBSERVE_RESPONSE, this.#onObserveResponse);
|
|
94
|
+
this[kStateManagerClient].transport.addListener(OBSERVE_ERROR, this.#onObserveError);
|
|
95
|
+
this[kStateManagerClient].transport.addListener(OBSERVE_NOTIFICATION, this.#onObserveNotification);
|
|
177
96
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const unsubscribe = () => {
|
|
181
|
-
this.#observeListeners.delete(observeInfos);
|
|
182
|
-
// no more listeners, we can stop receiving notifications from the server
|
|
183
|
-
if (this.#observeListeners.size === 0) {
|
|
184
|
-
this[kStateManagerClient].transport.emit(UNOBSERVE_NOTIFICATION);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
this.#promiseStore.resolve(reqId, unsubscribe);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Observe error occur if observed class name does not exists
|
|
192
|
-
this[kStateManagerClient].transport.addListener(OBSERVE_ERROR, (reqId, msg) => {
|
|
193
|
-
msg = `Cannot execute 'observe' on BaseStateManager: ${msg}`;
|
|
194
|
-
this.#observeRequestCallbacks.delete(reqId);
|
|
195
|
-
this.#promiseStore.reject(reqId, msg);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
this[kStateManagerClient].transport.addListener(
|
|
199
|
-
OBSERVE_NOTIFICATION,
|
|
200
|
-
(className, stateId, nodeId) => {
|
|
201
|
-
this.#observeListeners.forEach(observeInfos => {
|
|
202
|
-
const [observedClassName, callback, options] = observeInfos;
|
|
203
|
-
const filter = this.#filterObserve(observedClassName, className, nodeId, options);
|
|
204
|
-
|
|
205
|
-
if (!filter) {
|
|
206
|
-
callback(className, stateId, nodeId);
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
);
|
|
97
|
+
this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_RESPONSE, this.#onGetClassDescriptionResponse);
|
|
98
|
+
this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_ERROR, this.#onGetClassDescriptionError);
|
|
211
99
|
|
|
212
100
|
// ---------------------------------------------
|
|
213
101
|
// note 2025-05-05: caching of class descriptions has been removed because it
|
|
@@ -216,25 +104,32 @@ class BaseStateManager {
|
|
|
216
104
|
// ---------------------------------------------
|
|
217
105
|
// this[kStateManagerClient].transport.addListener(DELETE_SHARED_STATE_CLASS, _className => {});
|
|
218
106
|
|
|
219
|
-
// ---------------------------------------------
|
|
220
|
-
// Get class description
|
|
221
|
-
// ---------------------------------------------
|
|
222
|
-
this[kStateManagerClient].transport.addListener(
|
|
223
|
-
GET_CLASS_DESCRIPTION_RESPONSE,
|
|
224
|
-
(reqId, _className, classDescription) => {
|
|
225
|
-
const fullDescription = ParameterBag.getFullDescription(classDescription);
|
|
226
|
-
this.#promiseStore.resolve(reqId, fullDescription);
|
|
227
|
-
},
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_ERROR, (reqId, msg) => {
|
|
231
|
-
msg = `Cannot execute 'getClassDescription' on BaseStateManager: ${msg}`;
|
|
232
|
-
this.#promiseStore.reject(reqId, msg);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
107
|
this.#status = 'inited';
|
|
236
108
|
}
|
|
237
109
|
|
|
110
|
+
// This pattern allows to have a clean SharedState constructor signature for which
|
|
111
|
+
// which will provide a cleaner user facing API when derived.
|
|
112
|
+
// cf. instantiation pattern from AudioWorkletProcessor
|
|
113
|
+
// <https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-instantiation>
|
|
114
|
+
#buildSharedState(options) {
|
|
115
|
+
globalThis[kPendingSharedStateConstructionData] = {
|
|
116
|
+
manager: this,
|
|
117
|
+
className: options.className,
|
|
118
|
+
classDescription: options.classDescription,
|
|
119
|
+
stateId: options.stateId,
|
|
120
|
+
instanceId: options.instanceId,
|
|
121
|
+
isOwner: options.isOwner,
|
|
122
|
+
initValues: options.initValues,
|
|
123
|
+
filter: options.filter,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const state = new SharedState();
|
|
127
|
+
// cleanup global scope
|
|
128
|
+
delete globalThis[kPendingSharedStateConstructionData];
|
|
129
|
+
|
|
130
|
+
return state;
|
|
131
|
+
}
|
|
132
|
+
|
|
238
133
|
/**
|
|
239
134
|
* Validates the given class description.
|
|
240
135
|
*
|
|
@@ -266,13 +161,15 @@ class BaseStateManager {
|
|
|
266
161
|
return promise;
|
|
267
162
|
}
|
|
268
163
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
164
|
+
#onGetClassDescriptionResponse = (reqId, _className, classDescription) => {
|
|
165
|
+
const fullDescription = ParameterBag.getFullDescription(classDescription);
|
|
166
|
+
this.#promiseStore.resolve(reqId, fullDescription);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
#onGetClassDescriptionError = (reqId, msg) => {
|
|
170
|
+
msg = `Cannot execute 'getClassDescription' on BaseStateManager: ${msg}`;
|
|
171
|
+
this.#promiseStore.reject(reqId, msg);
|
|
172
|
+
};
|
|
276
173
|
|
|
277
174
|
/**
|
|
278
175
|
* Create a {@link SharedState} instance from a registered class.
|
|
@@ -294,6 +191,26 @@ class BaseStateManager {
|
|
|
294
191
|
return promise;
|
|
295
192
|
}
|
|
296
193
|
|
|
194
|
+
#onCreateResponse = (reqId, stateId, instanceId, className, classDescription, initValues) => {
|
|
195
|
+
const state = this.#buildSharedState({
|
|
196
|
+
className,
|
|
197
|
+
classDescription,
|
|
198
|
+
stateId,
|
|
199
|
+
instanceId,
|
|
200
|
+
isOwner: true,
|
|
201
|
+
initValues,
|
|
202
|
+
filter: null, // owner cannot filter parameters
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.#statesById.set(state.id, state);
|
|
206
|
+
this.#promiseStore.resolve(reqId, state);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
#onCreateError = (reqId, msg) => {
|
|
210
|
+
msg = `Cannot execute 'create' on BaseStateManager: ${msg}`;
|
|
211
|
+
this.#promiseStore.reject(reqId, msg);
|
|
212
|
+
};
|
|
213
|
+
|
|
297
214
|
/**
|
|
298
215
|
* Attach to an existing {@link SharedState} instance.
|
|
299
216
|
*
|
|
@@ -320,7 +237,9 @@ class BaseStateManager {
|
|
|
320
237
|
*
|
|
321
238
|
* @overload
|
|
322
239
|
* @param {SharedStateClassName} className - Name of the class.
|
|
323
|
-
* @param {
|
|
240
|
+
* @param {Object} [options={}]
|
|
241
|
+
* @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
|
|
242
|
+
* @param {string[]} [options.blackList] - Black list of parameter names to ignore.
|
|
324
243
|
* @returns {Promise<SharedState>}
|
|
325
244
|
*
|
|
326
245
|
* @example
|
|
@@ -332,7 +251,9 @@ class BaseStateManager {
|
|
|
332
251
|
* @overload
|
|
333
252
|
* @param {SharedStateClassName} className - Name of the class.
|
|
334
253
|
* @param {number} stateId - Id of the state
|
|
335
|
-
* @param {
|
|
254
|
+
* @param {object} [options={}]
|
|
255
|
+
* @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
|
|
256
|
+
* @param {string[]} [options.blackList] - Black list of parameter names to ignore.
|
|
336
257
|
* @returns {Promise<SharedState>}
|
|
337
258
|
*
|
|
338
259
|
* @example
|
|
@@ -344,21 +265,22 @@ class BaseStateManager {
|
|
|
344
265
|
* Alternative signatures:
|
|
345
266
|
* - `stateManager.attach(className)`
|
|
346
267
|
* - `stateManager.attach(className, stateId)`
|
|
347
|
-
* - `stateManager.attach(className,
|
|
348
|
-
* - `stateManager.attach(className, stateId,
|
|
268
|
+
* - `stateManager.attach(className, options)`
|
|
269
|
+
* - `stateManager.attach(className, stateId, options)`
|
|
349
270
|
*
|
|
350
271
|
* @param {SharedStateClassName} className - Name of the class.
|
|
351
|
-
* @param {number
|
|
272
|
+
* @param {number} [stateId] - Id of the state to attach to. If `null`,
|
|
352
273
|
* attach to the first state found with the given class name (useful for
|
|
353
274
|
* globally shared states owned by the server).
|
|
354
|
-
* @param {
|
|
355
|
-
*
|
|
275
|
+
* @param {object} [options={}]
|
|
276
|
+
* @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
|
|
277
|
+
* @param {string[]} [options.blackList] - Black list of parameter names to ignore.
|
|
356
278
|
* @returns {Promise<SharedState>}
|
|
357
279
|
*
|
|
358
280
|
* @example
|
|
359
281
|
* const state = await client.stateManager.attach('my-class');
|
|
360
282
|
*/
|
|
361
|
-
async attach(className,
|
|
283
|
+
async attach(className, stateIdOrOptions = null, options = null) {
|
|
362
284
|
if (this.#status !== 'inited') {
|
|
363
285
|
throw new DOMException(`Cannot execute 'attach' on BaseStateManager: BaseStateManager is not inited`, 'InvalidStateError');
|
|
364
286
|
}
|
|
@@ -370,38 +292,79 @@ class BaseStateManager {
|
|
|
370
292
|
}
|
|
371
293
|
|
|
372
294
|
if (arguments.length === 2) {
|
|
373
|
-
if (
|
|
295
|
+
if (stateIdOrOptions === null) {
|
|
296
|
+
stateId = null;
|
|
297
|
+
options = null;
|
|
298
|
+
} else if (Number.isFinite(stateIdOrOptions)) {
|
|
299
|
+
stateId = stateIdOrOptions;
|
|
300
|
+
options = null;
|
|
301
|
+
} else if (Array.isArray(stateIdOrOptions)) {
|
|
302
|
+
// backward compatibility for legacy filter array argument
|
|
303
|
+
warnings.deprecated(
|
|
304
|
+
'argument `filter: string[]` of BaseStateManager#attach',
|
|
305
|
+
'argument `options { whiteList: string[] }`',
|
|
306
|
+
'5.6.0',
|
|
307
|
+
);
|
|
374
308
|
stateId = null;
|
|
375
|
-
|
|
376
|
-
} else if (
|
|
377
|
-
stateId = stateIdOrFilter;
|
|
378
|
-
filter = null;
|
|
379
|
-
} else if (Array.isArray(stateIdOrFilter)) {
|
|
309
|
+
options = { whiteList: stateIdOrOptions };
|
|
310
|
+
} else if (isPlainObject(stateIdOrOptions)) {
|
|
380
311
|
stateId = null;
|
|
381
|
-
|
|
312
|
+
options = stateIdOrOptions;
|
|
382
313
|
} else {
|
|
383
|
-
throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null, a number or an
|
|
314
|
+
throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null, a number or an object`);
|
|
384
315
|
}
|
|
385
316
|
}
|
|
386
317
|
|
|
387
318
|
if (arguments.length === 3) {
|
|
388
|
-
stateId =
|
|
319
|
+
stateId = stateIdOrOptions;
|
|
389
320
|
|
|
390
321
|
if (stateId !== null && !Number.isFinite(stateId)) {
|
|
391
322
|
throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null or a number`);
|
|
392
323
|
}
|
|
393
324
|
|
|
394
|
-
if (
|
|
395
|
-
|
|
325
|
+
if (options !== null) {
|
|
326
|
+
if (Array.isArray(options)) {
|
|
327
|
+
// backward compatibility for legacy filter array argument
|
|
328
|
+
warnings.deprecated(
|
|
329
|
+
'argument `filter: string[]` of BaseStateManager#attach',
|
|
330
|
+
'argument `options { whiteList: string[] }`',
|
|
331
|
+
'5.6.0',
|
|
332
|
+
);
|
|
333
|
+
options = { whiteList: options };
|
|
334
|
+
} else if (!isPlainObject(options)) {
|
|
335
|
+
throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 3 must be either null or an object`);
|
|
336
|
+
}
|
|
337
|
+
// keep option object as is
|
|
396
338
|
}
|
|
397
339
|
}
|
|
398
340
|
|
|
399
341
|
const { id: reqId, promise } = this.#promiseStore.createPromise();
|
|
400
|
-
this[kStateManagerClient].transport.emit(ATTACH_REQUEST, reqId, className, stateId,
|
|
342
|
+
this[kStateManagerClient].transport.emit(ATTACH_REQUEST, reqId, className, stateId, options);
|
|
401
343
|
|
|
402
344
|
return promise;
|
|
403
345
|
}
|
|
404
346
|
|
|
347
|
+
#onAttachResponse = (reqId, stateId, instanceId, className, classDescription, currentValues, filter) => {
|
|
348
|
+
const state = this.#buildSharedState({
|
|
349
|
+
manager: this,
|
|
350
|
+
className,
|
|
351
|
+
classDescription,
|
|
352
|
+
stateId,
|
|
353
|
+
instanceId,
|
|
354
|
+
isOwner: false,
|
|
355
|
+
initValues: currentValues,
|
|
356
|
+
filter,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
this.#statesById.set(state.id, state);
|
|
360
|
+
this.#promiseStore.resolve(reqId, state);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
#onAttachError = (reqId, msg) => {
|
|
364
|
+
msg = `Cannot execute 'attach' on BaseStateManager: ${msg}`;
|
|
365
|
+
this.#promiseStore.reject(reqId, msg);
|
|
366
|
+
};
|
|
367
|
+
|
|
405
368
|
/**
|
|
406
369
|
* Observe all the {@link SharedState} instances that are created on the network.
|
|
407
370
|
*
|
|
@@ -437,7 +400,7 @@ class BaseStateManager {
|
|
|
437
400
|
* @param {stateManagerObserveCallback} callback - Function to execute when a
|
|
438
401
|
* new {@link SharedState} is created on the network.
|
|
439
402
|
* @param {object} options - Options.
|
|
440
|
-
* @param {boolean} options.excludeLocal=false - If set to true, exclude states
|
|
403
|
+
* @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
|
|
441
404
|
* created by the same node from the collection.
|
|
442
405
|
* @example
|
|
443
406
|
* client.stateManager.observe(async (className, stateId) => {
|
|
@@ -456,7 +419,7 @@ class BaseStateManager {
|
|
|
456
419
|
* @param {stateManagerObserveCallback} callback - Function to execute when a
|
|
457
420
|
* new {@link SharedState} is created on the network.
|
|
458
421
|
* @param {object} options - Options.
|
|
459
|
-
* @param {boolean} options.excludeLocal=false - If set to true, exclude states
|
|
422
|
+
* @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
|
|
460
423
|
* created by the same node from the collection.
|
|
461
424
|
* @example
|
|
462
425
|
* client.stateManager.observe('my-shared-state-class', async (className, stateId) => {
|
|
@@ -607,6 +570,74 @@ class BaseStateManager {
|
|
|
607
570
|
return promise;
|
|
608
571
|
}
|
|
609
572
|
|
|
573
|
+
/** @private */
|
|
574
|
+
#filterObserve(observedClassName, className, ownerId, options) {
|
|
575
|
+
let filter = true;
|
|
576
|
+
|
|
577
|
+
if (observedClassName === null || observedClassName === className) {
|
|
578
|
+
filter = false;
|
|
579
|
+
}
|
|
580
|
+
// filter states created by this client if excludeLocal is true
|
|
581
|
+
if (options.excludeLocal === true && ownerId === this[kStateManagerClient].id) {
|
|
582
|
+
filter = true;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return filter;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** @private */
|
|
589
|
+
#onObserveResponse = async (reqId, ...list) => {
|
|
590
|
+
// retrieve the callback that have been stored in `observe()` to make sure
|
|
591
|
+
// we don't call another callback that may have been registered earlier.
|
|
592
|
+
const observeInfos = this.#observeRequestCallbacks.get(reqId);
|
|
593
|
+
const [observedClassName, callback, options] = observeInfos;
|
|
594
|
+
// move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
|
|
595
|
+
// to guarantee future order of execution
|
|
596
|
+
this.#observeRequestCallbacks.delete(reqId);
|
|
597
|
+
this.#observeListeners.add(observeInfos);
|
|
598
|
+
|
|
599
|
+
const promises = list.map(([className, stateId, ownerId]) => {
|
|
600
|
+
const filter = this.#filterObserve(observedClassName, className, ownerId, options);
|
|
601
|
+
|
|
602
|
+
if (!filter) {
|
|
603
|
+
return callback(className, stateId, ownerId);
|
|
604
|
+
} else {
|
|
605
|
+
return Promise.resolve();
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
await Promise.all(promises);
|
|
610
|
+
|
|
611
|
+
const unsubscribe = () => {
|
|
612
|
+
this.#observeListeners.delete(observeInfos);
|
|
613
|
+
// no more listeners, we can stop receiving notifications from the server
|
|
614
|
+
if (this.#observeListeners.size === 0) {
|
|
615
|
+
this[kStateManagerClient].transport.emit(UNOBSERVE_NOTIFICATION);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
this.#promiseStore.resolve(reqId, unsubscribe);
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
/** @private */
|
|
623
|
+
#onObserveError = (reqId, msg) => {
|
|
624
|
+
msg = `Cannot execute 'observe' on BaseStateManager: ${msg}`;
|
|
625
|
+
this.#observeRequestCallbacks.delete(reqId);
|
|
626
|
+
this.#promiseStore.reject(reqId, msg);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
/** @private */
|
|
630
|
+
#onObserveNotification = (className, stateId, ownerId) => {
|
|
631
|
+
this.#observeListeners.forEach(observeInfos => {
|
|
632
|
+
const [observedClassName, callback, options] = observeInfos;
|
|
633
|
+
const filter = this.#filterObserve(observedClassName, className, ownerId, options);
|
|
634
|
+
|
|
635
|
+
if (!filter) {
|
|
636
|
+
callback(className, stateId, ownerId);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
|
|
610
641
|
/**
|
|
611
642
|
* Returns a collection of all the states created from a given shared state class.
|
|
612
643
|
*
|
|
@@ -622,62 +653,38 @@ class BaseStateManager {
|
|
|
622
653
|
*
|
|
623
654
|
* @overload
|
|
624
655
|
* @param {SharedStateClassName} className - Name of the shared state class.
|
|
625
|
-
* @param {
|
|
626
|
-
*
|
|
627
|
-
* @returns {Promise<SharedStateCollection>}
|
|
628
|
-
*
|
|
629
|
-
* @example
|
|
630
|
-
* const collection = await client.stateManager.getCollection(className, ['my-param']);
|
|
631
|
-
*/
|
|
632
|
-
/**
|
|
633
|
-
* Returns a collection of all the states created from a given shared state class.
|
|
634
|
-
*
|
|
635
|
-
* @overload
|
|
636
|
-
* @param {SharedStateClassName} className - Name of the shared state class.
|
|
637
|
-
* @param {object} options - Options.
|
|
638
|
-
* @param {boolean} options.excludeLocal=false - If set to true, exclude states
|
|
639
|
-
* created by the same node from the collection.
|
|
640
|
-
* @returns {Promise<SharedStateCollection>}
|
|
641
|
-
*
|
|
642
|
-
* @example
|
|
643
|
-
* const collection = await client.stateManager.getCollection(className, { excludeLocal: true });
|
|
644
|
-
*/
|
|
645
|
-
/**
|
|
646
|
-
* Returns a collection of all the states created from a given shared state class.
|
|
647
|
-
*
|
|
648
|
-
* @overload
|
|
649
|
-
* @param {SharedStateClassName} className - Name of the shared state class.
|
|
650
|
-
* @param {SharedStateParameterName[]} filter - Filter parameter of interest for each
|
|
651
|
-
* state of the collection.
|
|
652
|
-
* @param {object} options - Options.
|
|
653
|
-
* @param {boolean} options.excludeLocal=false - If set to true, exclude states
|
|
656
|
+
* @param {object} [options={}] - Options.
|
|
657
|
+
* @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
|
|
654
658
|
* created by the same node from the collection.
|
|
659
|
+
* @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
|
|
660
|
+
* @param {string[]} [options.blackList] - Black list of parameter names to ignore.
|
|
655
661
|
* @returns {Promise<SharedStateCollection>}
|
|
656
662
|
*
|
|
657
663
|
* @example
|
|
658
|
-
* const collection = await client.stateManager.getCollection(className,
|
|
664
|
+
* const collection = await client.stateManager.getCollection(className, {
|
|
665
|
+
* blackList: ['my-param'],
|
|
666
|
+
* excludeLocal: true,
|
|
667
|
+
* });
|
|
659
668
|
*/
|
|
660
669
|
/**
|
|
661
670
|
* Returns a collection of all the states created from a given shared state class.
|
|
662
671
|
*
|
|
663
672
|
* Alternative signatures:
|
|
664
673
|
* - `stateManager.getCollection(className)`
|
|
665
|
-
* - `stateManager.getCollection(className, filter)`
|
|
666
674
|
* - `stateManager.getCollection(className, options)`
|
|
667
|
-
* - `stateManager.getCollection(className, filter, options)`
|
|
668
675
|
*
|
|
669
676
|
* @param {SharedStateClassName} className - Name of the shared state class.
|
|
670
|
-
* @param {array|null} [filter=null] - Filter parameter of interest for each
|
|
671
|
-
* state of the collection. If set to `null`, no filter applied.
|
|
672
677
|
* @param {object} [options={}] - Options.
|
|
673
678
|
* @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
|
|
674
679
|
* created by the same node from the collection.
|
|
680
|
+
* @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
|
|
681
|
+
* @param {string[]} [options.blackList] - Black list of parameter names to ignore.
|
|
675
682
|
* @returns {Promise<SharedStateCollection>}
|
|
676
683
|
*
|
|
677
684
|
* @example
|
|
678
685
|
* const collection = await client.stateManager.getCollection(className);
|
|
679
686
|
*/
|
|
680
|
-
async getCollection(className,
|
|
687
|
+
async getCollection(className, options = {}) {
|
|
681
688
|
if (this.#status !== 'inited') {
|
|
682
689
|
throw new DOMException(`Cannot execute 'getCollection' on BaseStateManager: BaseStateManager is not inited`, 'InvalidStateError');
|
|
683
690
|
}
|
|
@@ -686,36 +693,45 @@ class BaseStateManager {
|
|
|
686
693
|
throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 1 should be a string"`);
|
|
687
694
|
}
|
|
688
695
|
|
|
689
|
-
let filter;
|
|
690
|
-
|
|
691
696
|
if (arguments.length === 2) {
|
|
692
|
-
if (
|
|
693
|
-
filter
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
options =
|
|
701
|
-
} else {
|
|
702
|
-
throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either null
|
|
697
|
+
if (Array.isArray(options)) {
|
|
698
|
+
// backward compatibility for legacy filter array argument
|
|
699
|
+
warnings.deprecated(
|
|
700
|
+
'argument `filter: string[]` of BaseStateManager#getCollection',
|
|
701
|
+
'argument `options { whiteList: string[] }`',
|
|
702
|
+
'5.6.0',
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
options = { whiteList: options };
|
|
706
|
+
} else if (!isPlainObject(options)) {
|
|
707
|
+
throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either null or an object"`);
|
|
703
708
|
}
|
|
704
709
|
}
|
|
705
710
|
|
|
711
|
+
// backward compatibility for legacy filter array argument
|
|
706
712
|
if (arguments.length === 3) {
|
|
707
|
-
filter
|
|
713
|
+
// backward compatibility for legacy filter array argument
|
|
714
|
+
warnings.deprecated(
|
|
715
|
+
'argument `filter: string[]` of BaseStateManager#getCollection',
|
|
716
|
+
'argument `options { whiteList: string[] }`',
|
|
717
|
+
'5.6.0',
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
const filter = options;
|
|
721
|
+
options = arguments[2];
|
|
708
722
|
|
|
709
723
|
if (filter !== null && !Array.isArray(filter)) {
|
|
710
724
|
throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either an array or null"`);
|
|
711
725
|
}
|
|
712
726
|
|
|
713
|
-
if (options === null ||
|
|
727
|
+
if (options === null || !isPlainObject(options)) {
|
|
714
728
|
throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 3 should be either an object"`);
|
|
715
729
|
}
|
|
730
|
+
|
|
731
|
+
options.whiteList = filter;
|
|
716
732
|
}
|
|
717
733
|
|
|
718
|
-
const collection = new SharedStateCollection(this, className,
|
|
734
|
+
const collection = new SharedStateCollection(this, className, options);
|
|
719
735
|
|
|
720
736
|
try {
|
|
721
737
|
await collection[kSharedStateCollectionInit]();
|
|
@@ -725,6 +741,17 @@ class BaseStateManager {
|
|
|
725
741
|
|
|
726
742
|
return collection;
|
|
727
743
|
}
|
|
744
|
+
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
746
|
+
// DEPRECATED
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
/**
|
|
749
|
+
* @deprecated Use {@link BaseStateManager#getClassDescription} instead.
|
|
750
|
+
*/
|
|
751
|
+
async getSchema(className) {
|
|
752
|
+
warnings.deprecated('BaseStateManager#getSchema', 'BaseStateManager#getClassDescription', '4.0.0-alpha.29');
|
|
753
|
+
return this.getClassDescription(className);
|
|
754
|
+
}
|
|
728
755
|
}
|
|
729
756
|
|
|
730
757
|
export default BaseStateManager;
|