@soundworks/core 4.0.0-alpha.2 → 4.0.0-alpha.21

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,324 @@
1
+ /** @private */
2
+ class BaseSharedStateCollection {
3
+ constructor(stateManager, schemaName, filter = null, options = {}) {
4
+ this._stateManager = stateManager;
5
+ this._schemaName = schemaName;
6
+ this._filter = filter;
7
+ this._options = Object.assign({ excludeLocal: false }, options);
8
+
9
+ this._schema = null;
10
+ this._states = [];
11
+
12
+ this._onUpdateCallbacks = new Set();
13
+ this._onAttachCallbacks = new Set();
14
+ this._onDetachCallbacks = new Set();
15
+ this._unobserve = null;
16
+ }
17
+
18
+ /** @private */
19
+ async _init() {
20
+ this._schema = await this._stateManager.getSchema(this._schemaName);
21
+
22
+ // if filter is set, check that it contains only valid param names
23
+ if (this._filter !== null) {
24
+ const keys = Object.keys(this._schema);
25
+
26
+ for (let filter of this._filter) {
27
+ if (!keys.includes(filter)) {
28
+ throw new ReferenceError(`[SharedStateCollection] Invalid filter key (${filter}) for schema "${this._schemaName}"`)
29
+ }
30
+ }
31
+ }
32
+
33
+ this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId) => {
34
+ const state = await this._stateManager.attach(schemaName, stateId, this._filter);
35
+ this._states.push(state);
36
+
37
+ state.onDetach(() => {
38
+ const index = this._states.indexOf(state);
39
+ this._states.splice(index, 1);
40
+
41
+ this._onDetachCallbacks.forEach(callback => callback(state));
42
+ });
43
+
44
+ state.onUpdate((newValues, oldValues, context) => {
45
+ Array.from(this._onUpdateCallbacks).forEach(callback => {
46
+ callback(state, newValues, oldValues, context);
47
+ });
48
+ });
49
+
50
+ this._onAttachCallbacks.forEach(callback => callback(state));
51
+ }, this._options);
52
+ }
53
+
54
+ /**
55
+ * Size of the collection, alias `size`
56
+ * @type {number}
57
+ * @readonly
58
+ */
59
+ get length() {
60
+ return this._states.length;
61
+ }
62
+
63
+ /**
64
+ * Size of the collection, , alias `length`
65
+ * @type {number}
66
+ * @readonly
67
+ */
68
+ get size() {
69
+ return this._states.length;
70
+ }
71
+
72
+ /**
73
+ * Name of the schema from which the collection has been created.
74
+ * @type {String}
75
+ * @readonly
76
+ */
77
+ get schemaName() {
78
+ return this._schemaName;
79
+ }
80
+
81
+ /**
82
+ * Definition of schema from which the collection has been created.
83
+ *
84
+ * @param {string} [name=null] - If given, returns only the definition
85
+ * corresponding to the given param name.
86
+ * @throws Throws if `name` does not correspond to an existing field
87
+ * of the schema.
88
+ * @return {object}
89
+ * @example
90
+ * const schema = collection.getSchema();
91
+ */
92
+ getSchema(name = null) {
93
+ if (name) {
94
+ return this._schema[name];
95
+ }
96
+
97
+ return this._schema;
98
+ }
99
+
100
+ /**
101
+ * Get the default values as declared in the schema.
102
+ *
103
+ * @return {object}
104
+ * @example
105
+ * const defaults = state.getDefaults();
106
+ */
107
+ getDefaults() {
108
+ const defaults = {};
109
+ for (let name in this._schema) {
110
+ defaults[name] = this._schema[name].default;
111
+ }
112
+ return defaults;
113
+ }
114
+
115
+ /**
116
+ * Return the current values of all the states in the collection.
117
+ * @return {Object[]}
118
+ */
119
+ getValues() {
120
+ return this._states.map(state => state.getValues());
121
+ }
122
+
123
+ /**
124
+ * Return the current values of all the states in the collection.
125
+ *
126
+ * Similar to `getValues` but returns a reference to the underlying value in
127
+ * case of `any` type. May be usefull if the underlying value is big (e.g.
128
+ * sensors recordings, etc.) and deep cloning expensive. Be aware that if
129
+ * changes are made on the returned object, the state of your application will
130
+ * become inconsistent.
131
+ *
132
+ * @return {Object[]}
133
+ */
134
+ getValuesUnsafe() {
135
+ return this._states.map(state => state.getValues());
136
+ }
137
+
138
+ /**
139
+ * Return the current param value of all the states in the collection.
140
+ *
141
+ * @param {String} name - Name of the parameter
142
+ * @return {any[]}
143
+ */
144
+ get(name) {
145
+ // we can delegate to the state.get(name) method for throwing in case of filtered
146
+ // keys, as the Promise.all will reject on first reject Promise
147
+ return this._states.map(state => state.get(name));
148
+ }
149
+
150
+ /**
151
+ * Similar to `get` but returns a reference to the underlying value in case of
152
+ * `any` type. May be usefull if the underlying value is big (e.g. sensors
153
+ * recordings, etc.) and deep cloning expensive. Be aware that if changes are
154
+ * made on the returned object, the state of your application will become
155
+ * inconsistent.
156
+ *
157
+ * @param {String} name - Name of the parameter
158
+ * @return {any[]}
159
+ */
160
+ getUnsafe(name) {
161
+ // we can delegate to the state.get(name) method for throwing in case of filtered
162
+ // keys, as the Promise.all will reject on first reject Promise
163
+ return this._states.map(state => state.get(name));
164
+ }
165
+
166
+ /**
167
+ * Update all states of the collection with given values.
168
+ * @param {object} updates - key / value pairs of updates to apply to the state.
169
+ * @param {mixed} [context=null] - optionnal contextual object that will be propagated
170
+ * alongside the updates of the state. The context is valid only for the
171
+ * current call and will be passed as third argument to all update listeners.
172
+ */
173
+ async set(updates, context = null) {
174
+ // we can delegate to the state.set(update) method for throwing in case of
175
+ // filtered keys, as the Promise.all will reject on first reject Promise
176
+ const promises = this._states.map(state => state.set(updates, context));
177
+ return Promise.all(promises);
178
+ }
179
+
180
+ /**
181
+ * Subscribe to any state update of the collection.
182
+ *
183
+ * @param {server.SharedStateCollection~onUpdateCallback|client.SharedStateCollection~onUpdateCallback}
184
+ * callback - Callback to execute when an update is applied on a state.
185
+ * @param {Boolean} [executeListener=false] - Execute the callback immediately
186
+ * for all underlying states with current state values. (`oldValues` will be
187
+ * set to `{}`, and `context` to `null`)
188
+ * @returns {Function} - Function that delete the registered listener.
189
+ */
190
+ onUpdate(callback, executeListener = false) {
191
+ this._onUpdateCallbacks.add(callback);
192
+
193
+ if (executeListener === true) {
194
+ this._states.forEach(state => {
195
+ const currentValues = state.getValues();
196
+ const oldValues = {};
197
+ const context = null;
198
+
199
+ callback(state, currentValues, oldValues, context);
200
+ });
201
+ }
202
+
203
+ return () => this._onUpdateCallbacks.delete(callback);
204
+ }
205
+
206
+ /**
207
+ * Register a function to execute when a state is added to the collection.
208
+ *
209
+ * @param {Function} callback - callback to execute when a state is added to
210
+ * the collection.
211
+ * @param {Function} executeListener - execute the callback with the states
212
+ * already present in the collection.
213
+ * @returns {Function} - Function that delete the registered listener.
214
+ */
215
+ onAttach(callback, executeListener = false) {
216
+ if (executeListener === true) {
217
+ this._states.forEach(state => callback(state));
218
+ }
219
+
220
+ this._onAttachCallbacks.add(callback);
221
+
222
+ return () => this._onAttachCallbacks.delete(callback);
223
+ }
224
+
225
+ /**
226
+ * Register a function to execute when a state is removed from the collection.
227
+ *
228
+ * @param {Function} callback - callback to execute when a state is removed
229
+ * from the collection.
230
+ * @returns {Function} - Function that delete the registered listener.
231
+ */
232
+ onDetach(callback) {
233
+ this._onDetachCallbacks.add(callback);
234
+
235
+ return () => this._onDetachCallbacks.delete(callback);
236
+ }
237
+
238
+ /**
239
+ * Detach from the collection, i.e. detach all underlying shared states.
240
+ * @type {number}
241
+ */
242
+ async detach() {
243
+ this._unobserve();
244
+ this._onUpdateCallbacks.clear();
245
+
246
+ const promises = this._states.map(state => state.detach());
247
+ await Promise.all(promises);
248
+
249
+ this._onDetachCallbacks.clear();
250
+ }
251
+
252
+ /**
253
+ * Execute the given function once for each states of the collection (see `Array.forEach`).
254
+ *
255
+ * @param {Function} func - A function to execute for each element in the array.
256
+ * Its return value is discarded.
257
+ */
258
+ forEach(func) {
259
+ this._states.forEach(func);
260
+ }
261
+
262
+ /**
263
+ * Creates a new array populated with the results of calling a provided function
264
+ * on every state of the collection (see `Array.map`).
265
+ *
266
+ * @param {Function} func - A function to execute for each element in the array.
267
+ * Its return value is added as a single element in the new array.
268
+ */
269
+ map(func) {
270
+ return this._states.map(func);
271
+ }
272
+
273
+ /**
274
+ * Creates a shallow copy of a portion of the collection, filtered down to just
275
+ * the estates that pass the test implemented by the provided function (see `Array.filter`).
276
+ *
277
+ * @param {Function} func - A function to execute for each element in the array.
278
+ * It should return a truthy to keep the element in the resulting array, and a f
279
+ * alsy value otherwise.
280
+ */
281
+ filter(func) {
282
+ return this._states.filter(func);
283
+ }
284
+
285
+ /**
286
+ * Sort the elements of the collection in place (see `Array.sort`).
287
+ *
288
+ * @param {Function} func - Function that defines the sort order.
289
+ */
290
+ sort(func) {
291
+ this._states.sort(func);
292
+ }
293
+
294
+ /**
295
+ * Returns the first element of the collection that satisfies the provided testing
296
+ * function. If no values satisfy the testing function, undefined is returned.
297
+ *
298
+ * @param {Function} func - Function to execute for each element in the array.
299
+ * It should return a truthy value to indicate a matching element has been found.
300
+ * @return {}
301
+ */
302
+ find(func) {
303
+ return this._states.find(func);
304
+ }
305
+
306
+ /**
307
+ * Iterable API, e.g. for use in `for .. of` loops
308
+ */
309
+ [Symbol.iterator]() {
310
+ let index = 0;
311
+
312
+ return {
313
+ next: () => {
314
+ if (index >= this._states.length) {
315
+ return { value: undefined, done: true };
316
+ } else {
317
+ return { value: this._states[index++], done: false };
318
+ }
319
+ },
320
+ };
321
+ }
322
+ }
323
+
324
+ export default BaseSharedStateCollection;