@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soundworks/core",
3
- "version": "5.6.0",
3
+ "version": "5.8.0",
4
4
  "description": "Open-source creative coding framework for distributed applications based on Web technologies",
5
5
  "collaborators": [
6
6
  "Benjamin Matuszewski",
@@ -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
- // CREATE
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
- // OBSERVE PEERS (be notified when a state is created, lazy)
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
- if (!filter) {
172
- return callback(className, stateId, nodeId);
173
- } else {
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
- await Promise.all(promises);
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
- * @deprecated Use {@link BaseStateManager#getClassDescription} instead.
271
- */
272
- async getSchema(className) {
273
- warnings.deprecated('BaseStateManager#getSchema', 'BaseStateManager#getClassDescription', '4.0.0-alpha.29');
274
- return this.getClassDescription(className);
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 {string[]} filter - List of parameters of interest
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 {string[]} filter - List of parameters of interest
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, filter)`
348
- * - `stateManager.attach(className, stateId, filter)`
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|string[]} [stateIdOrFilter] - Id of the state to attach to. If `null`,
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 {string[]} [filter] - List of parameters of interest in the
355
- * returned state. If set to `null`, no filter is applied.
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, stateIdOrFilter = null, filter = null) {
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 (stateIdOrFilter === null) {
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
- filter = null;
376
- } else if (Number.isFinite(stateIdOrFilter)) {
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
- filter = stateIdOrFilter;
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 array`);
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 = stateIdOrFilter;
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 (filter !== null && !Array.isArray(filter)) {
395
- throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 3 must be either null or an array`);
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, filter);
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 {SharedStateParameterName[]} filter - Filter parameter of interest for each
626
- * state of the collection.
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, ['my-param'], { excludeLocal: true });
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, filterOrOptions = null, options = {}) {
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 (filterOrOptions === null) {
693
- filter = null;
694
- options = null;
695
- } else if (Array.isArray(filterOrOptions)) {
696
- filter = filterOrOptions;
697
- options = {};
698
- } else if (typeof filterOrOptions === 'object') {
699
- filter = null;
700
- options = filterOrOptions;
701
- } else {
702
- throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either null, an array or an object"`);
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 = filterOrOptions;
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 || typeof options !== 'object') {
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, filter, options);
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;