@soundworks/core 3.0.4 → 3.1.0-beta.3

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.
@@ -0,0 +1,561 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.types = exports.sharedOptions = exports.default = void 0;
7
+
8
+ var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
9
+
10
+ var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ // import types from './types';
15
+ // import shallowClone from 'shallow-clone';
16
+
17
+ /**
18
+ * @typedef {Object} server.SharedStateManagerServer~schema
19
+ *
20
+ * Description of a schema to be register by the {@link server.ServerStateManagerServer}
21
+ * A schema consists of a combinaison of key value pairs where the key is the
22
+ * name of the parameter, and the value is an object describing the parameter.
23
+ *
24
+ * Available types are:
25
+ * - {@link server.SharedStateManagerServer~schemaBooleanDef}
26
+ * - {@link server.SharedStateManagerServer~schemaStringDef}
27
+ * - {@link server.SharedStateManagerServer~schemaIntegerDef}
28
+ * - {@link server.SharedStateManagerServer~schemaFloatDef}
29
+ * - {@link server.SharedStateManagerServer~schemaEnumDef}
30
+ * - {@link server.SharedStateManagerServer~schemaAnyDef}
31
+ *
32
+ * @example
33
+ * {
34
+ * myBoolean: {
35
+ * type: 'boolean'
36
+ * default: false,
37
+ * },
38
+ * myFloat: {
39
+ * type: 'float'
40
+ * default: 0.1,
41
+ * min: -1,
42
+ * max: 1,
43
+ * event: true,
44
+ * }
45
+ * }
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} server.SharedStateManagerServer~schemaBooleanDef
50
+ *
51
+ * @property {String} type='boolean' - Define a boolean parameter.
52
+ * @property {Boolean} default - Default value of the parameter.
53
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
54
+ * set to `true` the parameter `default` is set to `null`.
55
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
56
+ * set its value back to `null` after propagation. When `true`, `nullable` is
57
+ * automatically set to `true` and `default` to `null`.
58
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
59
+ * the propagation of a parameter even when its value do not change. It
60
+ * offers a kind of middle ground between the default bahavior (e.g. where
61
+ * only changed values are propagated) and the behavior of the `event` option
62
+ * (which has no state per se). As such, setting this options to `false` if
63
+ * `event=true` does not make sens.
64
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
65
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
66
+ * state that generate the update (i.e. calling `set`), before propagating the
67
+ * change on the network. This option can be usefull in cases the network
68
+ * would introduce a noticeable latency on the client. If for some reason
69
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
70
+ * will be called again on when the "real" / final value will be received.
71
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
72
+ */
73
+
74
+ /**
75
+ * @typedef {Object} server.SharedStateManagerServer~schemaStringDef
76
+ *
77
+ * @property {String} type='string' - Define a boolean parameter.
78
+ * @property {Mixed} default - Default value of the parameter.
79
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
80
+ * set to `true` the parameter `default` is set to `null`.
81
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
82
+ * set its value back to `null` after propagation. When `true`, `nullable` is
83
+ * automatically set to `true` and `default` to `null`.
84
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
85
+ * the propagation of a parameter even when its value do not change. It
86
+ * offers a kind of middle ground between the default bahavior (e.g. where
87
+ * only changed values are propagated) and the behavior of the `event` option
88
+ * (which has no state per se). As such, setting this options to `false` if
89
+ * `event=true` does not make sens.
90
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
91
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
92
+ * state that generate the update (i.e. calling `set`), before propagating the
93
+ * change on the network. This option can be usefull in cases the network
94
+ * would introduce a noticeable latency on the client. If for some reason
95
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
96
+ * will be called again on when the "real" / final value will be received.
97
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
98
+ */
99
+
100
+ /**
101
+ @typedef {Object} server.SharedStateManagerServer~schemaIntegerDef
102
+ *
103
+ * @property {String} type='integer' - Define a boolean parameter.
104
+ * @property {Mixed} default - Default value of the parameter.
105
+ * @property {Number} [min=-Infinity] - Minimum value of the parameter.
106
+ * @property {Number} [max=+Infinity] - Maximum value of the parameter.
107
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
108
+ * set to `true` the parameter `default` is set to `null`.
109
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
110
+ * set its value back to `null` after propagation. When `true`, `nullable` is
111
+ * automatically set to `true` and `default` to `null`.
112
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
113
+ * the propagation of a parameter even when its value do not change. It
114
+ * offers a kind of middle ground between the default bahavior (e.g. where
115
+ * only changed values are propagated) and the behavior of the `event` option
116
+ * (which has no state per se). As such, setting this options to `false` if
117
+ * `event=true` does not make sens.
118
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
119
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
120
+ * state that generate the update (i.e. calling `set`), before propagating the
121
+ * change on the network. This option can be usefull in cases the network
122
+ * would introduce a noticeable latency on the client. If for some reason
123
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
124
+ * will be called again on when the "real" / final value will be received.
125
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
126
+ */
127
+
128
+ /**
129
+ * @typedef {Object} server.SharedStateManagerServer~schemaFloatDef
130
+ *
131
+ * @property {String} [type='float'] - Float parameter.
132
+ * @property {Mixed} default - Default value.
133
+ * @property {Number} [min=-Infinity] - Minimum value.
134
+ * @property {Number} [max=+Infinity] - Maximum value.
135
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
136
+ * set to `true` the parameter `default` is set to `null`.
137
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
138
+ * set its value back to `null` after propagation. When `true`, `nullable` is
139
+ * automatically set to `true` and `default` to `null`.
140
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
141
+ * the propagation of a parameter even when its value do not change. It
142
+ * offers a kind of middle ground between the default bahavior (e.g. where
143
+ * only changed values are propagated) and the behavior of the `event` option
144
+ * (which has no state per se). As such, setting this options to `false` if
145
+ * `event=true` does not make sens.
146
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
147
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
148
+ * state that generate the update (i.e. calling `set`), before propagating the
149
+ * change on the network. This option can be usefull in cases the network
150
+ * would introduce a noticeable latency on the client. If for some reason
151
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
152
+ * will be called again on when the "real" / final value will be received.
153
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
154
+ */
155
+
156
+ /**
157
+ * @typedef {Object} server.SharedStateManagerServer~schemaEnumDef
158
+ *
159
+ * @property {String} [type='enum'] - Enum parameter.
160
+ * @property {Mixed} default - Default value of the parameter.
161
+ * @property {Array} list - Possible values of the parameter.
162
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
163
+ * set to `true` the parameter `default` is set to `null`.
164
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
165
+ * set its value back to `null` after propagation. When `true`, `nullable` is
166
+ * automatically set to `true` and `default` to `null`.
167
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
168
+ * the propagation of a parameter even when its value do not change. It
169
+ * offers a kind of middle ground between the default bahavior (e.g. where
170
+ * only changed values are propagated) and the behavior of the `event` option
171
+ * (which has no state per se). As such, setting this options to `false` if
172
+ * `event=true` does not make sens.
173
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
174
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
175
+ * state that generate the update (i.e. calling `set`), before propagating the
176
+ * change on the network. This option can be usefull in cases the network
177
+ * would introduce a noticeable latency on the client. If for some reason
178
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
179
+ * will be called again on when the "real" / final value will be received.
180
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
181
+ */
182
+
183
+ /**
184
+ * @typedef {Object} server.SharedStateManagerServer~schemaAnyDef
185
+ *
186
+ * Note that the `any` type always return a shallow copy of the state internal
187
+ * value. Mutating the returned value will not modify the internal state.
188
+ *
189
+ * @property {String} [type='any'] - Parameter of any type.
190
+ * @property {Mixed} default - Default value of the parameter.
191
+ * @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
192
+ * set to `true` the parameter `default` is set to `null`.
193
+ * @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g.
194
+ * set its value back to `null` after propagation. When `true`, `nullable` is
195
+ * automatically set to `true` and `default` to `null`.
196
+ * @property {Boolean} [filterChange=true] - Setting this option to `false` forces
197
+ * the propagation of a parameter even when its value do not change. It
198
+ * offers a kind of middle ground between the default bahavior (e.g. where
199
+ * only changed values are propagated) and the behavior of the `event` option
200
+ * (which has no state per se). As such, setting this options to `false` if
201
+ * `event=true` does not make sens.
202
+ * @property {Boolean} [immediate=false] - Setting this option to `true` will
203
+ * trigger any change (e.g. call the subscribe listeners) immediately on the
204
+ * state that generate the update (i.e. calling `set`), before propagating the
205
+ * change on the network. This option can be usefull in cases the network
206
+ * would introduce a noticeable latency on the client. If for some reason
207
+ * the value is overriden server-side (e.g. in an updateHook) the listeners
208
+ * will be called again on when the "real" / final value will be received.
209
+ * @property {Object} [metas={}] - Optionnal metadata of the parameter.
210
+ */
211
+ const sharedOptions = {
212
+ nullable: false,
213
+ event: false,
214
+ // if event=true, nullable=true
215
+ metas: {},
216
+ filterChange: true,
217
+ immediate: false
218
+ };
219
+ exports.sharedOptions = sharedOptions;
220
+ const types = {
221
+ boolean: {
222
+ required: ['default'],
223
+
224
+ get defaultOptions() {
225
+ return Object.assign((0, _lodash.default)(sharedOptions), {});
226
+ },
227
+
228
+ coerceFunction: (name, def, value) => {
229
+ if (typeof value !== 'boolean') {
230
+ throw new TypeError(`[stateManager] Invalid value for boolean param "${name}": ${value}`);
231
+ }
232
+
233
+ return value;
234
+ }
235
+ },
236
+ string: {
237
+ required: ['default'],
238
+
239
+ get defaultOptions() {
240
+ return Object.assign((0, _lodash.default)(sharedOptions), {});
241
+ },
242
+
243
+ coerceFunction: (name, def, value) => {
244
+ if (typeof value !== 'string') {
245
+ throw new TypeError(`[stateManager] Invalid value for string param "${name}": ${value}`);
246
+ }
247
+
248
+ return value;
249
+ }
250
+ },
251
+ integer: {
252
+ required: ['default'],
253
+
254
+ get defaultOptions() {
255
+ return Object.assign((0, _lodash.default)(sharedOptions), {
256
+ min: -Infinity,
257
+ max: +Infinity
258
+ });
259
+ },
260
+
261
+ coerceFunction: (name, def, value) => {
262
+ if (!(typeof value === 'number' && Math.floor(value) === value)) {
263
+ throw new TypeError(`[stateManager] Invalid value for integer param "${name}": ${value}`);
264
+ }
265
+
266
+ return Math.max(def.min, Math.min(def.max, value));
267
+ }
268
+ },
269
+ float: {
270
+ required: ['default'],
271
+
272
+ get defaultOptions() {
273
+ return Object.assign((0, _lodash.default)(sharedOptions), {
274
+ min: -Infinity,
275
+ max: +Infinity
276
+ });
277
+ },
278
+
279
+ coerceFunction: (name, def, value) => {
280
+ if (typeof value !== 'number' || value !== value) {
281
+ // reject NaN
282
+ throw new TypeError(`[stateManager] Invalid value for float param "${name}": ${value}`);
283
+ }
284
+
285
+ return Math.max(def.min, Math.min(def.max, value));
286
+ }
287
+ },
288
+ enum: {
289
+ required: ['default', 'list'],
290
+
291
+ get defaultOptions() {
292
+ return Object.assign((0, _lodash.default)(sharedOptions), {});
293
+ },
294
+
295
+ coerceFunction: (name, def, value) => {
296
+ if (def.list.indexOf(value) === -1) {
297
+ throw new TypeError(`[stateManager] Invalid value for enum param "${name}": ${value}`);
298
+ }
299
+
300
+ return value;
301
+ }
302
+ },
303
+ any: {
304
+ required: ['default'],
305
+
306
+ get defaultOptions() {
307
+ return Object.assign((0, _lodash.default)(sharedOptions), {});
308
+ },
309
+
310
+ coerceFunction: (name, def, value) => {
311
+ // no check as it can have any type...
312
+ return value;
313
+ }
314
+ }
315
+ };
316
+ /**
317
+ * Bag of parameters.
318
+ * @private
319
+ */
320
+
321
+ exports.types = types;
322
+
323
+ class ParameterBag {
324
+ static validateSchema(schema) {
325
+ for (let name in schema) {
326
+ const def = schema[name];
327
+
328
+ if (!def.hasOwnProperty('type')) {
329
+ throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "type" key is required`);
330
+ }
331
+
332
+ if (!types.hasOwnProperty(def.type)) {
333
+ throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "{ type: '${def.type}' }" does not exists`);
334
+ }
335
+
336
+ const required = types[def.type].required;
337
+ required.forEach(function (key) {
338
+ if (def.event === true && key === 'default') {// do nothing, default is always null for `event` params
339
+ } else if (!def.hasOwnProperty(key)) {
340
+ throw new TypeError(`[stateManager] Invalid schema definition - param "${name}" (type "${def.type}"): "${key}" key is required`);
341
+ }
342
+ });
343
+ }
344
+ }
345
+
346
+ constructor(schema, initValues = {}) {
347
+ if (!schema) {
348
+ throw new Error(`[stateManager] schema is mandatory`);
349
+ }
350
+
351
+ schema = (0, _lodash.default)(schema);
352
+ initValues = (0, _lodash.default)(initValues);
353
+ /**
354
+ * List of parameters.
355
+ *
356
+ * @type {Object<String, Param>}
357
+ * @name _params
358
+ * @memberof ParameterBag
359
+ * @instance
360
+ * @private
361
+ */
362
+
363
+ this._values = {};
364
+ /**
365
+ * List of schema with init values.
366
+ *
367
+ * @type {Object<String, paramDefinition>}
368
+ * @name _schema
369
+ * @memberof ParameterBag
370
+ * @instance
371
+ * @private
372
+ */
373
+
374
+ this._schema = {};
375
+ ParameterBag.validateSchema(schema); // make shure initValues make sens according to the given schema
376
+
377
+ for (let name in initValues) {
378
+ if (!schema.hasOwnProperty(name)) {
379
+ throw new ReferenceError(`[stateManager] init value defined for undefined param "${name}"`);
380
+ }
381
+ }
382
+
383
+ for (let [name, def] of Object.entries(schema)) {
384
+ const {
385
+ defaultOptions,
386
+ coerceFunction
387
+ } = types[def.type];
388
+ def = Object.assign({}, defaultOptions, def); // if event property is set to true, the param must
389
+ // be nullable and its default value is `undefined`
390
+
391
+ if (def.event === true) {
392
+ def.nullable = true;
393
+ def.default = null;
394
+ }
395
+
396
+ let initValue;
397
+
398
+ if (initValues.hasOwnProperty(name)) {
399
+ initValue = initValues[name];
400
+ } else {
401
+ initValue = def.default;
402
+ }
403
+
404
+ this._schema[name] = def; // coerce init value and store in definition
405
+
406
+ initValue = this.set(name, initValue)[0];
407
+ this._schema[name].initValue = initValue;
408
+ this._values[name] = initValue;
409
+ }
410
+ }
411
+ /**
412
+ * Define if the parameter exists.
413
+ *
414
+ * @param {String} name - Name of the parameter.
415
+ * @return {Boolean}
416
+ */
417
+
418
+
419
+ has(name) {
420
+ return this._schema.hasOwnProperty(name);
421
+ }
422
+ /**
423
+ * Return values of all parameters as a flat object.
424
+ *
425
+ * @return {Object}
426
+ */
427
+
428
+
429
+ getValues() {
430
+ let values = {};
431
+
432
+ for (let name in this._values) {
433
+ values[name] = this.get(name);
434
+ }
435
+
436
+ return values;
437
+ }
438
+ /**
439
+ * Return the value of the given parameter. If the parameter is of `any` type,
440
+ * a deep copy is returned.
441
+ *
442
+ * @param {String} name - Name of the parameter.
443
+ * @return {Mixed} - Value of the parameter.
444
+ */
445
+
446
+
447
+ get(name) {
448
+ if (!this.has(name)) {
449
+ throw new ReferenceError(`[stateManager] Cannot get value of undefined parameter "${name}"`);
450
+ }
451
+
452
+ if (this._schema[name].type === 'any') {
453
+ return (0, _lodash.default)(this._values[name]);
454
+ } else {
455
+ return this._values[name];
456
+ }
457
+ }
458
+ /**
459
+ * Set the value of a parameter. If the value of the parameter is updated
460
+ * (aka if previous value is different from new value) all registered
461
+ * callbacks are registered.
462
+ *
463
+ * @param {String} name - Name of the parameter.
464
+ * @param {Mixed} value - Value of the parameter.
465
+ * @param {Boolean} [forcePropagation=false] - if true, propagate value even
466
+ * if the value has not changed.
467
+ * @return {Array} - [new value, updated flag].
468
+ */
469
+
470
+
471
+ set(name, value) {
472
+ if (!this.has(name)) {
473
+ throw new ReferenceError(`[stateManager] Cannot set value of undefined parameter "${name}"`);
474
+ }
475
+
476
+ const def = this._schema[name];
477
+ const {
478
+ coerceFunction
479
+ } = types[def.type];
480
+
481
+ if (value === null && def.nullable === false) {
482
+ throw new TypeError(`[stateManager] Invalid value for ${def.type} param "${name}": value is null and param is not nullable`);
483
+ } else if (value === null && def.nullable === true) {
484
+ value = value;
485
+ } else {
486
+ value = coerceFunction(name, def, value);
487
+ }
488
+
489
+ const currentValue = this._values[name];
490
+ const updated = !(0, _fastDeepEqual.default)(currentValue, value);
491
+ this._values[name] = value;
492
+
493
+ if (def.event === true) {
494
+ this._values[name] = null;
495
+ } // return tuple so that the state manager can handle the `filterChange` option
496
+
497
+
498
+ return [value, updated];
499
+ }
500
+ /**
501
+ * Reset a parameter to its initialization values. Reset all parameters if no argument.
502
+ * @note - prefer `state.set(state.getInitValues())`
503
+ * or `state.set(state.getDefaultValues())`
504
+ *
505
+ * @param {String} [name=null] - Name of the parameter to reset.
506
+ */
507
+ // reset(name = null) {
508
+ // if (name !== null) {
509
+ // this._params[name] = this._initValues[name];
510
+ // } else {
511
+ // for (let name in this.params) {
512
+ // this._params[name].reset();
513
+ // }
514
+ // }
515
+ // }
516
+
517
+ /**
518
+ * Return the given schema along with the initialization values.
519
+ *
520
+ * @return {Object}
521
+ */
522
+
523
+
524
+ getSchema(name = null) {
525
+ if (name === null) {
526
+ return this._schema;
527
+ } else {
528
+ if (!this.has(name)) {
529
+ throw new ReferenceError(`[stateManager] Cannot get schema description of undefined parameter "${name}"`);
530
+ }
531
+
532
+ return this._schema[name];
533
+ }
534
+ } // return the default value, if initValue has been given, return init values
535
+
536
+
537
+ getInitValues() {
538
+ const initValues = {};
539
+
540
+ for (let [name, def] of Object.entries(this._schema)) {
541
+ initValues[name] = def.initValue;
542
+ }
543
+
544
+ return initValues;
545
+ } // return the default value, if initValue has been given, return init values
546
+
547
+
548
+ getDefaults() {
549
+ const defaults = {};
550
+
551
+ for (let [name, def] of Object.entries(this._schema)) {
552
+ defaults[name] = def.defaults;
553
+ }
554
+
555
+ return defaults;
556
+ }
557
+
558
+ }
559
+
560
+ var _default = ParameterBag;
561
+ exports.default = _default;