@soundworks/core 4.0.0-alpha.13 → 4.0.0-alpha.15

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,6 +1,7 @@
1
- import { isString, isFunction } from '@ircam/sc-utils';
1
+ import { isString, isFunction, isPlainObject } from '@ircam/sc-utils';
2
2
  import SharedState from './BaseSharedState.js';
3
3
  import SharedStateCollection from './BaseSharedStateCollection.js';
4
+ import PromiseStore from './PromiseStore.js';
4
5
  import {
5
6
  CREATE_REQUEST,
6
7
  CREATE_RESPONSE,
@@ -10,15 +11,11 @@ import {
10
11
  ATTACH_ERROR,
11
12
  OBSERVE_REQUEST,
12
13
  OBSERVE_RESPONSE,
14
+ OBSERVE_ERROR,
13
15
  OBSERVE_NOTIFICATION,
14
16
  UNOBSERVE_NOTIFICATION,
15
17
  DELETE_SCHEMA,
16
18
  } from './constants.js';
17
- import {
18
- storeRequestPromise,
19
- resolveRequest,
20
- rejectRequest,
21
- } from './promise-store.js';
22
19
 
23
20
  /**
24
21
  * @private
@@ -32,10 +29,13 @@ class BaseStateManager {
32
29
  constructor(id, transport) {
33
30
  this.client = { id, transport };
34
31
 
35
- this._statesById = new Map();
36
- this._observeListeners = new Map(); // Map <callback, filterSchemaName>
37
- this._cachedSchemas = new Map();
38
- this._observeRequestCallbacks = new Map();
32
+ this._statesById = new Map(); // <id, state>
33
+ this._cachedSchemas = new Map(); // <shemaName, definition>
34
+
35
+ this._observeListeners = new Set(); // Set <[observedSchemaName, callback, options]>
36
+ this._observeRequestCallbacks = new Map(); // Map <reqId, [observedSchemaName, callback, options]>
37
+
38
+ this._promiseStore = new PromiseStore();
39
39
 
40
40
  // ---------------------------------------------
41
41
  // CREATE
@@ -53,11 +53,11 @@ class BaseStateManager {
53
53
  const state = new SharedState(stateId, remoteId, schemaName, schema, this.client, true, this, initValues);
54
54
  this._statesById.set(state.id, state);
55
55
 
56
- resolveRequest(reqId, state);
56
+ this._promiseStore.resolve(reqId, state);
57
57
  });
58
58
 
59
59
  this.client.transport.addListener(CREATE_ERROR, (reqId, msg) => {
60
- rejectRequest(reqId, msg);
60
+ this._promiseStore.reject(reqId, msg);
61
61
  });
62
62
 
63
63
  // ---------------------------------------------
@@ -77,11 +77,11 @@ class BaseStateManager {
77
77
  const state = new SharedState(stateId, remoteId, schemaName, schema, this.client, false, this, currentValues);
78
78
  this._statesById.set(state.id, state);
79
79
 
80
- resolveRequest(reqId, state);
80
+ this._promiseStore.resolve(reqId, state);
81
81
  });
82
82
 
83
83
  this.client.transport.addListener(ATTACH_ERROR, (reqId, msg) => {
84
- rejectRequest(reqId, msg);
84
+ this._promiseStore.reject(reqId, msg);
85
85
  });
86
86
 
87
87
  // ---------------------------------------------
@@ -90,11 +90,18 @@ class BaseStateManager {
90
90
  this.client.transport.addListener(OBSERVE_RESPONSE, async (reqId, ...list) => {
91
91
  // retrieve the callback that have been stored in observe to make sure
92
92
  // we don't call another callback that may have been registered earlier.
93
- const [callback, filterSchemaName] = this._observeRequestCallbacks.get(reqId);
93
+ const observeInfos = this._observeRequestCallbacks.get(reqId);
94
+ const [observedSchemaName, callback, options] = observeInfos;
95
+
96
+ // move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
97
+ // to guarantee order of execution, @see not in `.observe`
94
98
  this._observeRequestCallbacks.delete(reqId);
99
+ this._observeListeners.add(observeInfos);
95
100
 
96
101
  const promises = list.map(([schemaName, stateId, nodeId]) => {
97
- if (filterSchemaName === '*' || filterSchemaName === schemaName) {
102
+ const filter = this._filterObserve(observedSchemaName, schemaName, nodeId, options);
103
+
104
+ if (!filter) {
98
105
  return callback(schemaName, stateId, nodeId);
99
106
  } else {
100
107
  return Promise.resolve();
@@ -104,19 +111,29 @@ class BaseStateManager {
104
111
  await Promise.all(promises);
105
112
 
106
113
  const unsubscribe = () => {
107
- this._observeListeners.delete(callback);
108
- // no more listeners, we can stop receiving notification from the server
114
+ this._observeListeners.delete(observeInfos);
115
+
116
+ // no more listeners, we can stop receiving notifications from the server
109
117
  if (this._observeListeners.size === 0) {
110
118
  this.client.transport.emit(UNOBSERVE_NOTIFICATION);
111
119
  }
112
120
  };
113
121
 
114
- resolveRequest(reqId, unsubscribe);
122
+ this._promiseStore.resolve(reqId, unsubscribe);
123
+ });
124
+
125
+ // Observe error can occur if observed schema name does not exists
126
+ this.client.transport.addListener(OBSERVE_ERROR, (reqId, msg) => {
127
+ this._observeRequestCallbacks.delete(reqId);
128
+ this._promiseStore.reject(reqId, msg);
115
129
  });
116
130
 
117
131
  this.client.transport.addListener(OBSERVE_NOTIFICATION, (schemaName, stateId, nodeId) => {
118
- this._observeListeners.forEach((filterSchemaName, callback) => {
119
- if (filterSchemaName === '*' || filterSchemaName === schemaName) {
132
+ this._observeListeners.forEach(observeInfos => {
133
+ const [observedSchemaName, callback, options] = observeInfos;
134
+ const filter = this._filterObserve(observedSchemaName, schemaName, nodeId, options);
135
+
136
+ if (!filter) {
120
137
  callback(schemaName, stateId, nodeId);
121
138
  }
122
139
  });
@@ -130,6 +147,21 @@ class BaseStateManager {
130
147
  });
131
148
  }
132
149
 
150
+ /** @private */
151
+ _filterObserve(observedSchemaName, schemaName, creatorId, options) {
152
+ let filter = true;
153
+ // schema name filter filer
154
+ if (observedSchemaName === null || observedSchemaName === schemaName) {
155
+ filter = false;
156
+ }
157
+ // filter state created by client if excludeLocal is true
158
+ if (options.excludeLocal === true && creatorId === this.client.id) {
159
+ filter = true;
160
+ }
161
+
162
+ return filter;
163
+ }
164
+
133
165
  /**
134
166
  * Create a `SharedState` instance from a registered schema.
135
167
  *
@@ -144,7 +176,7 @@ class BaseStateManager {
144
176
  */
145
177
  async create(schemaName, initValues = {}) {
146
178
  return new Promise((resolve, reject) => {
147
- const reqId = storeRequestPromise(resolve, reject);
179
+ const reqId = this._promiseStore.add(resolve, reject, 'create-create');
148
180
  const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;
149
181
  this.client.transport.emit(CREATE_REQUEST, reqId, schemaName, requireSchema, initValues);
150
182
  });
@@ -167,7 +199,7 @@ class BaseStateManager {
167
199
  async attach(schemaName, stateId = null) {
168
200
  return new Promise((resolve, reject) => {
169
201
  // @todo - add a timeout
170
- const reqId = storeRequestPromise(resolve, reject);
202
+ const reqId = this._promiseStore.add(resolve, reject, 'attach-request');
171
203
  const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;
172
204
  this.client.transport.emit(ATTACH_REQUEST, reqId, schemaName, stateId, requireSchema);
173
205
  });
@@ -180,20 +212,31 @@ class BaseStateManager {
180
212
  * control over them from a single point.
181
213
  *
182
214
  * Notes:
183
- * - The states that are created by the same node are not propagated through
184
- * the observe callback.
185
- * - The order of execution is not guaranted, i.e. an state attached in the
186
- * `observe` callback could be created before the `async create` method resolves.
215
+ * - The order of execution is not guaranted between nodes, i.e. an state attached
216
+ * in the `observe` callback could be created before the `async create` method resolves.
217
+ * - Filtering, i.e. `observedSchemaName` and `options.excludeLocal` are handled
218
+ * on the node side, the server just notify all state creation activity and
219
+ * the node executes the given callbacks according to the different filter rules.
220
+ * Such strategy allows to share the observe notifications between all observers.
187
221
  *
188
- * @param {String} [schemaName] - optionnal schema name to filter the observed
222
+ * Alternative signatures:
223
+ * - .observe(callback)
224
+ * - .observe(schemaName, callback)
225
+ * - .observe(callback, options)
226
+ * - .observe(schemaName, callback, options)
227
+ *
228
+ * @param {string} [schemaName] - optionnal schema name to filter the observed
189
229
  * states.
190
230
  * @param {server.StateManager~ObserveCallback|client.StateManager~ObserveCallback}
191
231
  * callback - Function to be called when a new state is created on the network.
232
+ * @param {object} options - Options.
233
+ * @param {boolean} [options.excludeLocal = false] - If set to true, exclude states
234
+ * created locallly, i.e. by the same node, from the collection.
192
235
  * @returns {Promise<Function>} - Returns a Promise that resolves when the given
193
236
  * callback as been executed on each existing states. The promise value is a
194
237
  * function which allows to stop observing the states on the network.
195
238
  * @example
196
- * client.stateManager.observe(async (schemaName, stateId, nodeId) => {
239
+ * client.stateManager.observe(async (schemaName, stateId) => {
197
240
  * if (schemaName === 'something') {
198
241
  * const state = await this.client.stateManager.attach(schemaName, stateId);
199
242
  * console.log(state.getValues());
@@ -204,50 +247,127 @@ class BaseStateManager {
204
247
  // handle this way and the network overhead is very low for observe notifications:
205
248
  // i.e. schemaName, stateId, nodeId
206
249
  async observe(...args) {
207
- let filterSchemaName;
250
+
251
+ const defaultOptions = {
252
+ excludeLocal: false,
253
+ };
254
+
255
+ let observedSchemaName;
208
256
  let callback;
257
+ let options;
209
258
 
210
- if (args.length === 1) {
211
- filterSchemaName = '*';
212
- callback = args[0];
259
+ switch (args.length) {
260
+ case 1: {
261
+ // variation: .observe(callback)
262
+ if (!isFunction(args[0])) {
263
+ throw new TypeError(`[stateManager] Invalid arguments, argument 1 should be a function"`);
264
+ }
213
265
 
214
- if (!isFunction(callback)) {
215
- throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
266
+ observedSchemaName = null;
267
+ callback = args[0];
268
+ options = defaultOptions;
269
+ break;
216
270
  }
217
- } else if (args.length === 2) {
218
- filterSchemaName = args[0];
219
- callback = args[1];
271
+ case 2: {
272
+ // variation: .observe(schemaName, callback)
273
+ if (isString(args[0])) {
274
+ if (!isFunction(args[1])) {
275
+ throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be a function"`);
276
+ }
277
+
278
+ observedSchemaName = args[0];
279
+ callback = args[1];
280
+ options = defaultOptions;
281
+
282
+ // variation: .observe(callback, options) API
283
+ } else if (isFunction(args[0])) {
284
+ if (!isPlainObject(args[1])) {
285
+ throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be an object"`);
286
+ }
287
+
288
+ observedSchemaName = null;
289
+ callback = args[0];
290
+ options = Object.assign(defaultOptions, args[1]);
291
+
292
+ } else {
293
+ throw new Error(`[stateManager] Invalid signature, refer to the StateManager.observe documentation"`);
294
+ }
220
295
 
221
- if (!isString(filterSchemaName) || !isFunction(callback)) {
222
- throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
296
+ break;
297
+ }
298
+ case 3: {
299
+ // variation: .observe(schemaName, callback, options)
300
+ if (!isString(args[0])) {
301
+ throw new TypeError(`[stateManager] Invalid arguments, argument 1 should be a string"`);
302
+ }
303
+
304
+ if (!isFunction(args[1])) {
305
+ throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be a function"`);
306
+ }
307
+
308
+ if (!isPlainObject(args[2])) {
309
+ throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be an object"`);
310
+ }
311
+
312
+ observedSchemaName = args[0];
313
+ callback = args[1];
314
+ options = Object.assign(defaultOptions, args[2]);
315
+
316
+ break;
317
+ }
318
+ // throw in all other cases
319
+ default: {
320
+ throw new Error(`[stateManager] Invalid signature, refer to the StateManager.observe documentation"`);
223
321
  }
224
- } else {
225
- throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
226
322
  }
227
323
 
228
324
  // resend request to get updated list of states
229
325
  return new Promise((resolve, reject) => {
230
- const reqId = storeRequestPromise(resolve, reject);
326
+ const reqId = this._promiseStore.add(resolve, reject, 'observe-request');
231
327
  // store the callback for execution on the response. the returned Promise
232
328
  // is fullfiled once callback has been executed with each existing states
233
- this._observeRequestCallbacks.set(reqId, [callback, filterSchemaName]);
234
- // store the callback for execution on subsequent notifications
235
- this._observeListeners.set(callback, filterSchemaName);
236
-
237
- this.client.transport.emit(OBSERVE_REQUEST, reqId);
329
+ const observeInfos = [observedSchemaName, callback, options];
330
+ this._observeRequestCallbacks.set(reqId, observeInfos);
331
+
332
+ // NOTE: do not store in `_observeListeners` yet as it can produce race
333
+ // conditions, e.g.:
334
+ // ```
335
+ // await client.stateManager.observe(async (schemaName, stateId, nodeId) => {});
336
+ // // client now receives OBSERVE_NOTIFICATIONS
337
+ // await otherClient.stateManager.create('a');
338
+ // // second observer added in between
339
+ // client.stateManager.observe(async (schemaName, stateId, nodeId) => {});
340
+ // ````
341
+ // OBSERVE_NOTIFICATION is received before the OBSERVE_RESPONSE, then the
342
+ // second observer is called twice:
343
+ // - OBSERVE_RESPONSE 1 []
344
+ // - OBSERVE_NOTIFICATION [ 'a', 1, 0 ]
345
+ // - OBSERVE_NOTIFICATION [ 'a', 1, 0 ] // this should not happen
346
+ // - OBSERVE_RESPONSE 1 [ [ 'a', 1, 0 ] ]
347
+ //
348
+ // cf. unit test `observe should properly behave in race condition`
349
+
350
+ this.client.transport.emit(OBSERVE_REQUEST, reqId, observedSchemaName);
238
351
  });
239
352
  }
240
353
 
241
354
  /**
242
- * Returns a collection of all the states created from the schema name. Except
243
- * the ones created by the current node.
355
+ * Returns a collection of all the states created from the schema name.
244
356
  *
245
357
  * @param {string} schemaName - Name of the schema.
358
+ * @param {object} options - Options.
359
+ * @param {boolean} [options.excludeLocal = false] - If set to true, exclude states
360
+ * created locallly, i.e. by the same node, from the collection.
246
361
  * @returns {server.SharedStateCollection|client.SharedStateCollection}
247
362
  */
248
- async getCollection(schemaName) {
249
- const collection = new SharedStateCollection(this, schemaName);
250
- await collection._init();
363
+ async getCollection(schemaName, options) {
364
+ const collection = new SharedStateCollection(this, schemaName, options);
365
+
366
+ try {
367
+ await collection._init();
368
+ } catch (err) {
369
+ throw new Error(`Cannot create collection, schema "${schemaName}" does not exists`);
370
+ }
251
371
 
252
372
  return collection;
253
373
  }
@@ -1,4 +1,4 @@
1
- import cloneDeep from 'lodash.clonedeep';
1
+ import cloneDeep from 'lodash/cloneDeep.js';
2
2
  import equal from 'fast-deep-equal';
3
3
 
4
4
  export const sharedOptions = {
@@ -394,30 +394,34 @@ class ParameterBag {
394
394
  getSchema(name = null) {
395
395
  if (name === null) {
396
396
  return this._schema;
397
- } else {
398
- if (!this.has(name)) {
399
- throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
400
- }
397
+ }
401
398
 
402
- return this._schema[name];
399
+ if (!this.has(name)) {
400
+ throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
403
401
  }
402
+
403
+ return this._schema[name];
404
404
  }
405
405
 
406
406
  // return the default value, if initValue has been given, return init values
407
407
  getInitValues() {
408
408
  const initValues = {};
409
+
409
410
  for (let [name, def] of Object.entries(this._schema)) {
410
411
  initValues[name] = def.initValue;
411
412
  }
413
+
412
414
  return initValues;
413
415
  }
414
416
 
415
417
  // return the default value, if initValue has been given, return init values
416
418
  getDefaults() {
417
419
  const defaults = {};
420
+
418
421
  for (let [name, def] of Object.entries(this._schema)) {
419
- defaults[name] = def.defaults;
422
+ defaults[name] = def.default;
420
423
  }
424
+
421
425
  return defaults;
422
426
  }
423
427
  }
@@ -0,0 +1,41 @@
1
+ import { idGenerator } from '@ircam/sc-utils';
2
+
3
+ const generateRequestId = idGenerator();
4
+
5
+ export default class PromiseStore {
6
+ constructor(name) {
7
+ this.name = name;
8
+ this.store = new Map();
9
+ }
10
+
11
+ add(resolve, reject, type) {
12
+ const reqId = generateRequestId.next().value;
13
+ this.store.set(reqId, { resolve, reject, type });
14
+
15
+ return reqId;
16
+ }
17
+
18
+ resolve(reqId, data) {
19
+ const { resolve } = this.store.get(reqId);
20
+ this.store.delete(reqId);
21
+
22
+ resolve(data);
23
+ }
24
+
25
+ reject(reqId, msg) {
26
+ const { reject } = this.store.get(reqId);
27
+ this.store.delete(reqId);
28
+
29
+ reject(new Error(msg));
30
+ }
31
+
32
+ // reject all pendeing request
33
+ flush() {
34
+ for (let [_reqId, entry] of this.store) {
35
+ const { reject, type } = entry;
36
+ reject(new Error(`[${this.name}] Discard promise "${type}", cannot resolve`));
37
+ }
38
+
39
+ this.store.clear();
40
+ }
41
+ }
@@ -89,49 +89,46 @@ class SharedStatePrivate {
89
89
  }
90
90
 
91
91
  if (hasUpdates) {
92
- // send response to requester
93
- // client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
94
-
95
- // @note: we propagate server-side last, because as the server transport
96
- // is synchronous it can break ordering if a subscription function makes
97
- // itself an update in reaction to an update, therefore network messages
98
- // order would be broken,
99
-
100
- // we need to handle cases where:
101
- // client state (client.id: 2) sends a request
102
- // server attached state (client.id: -1) spot a problem and overrides the value
103
- // we want the remote client (id: 2) to receive in the right order:
92
+ // We need to handle cases where:
93
+ // - client state (client.id: 2) sends a request
94
+ // - server attached state (client.id: -1) spot a problem and overrides the value
95
+ // We want the remote client (id: 2) to receive in the right order:
104
96
  // * 1. the value it requested,
105
97
  // * 2. the value overriden by the server-side attached state (id: -1)
98
+ //
99
+ // such problem is now better solved with the the upateHook system, none
100
+ // nonetheway we don't want to introduce inconsistencies here
101
+ //
102
+ // Then we propagate server-side last, because as the server transport
103
+ // is synchronous it can break ordering if a subscription function makes
104
+ // itself an update in reaction to an update. Propagating to server last
105
+ // alllows to maintain network messages order consistent.
106
106
 
107
- // this problem could be solved properly with a reducer system:
108
- // if (dirty) {
109
- // -> call (async) reducer
110
- // -> get values from reducer
111
- //. -> dispatch to everybody
112
- // }
107
+ // @note - remoteId correspond to unique remote state id
108
+
109
+ // propagate RESPONSE to the client that originates the request if not the server
110
+ if (client.id !== -1) {
111
+ client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
112
+ }
113
113
 
114
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
115
- // propagate notification to all other attached clients except server
114
+ // propagate NOTIFICATION to all other attached clients except server
115
+ for (let [peerRemoteId, peer] of this._attachedClients) {
116
116
  if (remoteId !== peerRemoteId && peer.id !== -1) {
117
117
  peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
118
118
  }
119
119
  }
120
120
 
121
- if (client.id !== -1) {
121
+ // propagate RESPONSE to server if it is the requester
122
+ if (client.id === -1) {
122
123
  client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
123
124
  }
124
125
 
125
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
126
- // propagate notification to server
126
+ // propagate NOTIFICATION other attached state that belongs to server
127
+ for (let [peerRemoteId, peer] of this._attachedClients) {
127
128
  if (remoteId !== peerRemoteId && peer.id === -1) {
128
129
  peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
129
130
  }
130
131
  }
131
-
132
- if (client.id === -1) {
133
- client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
134
- }
135
132
  } else {
136
133
  // propagate back to the requester that the update has been aborted
137
134
  // ignore all other attached clients.
@@ -21,6 +21,7 @@ export const DETACH_ERROR = 's:dt:err';
21
21
 
22
22
  export const OBSERVE_REQUEST = 's:o:req';
23
23
  export const OBSERVE_RESPONSE = 's:o:res';
24
+ export const OBSERVE_ERROR = 's:o:err';
24
25
  export const OBSERVE_NOTIFICATION = 's:o:not';
25
26
 
26
27
  export const UNOBSERVE_NOTIFICATION = 's:uo:not';
@@ -47,5 +48,5 @@ export const CLIENT_HANDSHAKE_RESPONSE = 'cl:h:res';
47
48
  export const CLIENT_HANDSHAKE_ERROR = 'cl:h:err';
48
49
 
49
50
  // audit state schema name
50
- export const AUDIT_STATE_NAME = 's:c:audit';
51
+ export const AUDIT_STATE_NAME = 'p:s:audit';
51
52
  export const PRIVATE_STATES = [AUDIT_STATE_NAME];
@@ -1,8 +1,7 @@
1
1
  import 'fast-text-encoding';
2
- import root from 'window-or-global';
3
2
 
4
- const encoder = new root.TextEncoder('utf-8');
5
- const decoder = new root.TextDecoder('utf-8');
3
+ const encoder = new TextEncoder('utf-8');
4
+ const decoder = new TextDecoder('utf-8');
6
5
 
7
6
  const types = [
8
7
  'Int8Array',
@@ -57,7 +56,7 @@ export function unpackBinaryMessage(buffer /* arraybuffer */) {
57
56
  const channel = decoder.decode(channelBuffer);
58
57
  const type = types[typeIndex];
59
58
  // slice (copy) the underlying ArrayBuffer to create a clean TypedArray
60
- const data = new root[type](buffer.slice(startOffset));
59
+ const data = new globalThis[type](buffer.slice(startOffset));
61
60
 
62
61
  return [channel, data];
63
62
  }
@@ -5,14 +5,13 @@ import path from 'node:path';
5
5
  import os from 'node:os';
6
6
  import { X509Certificate, createPrivateKey } from 'node:crypto';
7
7
 
8
- import { getTime } from '@ircam/sc-gettime';
9
- import { isPlainObject, idGenerator } from '@ircam/sc-utils';
8
+ import { isPlainObject, idGenerator, getTime } from '@ircam/sc-utils';
10
9
  import chalk from 'chalk';
11
10
  import compression from 'compression';
12
11
  import express from 'express';
13
12
  import Keyv from 'keyv';
14
13
  import { KeyvFile } from 'keyv-file';
15
- import merge from 'lodash.merge';
14
+ import merge from 'lodash/merge.js';
16
15
  import pem from 'pem';
17
16
  import compile from 'template-literal';
18
17
 
@@ -15,8 +15,11 @@ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';
15
15
 
16
16
  /**
17
17
  * The `SharedStateCollection` interface represent a collection of all states
18
- * created from a given schema name on the network, at the execption of the ones
19
- * created by the current node.
18
+ * created from a given schema name on the network.
19
+ *
20
+ * It can optionnaly exclude the states created by the current node.
21
+ *
22
+ * See {@link server.StateManager#getCollection} for factory method API
20
23
  *
21
24
  * ```
22
25
  * const collection = await server.stateManager.getCollection('my-schema');
@@ -1,4 +1,4 @@
1
- import { getTime } from '@ircam/sc-gettime';
1
+ import { getTime } from '@ircam/sc-utils';
2
2
  import {
3
3
  packBinaryMessage,
4
4
  unpackBinaryMessage,