@soundworks/core 4.0.0-alpha.1 → 4.0.0-alpha.11

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.
@@ -1,195 +1,6 @@
1
1
  import cloneDeep from 'lodash.clonedeep';
2
2
  import equal from 'fast-deep-equal';
3
3
 
4
- /**
5
- * @typedef {Object} server.SharedStateManagerServer~schema
6
- *
7
- * Description of a schema to be register by the {@link server.ServerStateManagerServer}
8
- * A schema consists of a combinaison of key value pairs where the key is the
9
- * name of the parameter, and the value is an object describing the parameter.
10
- *
11
- * Available types are:
12
- * - {@link server.SharedStateManagerServer~schemaBooleanDef}
13
- * - {@link server.SharedStateManagerServer~schemaStringDef}
14
- * - {@link server.SharedStateManagerServer~schemaIntegerDef}
15
- * - {@link server.SharedStateManagerServer~schemaFloatDef}
16
- * - {@link server.SharedStateManagerServer~schemaEnumDef}
17
- * - {@link server.SharedStateManagerServer~schemaAnyDef}
18
- *
19
- * @example
20
- * {
21
- * myBoolean: {
22
- * type: 'boolean'
23
- * default: false,
24
- * },
25
- * myFloat: {
26
- * type: 'float'
27
- * default: 0.1,
28
- * min: -1,
29
- * max: 1,
30
- * event: true,
31
- * }
32
- * }
33
- */
34
- /**
35
- * @typedef {Object} server.SharedStateManagerServer~schemaBooleanDef
36
- *
37
- * @property {String} type='boolean' - Define a boolean parameter.
38
- * @property {Boolean} default - Default value of the parameter.
39
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
40
- * set to `true` the parameter `default` is set to `null`.
41
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
42
- * set its value back to `null` after propagation. When `true`, `nullable` is
43
- * automatically set to `true` and `default` to `null`.
44
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
45
- * the propagation of a parameter even when its value do not change. It
46
- * offers a kind of middle ground between the default bahavior (e.g. where
47
- * only changed values are propagated) and the behavior of the `event` option
48
- * (which has no state per se). As such, setting this options to `false` if
49
- * `event=true` does not make sens.
50
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
51
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
52
- * state that generate the update (i.e. calling `set`), before propagating the
53
- * change on the network. This option can be usefull in cases the network
54
- * would introduce a noticeable latency on the client. If for some reason
55
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
56
- * will be called again on when the "real" / final value will be received.
57
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
58
- */
59
- /**
60
- * @typedef {Object} server.SharedStateManagerServer~schemaStringDef
61
- *
62
- * @property {String} type='string' - Define a boolean parameter.
63
- * @property {Mixed} default - Default value of the parameter.
64
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
65
- * set to `true` the parameter `default` is set to `null`.
66
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
67
- * set its value back to `null` after propagation. When `true`, `nullable` is
68
- * automatically set to `true` and `default` to `null`.
69
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
70
- * the propagation of a parameter even when its value do not change. It
71
- * offers a kind of middle ground between the default bahavior (e.g. where
72
- * only changed values are propagated) and the behavior of the `event` option
73
- * (which has no state per se). As such, setting this options to `false` if
74
- * `event=true` does not make sens.
75
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
76
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
77
- * state that generate the update (i.e. calling `set`), before propagating the
78
- * change on the network. This option can be usefull in cases the network
79
- * would introduce a noticeable latency on the client. If for some reason
80
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
81
- * will be called again on when the "real" / final value will be received.
82
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
83
- */
84
- /**
85
- @typedef {Object} server.SharedStateManagerServer~schemaIntegerDef
86
- *
87
- * @property {String} type='integer' - Define a boolean parameter.
88
- * @property {Mixed} default - Default value of the parameter.
89
- * @property {Number} [min=-Infinity] - Minimum value of the parameter.
90
- * @property {Number} [max=+Infinity] - Maximum value of the parameter.
91
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
92
- * set to `true` the parameter `default` is set to `null`.
93
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
94
- * set its value back to `null` after propagation. When `true`, `nullable` is
95
- * automatically set to `true` and `default` to `null`.
96
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
97
- * the propagation of a parameter even when its value do not change. It
98
- * offers a kind of middle ground between the default bahavior (e.g. where
99
- * only changed values are propagated) and the behavior of the `event` option
100
- * (which has no state per se). As such, setting this options to `false` if
101
- * `event=true` does not make sens.
102
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
103
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
104
- * state that generate the update (i.e. calling `set`), before propagating the
105
- * change on the network. This option can be usefull in cases the network
106
- * would introduce a noticeable latency on the client. If for some reason
107
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
108
- * will be called again on when the "real" / final value will be received.
109
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
110
- */
111
- /**
112
- * @typedef {Object} server.SharedStateManagerServer~schemaFloatDef
113
- *
114
- * @property {String} [type='float'] - Float parameter.
115
- * @property {Mixed} default - Default value.
116
- * @property {Number} [min=-Infinity] - Minimum value.
117
- * @property {Number} [max=+Infinity] - Maximum value.
118
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
119
- * set to `true` the parameter `default` is set to `null`.
120
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
121
- * set its value back to `null` after propagation. When `true`, `nullable` is
122
- * automatically set to `true` and `default` to `null`.
123
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
124
- * the propagation of a parameter even when its value do not change. It
125
- * offers a kind of middle ground between the default bahavior (e.g. where
126
- * only changed values are propagated) and the behavior of the `event` option
127
- * (which has no state per se). As such, setting this options to `false` if
128
- * `event=true` does not make sens.
129
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
130
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
131
- * state that generate the update (i.e. calling `set`), before propagating the
132
- * change on the network. This option can be usefull in cases the network
133
- * would introduce a noticeable latency on the client. If for some reason
134
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
135
- * will be called again on when the "real" / final value will be received.
136
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
137
- */
138
- /**
139
- * @typedef {Object} server.SharedStateManagerServer~schemaEnumDef
140
- *
141
- * @property {String} [type='enum'] - Enum parameter.
142
- * @property {Mixed} default - Default value of the parameter.
143
- * @property {Array} list - Possible values of the parameter.
144
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
145
- * set to `true` the parameter `default` is set to `null`.
146
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
147
- * set its value back to `null` after propagation. When `true`, `nullable` is
148
- * automatically set to `true` and `default` to `null`.
149
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
150
- * the propagation of a parameter even when its value do not change. It
151
- * offers a kind of middle ground between the default bahavior (e.g. where
152
- * only changed values are propagated) and the behavior of the `event` option
153
- * (which has no state per se). As such, setting this options to `false` if
154
- * `event=true` does not make sens.
155
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
156
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
157
- * state that generate the update (i.e. calling `set`), before propagating the
158
- * change on the network. This option can be usefull in cases the network
159
- * would introduce a noticeable latency on the client. If for some reason
160
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
161
- * will be called again on when the "real" / final value will be received.
162
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
163
- */
164
- /**
165
- * @typedef {Object} server.SharedStateManagerServer~schemaAnyDef
166
- *
167
- * Note that the `any` type always return a shallow copy of the state internal
168
- * value. Mutating the returned value will not modify the internal state.
169
- *
170
- * @property {String} [type='any'] - Parameter of any type.
171
- * @property {Mixed} default - Default value of the parameter.
172
- * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
173
- * set to `true` the parameter `default` is set to `null`.
174
- * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
175
- * set its value back to `null` after propagation. When `true`, `nullable` is
176
- * automatically set to `true` and `default` to `null`.
177
- * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
178
- * the propagation of a parameter even when its value do not change. It
179
- * offers a kind of middle ground between the default bahavior (e.g. where
180
- * only changed values are propagated) and the behavior of the `event` option
181
- * (which has no state per se). As such, setting this options to `false` if
182
- * `event=true` does not make sens.
183
- * @property {Boolean} [immediate=false] - Setting this option to `true` will
184
- * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
185
- * state that generate the update (i.e. calling `set`), before propagating the
186
- * change on the network. This option can be usefull in cases the network
187
- * would introduce a noticeable latency on the client. If for some reason
188
- * the value is overriden server-side (e.g. in an `updateHook`) the listeners
189
- * will be called again on when the "real" / final value will be received.
190
- * @property {Object} [metas={}] - Optionnal metadata of the parameter.
191
- */
192
-
193
4
  export const sharedOptions = {
194
5
  nullable: false,
195
6
  event: false, // if event=true, nullable=true
@@ -206,7 +17,7 @@ export const types = {
206
17
  },
207
18
  coerceFunction: (name, def, value) => {
208
19
  if (typeof value !== 'boolean') {
209
- throw new TypeError(`[stateManager] Invalid value for boolean param "${name}": ${value}`);
20
+ throw new TypeError(`[SharedState] Invalid value "${value}" for boolean parameter "${name}"`);
210
21
  }
211
22
 
212
23
  return value;
@@ -219,7 +30,7 @@ export const types = {
219
30
  },
220
31
  coerceFunction: (name, def, value) => {
221
32
  if (typeof value !== 'string') {
222
- throw new TypeError(`[stateManager] Invalid value for string param "${name}": ${value}`);
33
+ throw new TypeError(`[SharedState] Invalid value "${value}" for string parameter "${name}"`);
223
34
  }
224
35
 
225
36
  return value;
@@ -252,7 +63,7 @@ export const types = {
252
63
  },
253
64
  coerceFunction: (name, def, value) => {
254
65
  if (!(typeof value === 'number' && Math.floor(value) === value)) {
255
- throw new TypeError(`[stateManager] Invalid value for integer param "${name}": ${value}`);
66
+ throw new TypeError(`[SharedState] Invalid value "${value}" for integer parameter "${name}"`);
256
67
  }
257
68
 
258
69
  return Math.max(def.min, Math.min(def.max, value));
@@ -285,7 +96,7 @@ export const types = {
285
96
  },
286
97
  coerceFunction: (name, def, value) => {
287
98
  if (typeof value !== 'number' || value !== value) { // reject NaN
288
- throw new TypeError(`[stateManager] Invalid value for float param "${name}": ${value}`);
99
+ throw new TypeError(`[SharedState] Invalid value "${value}" for float parameter "${name}"`);
289
100
  }
290
101
 
291
102
  return Math.max(def.min, Math.min(def.max, value));
@@ -298,7 +109,7 @@ export const types = {
298
109
  },
299
110
  coerceFunction: (name, def, value) => {
300
111
  if (def.list.indexOf(value) === -1) {
301
- throw new TypeError(`[stateManager] Invalid value for enum param "${name}": ${value}`);
112
+ throw new TypeError(`[SharedState] Invalid value "${value}" for enum parameter "${name}"`);
302
113
  }
303
114
 
304
115
 
@@ -328,11 +139,11 @@ class ParameterBag {
328
139
  const def = schema[name];
329
140
 
330
141
  if (!Object.prototype.hasOwnProperty.call(def, 'type')) {
331
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "type" key is required`);
142
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}": "type" key is required`);
332
143
  }
333
144
 
334
145
  if (!Object.prototype.hasOwnProperty.call(types, def.type)) {
335
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "{ type: '${def.type}' }" does not exists`);
146
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}": "{ type: '${def.type}' }" does not exists`);
336
147
  }
337
148
 
338
149
  const required = types[def.type].required;
@@ -341,7 +152,7 @@ class ParameterBag {
341
152
  if (def.event === true && key === 'default') {
342
153
  // do nothing, default is always null for `event` params
343
154
  } else if (!Object.prototype.hasOwnProperty.call(def, key)) {
344
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}" (type "${def.type}"): "${key}" key is required`);
155
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}" (type "${def.type}"): "${key}" key is required`);
345
156
  }
346
157
  });
347
158
  }
@@ -349,7 +160,7 @@ class ParameterBag {
349
160
 
350
161
  constructor(schema, initValues = {}) {
351
162
  if (!schema) {
352
- throw new Error(`[stateManager] schema is mandatory`);
163
+ throw new Error(`[ParameterBag] schema is mandatory`);
353
164
  }
354
165
 
355
166
  schema = cloneDeep(schema);
@@ -382,7 +193,7 @@ class ParameterBag {
382
193
  // make shure initValues make sens according to the given schema
383
194
  for (let name in initValues) {
384
195
  if (!Object.prototype.hasOwnProperty.call(schema, name)) {
385
- throw new ReferenceError(`[stateManager] init value defined for undefined param "${name}"`);
196
+ throw new ReferenceError(`[StateManager.create] init value defined for undefined param "${name}"`);
386
197
  }
387
198
  }
388
199
 
@@ -422,7 +233,7 @@ class ParameterBag {
422
233
  /**
423
234
  * Define if the parameter exists.
424
235
  *
425
- * @param {String} name - Name of the parameter.
236
+ * @param {string} name - Name of the parameter.
426
237
  * @return {Boolean}
427
238
  */
428
239
  has(name) {
@@ -430,9 +241,10 @@ class ParameterBag {
430
241
  }
431
242
 
432
243
  /**
433
- * Return values of all parameters as a flat object.
244
+ * Return values of all parameters as a flat object. If a parameter is of `any`
245
+ * type, a deep copy is made.
434
246
  *
435
- * @return {Object}
247
+ * @return {object}
436
248
  */
437
249
  getValues() {
438
250
  let values = {};
@@ -444,19 +256,40 @@ class ParameterBag {
444
256
  return values;
445
257
  }
446
258
 
259
+ /**
260
+ * Return values of all parameters as a flat object. Similar to `getValues` but
261
+ * returns a reference to the underlying value in case of `any` type. May be
262
+ * usefull if the underlying value is big (e.g. sensors recordings, etc.) and
263
+ * deep cloning expensive. Be aware that if changes are made on the returned
264
+ * object, the state of your application will become inconsistent.
265
+ *
266
+ * @return {object}
267
+ */
268
+ getValuesUnsafe() {
269
+ let values = {};
270
+
271
+ for (let name in this._values) {
272
+ values[name] = this.getUnsafe(name);
273
+ }
274
+
275
+ return values;
276
+ }
277
+
447
278
  /**
448
279
  * Return the value of the given parameter. If the parameter is of `any` type,
449
280
  * a deep copy is returned.
450
281
  *
451
- * @param {String} name - Name of the parameter.
282
+ * @param {string} name - Name of the parameter.
452
283
  * @return {Mixed} - Value of the parameter.
453
284
  */
454
285
  get(name) {
455
286
  if (!this.has(name)) {
456
- throw new ReferenceError(`[stateManager] Cannot get value of undefined parameter "${name}"`);
287
+ throw new ReferenceError(`[SharedState] Cannot get value of undefined parameter "${name}"`);
457
288
  }
458
289
 
459
290
  if (this._schema[name].type === 'any') {
291
+ // we return a deep copy of the object as we don't want the client code to
292
+ // be able to modify our underlying data.
460
293
  return cloneDeep(this._values[name]);
461
294
  } else {
462
295
  return this._values[name];
@@ -464,35 +297,72 @@ class ParameterBag {
464
297
  }
465
298
 
466
299
  /**
467
- * Set the value of a parameter. If the value of the parameter is updated
468
- * (aka if previous value is different from new value) all registered
469
- * callbacks are registered.
300
+ * Similar to `get` but returns a reference to the underlying value in case of
301
+ * `any` type. May be usefull if the underlying value is big (e.g. sensors
302
+ * recordings, etc.) and deep cloning expensive. Be aware that if changes are
303
+ * made on the returned object, the state of your application will become
304
+ * inconsistent.
305
+ *
306
+ * @param {string} name - Name of the parameter.
307
+ * @return {Mixed} - Value of the parameter.
308
+ */
309
+ getUnsafe(name) {
310
+ if (!this.has(name)) {
311
+ throw new ReferenceError(`[SharedState] Cannot get value of undefined parameter "${name}"`);
312
+ }
313
+
314
+ return this._values[name];
315
+ }
316
+
317
+ /**
318
+ * Check that the value is valid according to the schema and return it coerced
319
+ * to the schema definition
470
320
  *
471
321
  * @param {String} name - Name of the parameter.
472
322
  * @param {Mixed} value - Value of the parameter.
473
- * @param {Boolean} [forcePropagation=false] - if true, propagate value even
474
- * if the value has not changed.
475
- * @return {Array} - [new value, updated flag].
476
323
  */
477
- set(name, value) {
324
+ coerceValue(name, value) {
478
325
  if (!this.has(name)) {
479
- throw new ReferenceError(`[stateManager] Cannot set value of undefined parameter "${name}"`);
326
+ throw new ReferenceError(`[SharedState] Cannot set value of undefined parameter "${name}"`);
480
327
  }
481
328
 
482
329
  const def = this._schema[name];
483
- const { coerceFunction } = types[def.type];
484
330
 
485
331
  if (value === null && def.nullable === false) {
486
- throw new TypeError(`[stateManager] Invalid value for ${def.type} param "${name}": value is null and param is not nullable`);
332
+ throw new TypeError(`[SharedState] Invalid value for ${def.type} param "${name}": value is null and param is not nullable`);
487
333
  } else if (value === null && def.nullable === true) {
488
334
  value = null;
489
335
  } else {
336
+ const { coerceFunction } = types[def.type];
490
337
  value = coerceFunction(name, def, value);
491
338
  }
492
339
 
340
+ return value;
341
+ }
342
+
343
+ /**
344
+ * Set the value of a parameter. If the value of the parameter is updated
345
+ * (aka if previous value is different from new value) all registered
346
+ * callbacks are registered.
347
+ *
348
+ * @param {string} name - Name of the parameter.
349
+ * @param {Mixed} value - Value of the parameter.
350
+ * @return {Array} - [new value, updated flag].
351
+ */
352
+ set(name, value) {
353
+ value = this.coerceValue(name, value);
493
354
  const currentValue = this._values[name];
494
355
  const updated = !equal(currentValue, value);
495
356
 
357
+ // we store a deep copy of the object as we don't want the client to be able
358
+ // to modify our underlying data, which leads to unexpected behavior where the
359
+ // deep equal check to returns true, and therefore the update is not triggered.
360
+ // @see tests/common.state-manager.spec.js
361
+ // 'should copy stored value for "any" type to have a predictable behavior'
362
+ if (this._schema[name].type === 'any') {
363
+ value = cloneDeep(value);
364
+ }
365
+
496
366
  this._values[name] = value;
497
367
 
498
368
  // return tuple so that the state manager can handle the `filterChange` option
@@ -504,7 +374,7 @@ class ParameterBag {
504
374
  * @note - prefer `state.set(state.getInitValues())`
505
375
  * or `state.set(state.getDefaultValues())`
506
376
  *
507
- * @param {String} [name=null] - Name of the parameter to reset.
377
+ * @param {string} [name=null] - Name of the parameter to reset.
508
378
  */
509
379
  // reset(name = null) {
510
380
  // if (name !== null) {
@@ -519,14 +389,14 @@ class ParameterBag {
519
389
  /**
520
390
  * Return the given schema along with the initialization values.
521
391
  *
522
- * @return {Object}
392
+ * @return {object}
523
393
  */
524
394
  getSchema(name = null) {
525
395
  if (name === null) {
526
396
  return this._schema;
527
397
  } else {
528
398
  if (!this.has(name)) {
529
- throw new ReferenceError(`[stateManager] Cannot get schema description of undefined parameter "${name}"`);
399
+ throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
530
400
  }
531
401
 
532
402
  return this._schema[name];
@@ -1,4 +1,4 @@
1
- import { idGenerator } from './utils.js';
1
+ import { idGenerator } from '@ircam/sc-utils';
2
2
 
3
3
  const generateRequestId = idGenerator();
4
4
  const requestPromises = new Map();
@@ -1,7 +1,6 @@
1
+ import { idGenerator } from '@ircam/sc-utils';
1
2
  import { v4 as uuid } from 'uuid';
2
3
 
3
- import { idGenerator } from '../common/utils.js';
4
-
5
4
  const generateId = idGenerator();
6
5
 
7
6
  /**
@@ -44,6 +43,12 @@ class Client {
44
43
  * @type {server.Socket}
45
44
  */
46
45
  this.socket = socket;
46
+
47
+ /**
48
+ * Is set in server._onSocketConnection
49
+ * @private
50
+ */
51
+ this.token = null;
47
52
  }
48
53
  }
49
54