@soundworks/core 5.2.1 → 5.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soundworks/core",
3
- "version": "5.2.1",
3
+ "version": "5.4.0",
4
4
  "description": "Open-source creative coding framework for distributed applications based on Web technologies",
5
5
  "collaborators": [
6
6
  "Benjamin Matuszewski",
@@ -314,6 +314,10 @@ class Client {
314
314
  * await client.start();
315
315
  */
316
316
  async init() {
317
+ if (this.#status === 'inited') {
318
+ throw new DOMException(`Cannot execute 'init' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
319
+ }
320
+
317
321
  if (this.#status !== 'idle') {
318
322
  throw new DOMException(`Cannot execute 'init' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
319
323
  }
@@ -391,6 +395,10 @@ class Client {
391
395
  await this.init();
392
396
  }
393
397
 
398
+ if (this.#status === 'started') {
399
+ throw new DOMException(`Cannot execute 'start' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
400
+ }
401
+
394
402
  if (this.#status !== 'inited') {
395
403
  throw new DOMException(`Cannot execute 'start' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
396
404
  }
@@ -416,6 +424,10 @@ class Client {
416
424
  * await client.stop();
417
425
  */
418
426
  async stop() {
427
+ if (this.#status === 'stopped') {
428
+ throw new DOMException(`Cannot execute 'stop' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
429
+ }
430
+
419
431
  if (this.#status !== 'started') {
420
432
  throw new DOMException(`Cannot execute 'stop' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
421
433
  }
@@ -230,6 +230,17 @@ class BaseStateManager {
230
230
  this.#status = 'inited';
231
231
  }
232
232
 
233
+ /**
234
+ * Validates the given class description.
235
+ *
236
+ * @throws Throws if the class description is invalid
237
+ * @param {*} classDescription
238
+ * @returns undefined
239
+ */
240
+ validateClassDescription(classDescription) {
241
+ return ParameterBag.validateDescription(classDescription);
242
+ }
243
+
233
244
  /**
234
245
  * Return a class description from a given class name
235
246
  *
@@ -12,6 +12,7 @@ export const sharedOptions = {
12
12
  metas: {},
13
13
  filterChange: true,
14
14
  immediate: false,
15
+ acknowledge: true,
15
16
  };
16
17
 
17
18
  export const types = {
@@ -214,11 +214,19 @@ class SharedState {
214
214
  this.#clearTransport();
215
215
 
216
216
  for (let callback of this.#onDetachCallbacks) {
217
- await callback();
217
+ try {
218
+ await callback();
219
+ } catch (err) {
220
+ console.log(err.message);
221
+ }
218
222
  }
219
223
 
220
224
  for (let callback of this.#onDeleteCallbacks) {
221
- await callback();
225
+ try {
226
+ await callback();
227
+ } catch (err) {
228
+ console.log(err.message);
229
+ }
222
230
  }
223
231
 
224
232
  this.#onDetachCallbacks.clear();
@@ -240,7 +248,11 @@ class SharedState {
240
248
  this.#clearTransport();
241
249
 
242
250
  for (let callback of this.#onDetachCallbacks) {
243
- await callback();
251
+ try {
252
+ await callback();
253
+ } catch (err) {
254
+ console.log(err.message);
255
+ }
244
256
  }
245
257
 
246
258
  this.#onDetachCallbacks.clear();
@@ -465,31 +477,31 @@ class SharedState {
465
477
 
466
478
  const newValues = {};
467
479
  const oldValues = {};
468
- const localParams = {};
469
- const sharedParams = {};
480
+ const locallyResolvedParam = {};
481
+ const networkPropagatedParams = {};
470
482
  let hasLocalParam = false;
471
- let hasSharedParam = false;
472
- let forwardParams = undefined;
473
- let propagateNow = false;
483
+ let hasRegularSharedParam = false;
484
+ let hasNonAcknowledgedParams = false;
485
+ let syncedOnUpdate = false;
474
486
 
475
487
  for (let name in updates) {
476
- // Try to coerce value early, so that eventual errors are triggered early
477
- // on the node requesting the update, and not only on the server side
478
- // This throws if name does not exists
488
+ // Try to coerce value now, so that eventual errors are triggered early
489
+ // on the node requesting the update and not only on the server side.
479
490
  try {
491
+ // This also throws if name does not exists
480
492
  this.#parameters.coerceValue(name, updates[name]);
481
493
  } catch (err) {
482
494
  throw new TypeError(`Cannot execute 'set' on SharedState (${this.#className}): ${err.message}`);
483
495
  }
484
496
 
485
- // Check that name is in filter list, if any
497
+ // Make sure that given name is in filter white list (if any)
486
498
  if (this.#filter !== null) {
487
499
  if (!this.#filter.includes(name)) {
488
500
  throw new DOMException(`Cannot execute 'set' on SharedState (${this.#className}): Parameter '${name}' is not in white list`, 'NotSupportedError');
489
501
  }
490
502
  }
491
503
 
492
- // `immediate` option behavior
504
+ // ### `immediate` modifier behavior
493
505
  //
494
506
  // If immediate=true
495
507
  // - call listeners if value changed
@@ -501,7 +513,7 @@ class SharedState {
501
513
  // - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
502
514
  // to prevent execute the listeners twice
503
515
 
504
- // `local` option behavior
516
+ // ### `local` modifier behavior
505
517
  //
506
518
  // - If the `updates` object only contains local variables, we can call the
507
519
  // update listeners and return a fulfilled promise immediately
@@ -510,9 +522,19 @@ class SharedState {
510
522
  // response, call update listeners with server response and resolve promise with
511
523
  // the full payload, i.e. reintegrating the local params in the resolve payload
512
524
 
513
- const { local, immediate, filterChange, event } = this.#parameters.getDescription(name);
525
+ // ### `acknowledge` modifier behavior
526
+ //
527
+ // Same as immediate, but no feedback from server
514
528
 
515
- if (immediate || local) {
529
+ const {
530
+ local,
531
+ immediate,
532
+ filterChange,
533
+ event,
534
+ acknowledge,
535
+ } = this.#parameters.getDescription(name);
536
+
537
+ if (immediate || local || acknowledge === false) {
516
538
  const oldValue = this.#parameters.get(name);
517
539
  const [newValue, changed] = this.#parameters.set(name, updates[name]);
518
540
 
@@ -520,46 +542,50 @@ class SharedState {
520
542
  if (changed || filterChange === false || event) {
521
543
  oldValues[name] = oldValue;
522
544
  newValues[name] = newValue;
523
- propagateNow = true;
545
+ syncedOnUpdate = true;
524
546
  }
525
547
  }
526
548
 
527
- // define params that must go through network or not
528
549
  if (local) {
550
+ // these don't go to the network
529
551
  hasLocalParam = true;
530
- // get sanitize value for fulfilling promise
531
- localParams[name] = this.#parameters.get(name);
552
+ locallyResolvedParam[name] = this.#parameters.get(name);
553
+ } else if (acknowledge === false) {
554
+ // these go to the network but don't receive an ack
555
+ // they are both locally forwarded and network propagated then
556
+ hasNonAcknowledgedParams = true;
557
+ locallyResolvedParam[name] = this.#parameters.get(name);
558
+ networkPropagatedParams[name] = updates[name];
532
559
  } else {
533
- // note that immediate are shared params too
534
- hasSharedParam = true;
535
- sharedParams[name] = updates[name];
560
+ // note that immediate params are shared and acknowledged
561
+ hasRegularSharedParam = true;
562
+ networkPropagatedParams[name] = updates[name];
536
563
  }
537
564
  }
538
565
 
539
- // propagate immediate params if changed
540
- if (propagateNow) {
566
+ // params that trigger a synced onUpdate call: immediate, local, non-acknowledged
567
+ if (syncedOnUpdate) {
541
568
  this.#onUpdateCallbacks.forEach(listener => listener(newValues, oldValues));
542
569
  }
543
570
 
544
- // check if we can resolve immediately or if we need to go through network
545
- if (hasLocalParam) {
546
- if (!hasSharedParam) {
547
- return Promise.resolve(localParams);
548
- } else {
549
- // store local params to fulfill promise with all values, see PromiseStore
550
- forwardParams = localParams;
551
- }
571
+ // if we only have local params, we can resolve immediately
572
+ if (hasLocalParam && !hasNonAcknowledgedParams && !hasRegularSharedParam) {
573
+ return Promise.resolve(locallyResolvedParam);
552
574
  }
553
575
 
554
- // override updates to be shared on network without local params
555
- updates = sharedParams;
556
-
557
576
  // go through server-side normal behavior
558
577
  const { id: reqId, promise } = this[kSharedStatePromiseStore].createPromise();
559
- if (forwardParams) {
560
- this[kSharedStatePromiseStore].associateResolveData(reqId, forwardParams);
578
+ // store local params that should be re-merged w/ regular ones
579
+ if (Object.keys(locallyResolvedParam).length > 0) {
580
+ this[kSharedStatePromiseStore].associateResolveData(reqId, locallyResolvedParam);
581
+ }
582
+
583
+ this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, networkPropagatedParams);
584
+
585
+ // if we don't have regular shared params, we can resolve now
586
+ if (!hasRegularSharedParam) {
587
+ this[kSharedStatePromiseStore].resolve(reqId, {});
561
588
  }
562
- this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, updates);
563
589
 
564
590
  return promise;
565
591
  }
@@ -746,7 +772,7 @@ class SharedState {
746
772
 
747
773
  return this.detach();
748
774
  } else {
749
- throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}): SharedState (${this.#id}) is not owned by this node. Use 'SharedState#detach' instead`, 'NotSupportedError');
775
+ throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#detach' instead`, 'NotSupportedError');
750
776
  }
751
777
  }
752
778
 
@@ -814,6 +840,10 @@ class SharedState {
814
840
  * @param {Function} callback - Callback to execute when the state is deleted.
815
841
  */
816
842
  onDelete(callback) {
843
+ if (!this.#isOwner) {
844
+ throw new DOMException(`Cannot execute 'onDelete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#onDetach' instead`, 'NotSupportedError');
845
+ }
846
+
817
847
  this.#onDeleteCallbacks.add(callback);
818
848
  return () => this.#onDeleteCallbacks.delete(callback);
819
849
  }
@@ -52,25 +52,33 @@
52
52
  * @property {boolean} [event=false] - Define if the parameter is a volatile, i.e.
53
53
  * its value only exists on an update and is set back to `null` after propagation.
54
54
  * When `true`, `nullable` is automatically set to `true` and `default` to `null`.
55
+ * @property {boolean} [required=false] - When set to true, the parameter must be
56
+ * provided at the creation of the shared state.
55
57
  * @property {boolean} [filterChange=true] - When set to `false`, an update will
56
58
  * trigger the propagation of a parameter even when its value didn't change.
57
- * This option provides a sort of middle ground between the default behavior
59
+ * This modifier provides a sort of middle ground between the default behavior
58
60
  * (e.g. where only changed values are propagated) and the behavior of the `event`
59
61
  * option (which has no state per se). Hence, setting this options to `false` if
60
62
  * `event=true` makes no sens.
61
63
  * @property {boolean} [immediate=false] - When set to `true`, an update will
62
64
  * trigger the update listeners immediately on the node that generated the update,
63
- * before propagating the change on the network.
64
- * This option is useful in cases the network would introduce a noticeable
65
- * latency on the client.
65
+ * before propagating the change on the network. This modifier is useful in cases
66
+ * the network would introduce a noticeable latency on the client.
66
67
  * If for some reason the value is overridden server-side (e.g. in an `updateHook`)
67
68
  * the listeners will be called again when the "real" value is received.
68
- * @property {boolean} [required=false] - When set to true, the parameter must be
69
- * provided in the initialization values when the state is created.
69
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
70
+ * according to the `set` call.
70
71
  * @property {boolean} [local=false] - When set to true, the parameter is never
71
72
  * propagated on the network (hence it is no longer a shared parameter :). This
72
73
  * is useful to declare some common parameter (e.g. some interface state) that
73
74
  * don't need to be shared but to stay in the shared state API paradigm.
75
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
76
+ * according to the `set` call.
77
+ * @property {boolean} [acknowledge=true] - When set to false, the acknowledgement
78
+ * is never sent back to the shared state that initiated an update. This can be
79
+ * useful to e.g. stream values to the network, but can lead to invalid state.
80
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
81
+ * according to the `set` call.
74
82
  * @property {number} [min=-Number.MIN_VALUE] - Minimum value of the parameter. Only applies
75
83
  * for `integer` and `float` types.
76
84
  * @property {number} [max=Number.MAX_VALUE] - Maximum value of the parameter. Only applies
@@ -1,2 +1,2 @@
1
1
  // [WARNING] This file is generated, do not edit
2
- export default '5.2.1';
2
+ export default '5.4.0';
@@ -482,6 +482,10 @@ class Server {
482
482
  * await server.start(); // init is called implicitly
483
483
  */
484
484
  async init() {
485
+ if (this.#status === 'inited') {
486
+ throw new DOMException(`Cannot execute 'init' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
487
+ }
488
+
485
489
  if (this.#status !== 'idle') {
486
490
  throw new DOMException(`Cannot execute 'init' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
487
491
  }
@@ -547,6 +551,10 @@ class Server {
547
551
  await this.init();
548
552
  }
549
553
 
554
+ if (this.#status === 'started') {
555
+ throw new DOMException(`Cannot execute 'start' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
556
+ }
557
+
550
558
  if (this.#status !== 'inited') {
551
559
  throw new DOMException(`Cannot execute 'start' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
552
560
  }
@@ -601,6 +609,10 @@ class Server {
601
609
  * await server.stop();
602
610
  */
603
611
  async stop() {
612
+ if (this.#status === 'stopped') {
613
+ throw new DOMException(`Cannot execute 'stop' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
614
+ }
615
+
604
616
  if (this.#status !== 'started') {
605
617
  throw new DOMException(`Cannot execute 'stop' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
606
618
  }
@@ -16,18 +16,18 @@ import {
16
16
  } from '../server/ServerStateManager.js';
17
17
 
18
18
  /**
19
- * Filter update object according to filter list.
19
+ * Filter update object according to a given white list.
20
20
  * Return identity is filter is null
21
21
  * @param {object} updates
22
- * @param {array|null} filter
22
+ * @param {array|null} whiteList
23
23
  * @private
24
24
  */
25
- function filterUpdates(updates, filter) {
26
- if (filter === null) {
25
+ function whiteListFilterUpdates(updates, whiteList) {
26
+ if (whiteList === null) {
27
27
  return updates;
28
28
  }
29
29
 
30
- return filter.reduce((acc, key) => {
30
+ return whiteList.reduce((acc, key) => {
31
31
  if (key in updates) {
32
32
  acc[key] = updates[key];
33
33
  }
@@ -35,6 +35,29 @@ function filterUpdates(updates, filter) {
35
35
  }, {});
36
36
  }
37
37
 
38
+ /**
39
+ * Filter update object according to a given black list.
40
+ * Return identity is filter is null
41
+ * @param {object} updates
42
+ * @param {array|null} filter
43
+ * @private
44
+ */
45
+ function blackListFilterUpdates(updates, blackList) {
46
+ if (blackList === null) {
47
+ return updates;
48
+ }
49
+ // create a shallow copy so we can have multiple filters applied on same source
50
+ const filtered = Object.assign({}, updates);
51
+
52
+ blackList.forEach(key => {
53
+ if (key in updates) {
54
+ delete filtered[key];
55
+ }
56
+ });
57
+
58
+ return filtered;
59
+ }
60
+
38
61
  export const kSharedStatePrivateAttachClient = Symbol('soundworks:shared-state-private-attach-client');
39
62
  export const kSharedStatePrivateDetachClient = Symbol('soundworks:shared-state-private-detach-client');
40
63
  export const kSharedStatePrivateGetValues = Symbol('soundworks:shared-state-private-get-values');
@@ -122,13 +145,13 @@ class SharedStatePrivate {
122
145
 
123
146
  if (hookAborted === false) {
124
147
  let acknowledgedUpdates = {};
125
- let hasUpdates = false;
148
+ let acknowledgeFilter = [];
126
149
 
127
150
  for (let name in updates) {
128
151
  const [newValue, changed] = this.#parameters.set(name, updates[name]);
129
152
  // if `filterChange` is set to `false` we don't check if the value
130
153
  // has been changed or not, it is always propagated to client states
131
- const { event, filterChange } = this.#parameters.getDescription(name);
154
+ const { event, filterChange, acknowledge } = this.#parameters.getDescription(name);
132
155
 
133
156
  // if event type reset internal value to null
134
157
  if (event) {
@@ -137,11 +160,14 @@ class SharedStatePrivate {
137
160
 
138
161
  if ((filterChange && changed) || !filterChange) {
139
162
  acknowledgedUpdates[name] = newValue;
140
- hasUpdates = true;
163
+
164
+ if (acknowledge === false) {
165
+ acknowledgeFilter.push(name);
166
+ }
141
167
  }
142
168
  }
143
169
 
144
- if (hasUpdates) {
170
+ if (Object.keys(acknowledgedUpdates).length > 0) {
145
171
  // Normal case
146
172
  //
147
173
  // We need to handle cases where:
@@ -161,25 +187,29 @@ class SharedStatePrivate {
161
187
  //
162
188
  // @note - instanceId correspond to unique remote state id
163
189
 
190
+ // - Apply acknowledge filter on `acknowledgedUpdates`
191
+ // - We don't need to apply the regular filter on update request, they are blocked client-side
192
+ const requesterFilteredUpdates = blackListFilterUpdates(acknowledgedUpdates, acknowledgeFilter);
193
+
164
194
  // propagate RESPONSE to the client that originates the request if not the server
165
195
  if (client.id !== -1) {
166
- // no need to filter updates on requested, is blocked on client-side
167
- client.transport.emit(
168
- `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
169
- reqId,
170
- acknowledgedUpdates,
171
- );
196
+ // propagate only if there something left after applying the acknowledge filter
197
+ if (Object.keys(requesterFilteredUpdates).length > 0) {
198
+ client.transport.emit(
199
+ `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
200
+ reqId,
201
+ requesterFilteredUpdates,
202
+ );
203
+ };
172
204
  }
173
205
 
174
- // propagate NOTIFICATION to all attached clients except server
206
+ // propagate NOTIFICATION to all peer states except on server-side
175
207
  for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
176
- const peer = clientInfos.client;
208
+ const { client: peer, filter } = clientInfos;
177
209
 
178
210
  if (instanceId !== peerInstanceId && peer.id !== -1) {
179
- const filter = clientInfos.filter;
180
- const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
181
-
182
- // propagate only if there something left after applying the filter
211
+ const filteredUpdates = whiteListFilterUpdates(acknowledgedUpdates, filter);
212
+ // propagate only if there something left after applying the white list filter
183
213
  if (Object.keys(filteredUpdates).length > 0) {
184
214
  peer.transport.emit(
185
215
  `${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
@@ -191,22 +221,23 @@ class SharedStatePrivate {
191
221
 
192
222
  // propagate RESPONSE to server if it is the requester
193
223
  if (client.id === -1) {
194
- // no need to filter updates on requested, is blocked on client-side
195
- client.transport.emit(
196
- `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
197
- reqId,
198
- acknowledgedUpdates,
199
- );
224
+ // propagate only if there something left after applying the acknowledge filter
225
+ if (Object.keys(requesterFilteredUpdates).length > 0) {
226
+ client.transport.emit(
227
+ `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
228
+ reqId,
229
+ requesterFilteredUpdates,
230
+ );
231
+ }
200
232
  }
201
233
 
202
- // propagate NOTIFICATION to other state attached on the server
234
+ // propagate NOTIFICATION to all peer states on the server-side
203
235
  for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
204
- const peer = clientInfos.client;
236
+ const { client: peer, filter } = clientInfos;
205
237
 
206
238
  if (instanceId !== peerInstanceId && peer.id === -1) {
207
- const filter = clientInfos.filter;
208
- const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
209
-
239
+ const filteredUpdates = whiteListFilterUpdates(acknowledgedUpdates, filter);
240
+ // propagate only if there something left after applying the white list filter
210
241
  if (Object.keys(filteredUpdates).length > 0) {
211
242
  peer.transport.emit(
212
243
  `${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
@@ -216,8 +247,7 @@ class SharedStatePrivate {
216
247
  }
217
248
  }
218
249
  } else {
219
- // propagate back to the requester that the update has been aborted
220
- // ignore all other attached clients.
250
+ // propagate back to the requester that the update has been aborted, ignore all peers
221
251
  client.transport.emit(`${UPDATE_ABORT}-${this.id}-${instanceId}`, reqId, updates);
222
252
  }
223
253
  } else {
@@ -28,6 +28,14 @@ export type stateManagerDeleteObserveCallback = () => any;
28
28
  */
29
29
  /** @private */
30
30
  declare class BaseStateManager {
31
+ /**
32
+ * Validates the given class description.
33
+ *
34
+ * @throws Throws if the class description is invalid
35
+ * @param {*} classDescription
36
+ * @returns undefined
37
+ */
38
+ validateClassDescription(classDescription: any): void;
31
39
  /**
32
40
  * Return a class description from a given class name
33
41
  *
@@ -5,6 +5,7 @@ export namespace sharedOptions {
5
5
  let metas: {};
6
6
  let filterChange: boolean;
7
7
  let immediate: boolean;
8
+ let acknowledge: boolean;
8
9
  }
9
10
  export namespace types {
10
11
  export namespace boolean {
@@ -42,10 +42,15 @@ type SharedStateParameterDescription = {
42
42
  * When `true`, `nullable` is automatically set to `true` and `default` to `null`.
43
43
  */
44
44
  event?: boolean;
45
+ /**
46
+ * - When set to true, the parameter must be
47
+ * provided at the creation of the shared state.
48
+ */
49
+ required?: boolean;
45
50
  /**
46
51
  * - When set to `false`, an update will
47
52
  * trigger the propagation of a parameter even when its value didn't change.
48
- * This option provides a sort of middle ground between the default behavior
53
+ * This modifier provides a sort of middle ground between the default behavior
49
54
  * (e.g. where only changed values are propagated) and the behavior of the `event`
50
55
  * option (which has no state per se). Hence, setting this options to `false` if
51
56
  * `event=true` makes no sens.
@@ -54,25 +59,31 @@ type SharedStateParameterDescription = {
54
59
  /**
55
60
  * - When set to `true`, an update will
56
61
  * trigger the update listeners immediately on the node that generated the update,
57
- * before propagating the change on the network.
58
- * This option is useful in cases the network would introduce a noticeable
59
- * latency on the client.
62
+ * before propagating the change on the network. This modifier is useful in cases
63
+ * the network would introduce a noticeable latency on the client.
60
64
  * If for some reason the value is overridden server-side (e.g. in an `updateHook`)
61
65
  * the listeners will be called again when the "real" value is received.
66
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
67
+ * according to the `set` call.
62
68
  */
63
69
  immediate?: boolean;
64
- /**
65
- * - When set to true, the parameter must be
66
- * provided in the initialization values when the state is created.
67
- */
68
- required?: boolean;
69
70
  /**
70
71
  * - When set to true, the parameter is never
71
72
  * propagated on the network (hence it is no longer a shared parameter :). This
72
73
  * is useful to declare some common parameter (e.g. some interface state) that
73
74
  * don't need to be shared but to stay in the shared state API paradigm.
75
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
76
+ * according to the `set` call.
74
77
  */
75
78
  local?: boolean;
79
+ /**
80
+ * - When set to false, the acknowledgement
81
+ * is never sent back to the shared state that initiated an update. This can be
82
+ * useful to e.g. stream values to the network, but can lead to invalid state.
83
+ * Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
84
+ * according to the `set` call.
85
+ */
86
+ acknowledge?: boolean;
76
87
  /**
77
88
  * - Minimum value of the parameter. Only applies
78
89
  * for `integer` and `float` types.
@@ -1,2 +1,2 @@
1
- declare const _default: "5.2.1";
1
+ declare const _default: "5.4.0";
2
2
  export default _default;