@soundworks/core 4.0.0-alpha.9 → 5.0.1

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +14 -29
  3. package/package.json +24 -42
  4. package/src/client/Client.js +306 -256
  5. package/src/client/{Context.js → ClientContext.js} +63 -54
  6. package/src/client/ClientContextManager.js +115 -0
  7. package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
  8. package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
  9. package/src/client/ClientSocket.js +270 -0
  10. package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
  11. package/src/client/index.js +36 -39
  12. package/src/common/BasePlugin.js +64 -58
  13. package/src/common/BasePluginManager.js +191 -142
  14. package/src/common/BaseStateManager.js +635 -145
  15. package/src/common/BatchedTransport.js +73 -0
  16. package/src/common/ParameterBag.js +90 -93
  17. package/src/common/PromiseStore.js +54 -0
  18. package/src/common/SharedState.js +821 -0
  19. package/src/common/SharedStateCollection.js +492 -0
  20. package/src/common/constants.js +14 -2
  21. package/src/common/logger.js +64 -68
  22. package/src/common/shared-state-types.js +82 -0
  23. package/src/common/sockets-utils.js +0 -64
  24. package/src/common/version.js +2 -0
  25. package/src/server/Server.js +552 -824
  26. package/src/server/ServerClient.js +74 -0
  27. package/src/server/{Context.js → ServerContext.js} +96 -77
  28. package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
  29. package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
  30. package/src/server/ServerPluginManager.js +177 -0
  31. package/src/server/ServerSocket.js +256 -0
  32. package/src/server/ServerSockets.js +150 -0
  33. package/src/server/ServerStateManager.js +706 -0
  34. package/src/server/SharedStatePrivate.js +277 -0
  35. package/src/server/audit-network-latency.worker.js +19 -4
  36. package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
  37. package/src/server/create-http-server.js +127 -0
  38. package/src/server/crypto.js +8 -7
  39. package/src/server/index.js +60 -17
  40. package/types/client/Client.d.ts +95 -70
  41. package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
  42. package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
  43. package/types/client/ClientPlugin.d.ts +51 -0
  44. package/types/client/ClientPluginManager.d.ts +112 -0
  45. package/types/client/ClientSocket.d.ts +76 -0
  46. package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
  47. package/types/client/index.d.ts +33 -6
  48. package/types/common/BasePlugin.d.ts +64 -53
  49. package/types/common/BasePluginManager.d.ts +84 -39
  50. package/types/common/BaseStateManager.d.ts +227 -49
  51. package/types/common/BatchedTransport.d.ts +17 -0
  52. package/types/common/ParameterBag.d.ts +73 -76
  53. package/types/common/PromiseStore.d.ts +11 -0
  54. package/types/common/SharedState.d.ts +291 -0
  55. package/types/common/SharedStateCollection.d.ts +311 -0
  56. package/types/common/constants.d.ts +10 -3
  57. package/types/common/logger.d.ts +8 -6
  58. package/types/common/shared-state-types.d.ts +95 -0
  59. package/types/common/sockets-utils.d.ts +0 -5
  60. package/types/common/version.d.ts +2 -0
  61. package/types/server/Server.d.ts +215 -136
  62. package/types/server/ServerClient.d.ts +45 -0
  63. package/types/server/ServerContext.d.ts +209 -0
  64. package/types/server/ServerContextManager.d.ts +75 -0
  65. package/types/server/ServerPlugin.d.ts +57 -0
  66. package/types/server/ServerPluginManager.d.ts +115 -0
  67. package/types/server/ServerSocket.d.ts +69 -0
  68. package/types/server/ServerSockets.d.ts +42 -0
  69. package/types/server/ServerStateManager.d.ts +284 -0
  70. package/types/server/SharedStatePrivate.d.ts +23 -0
  71. package/types/server/audit-network-latency.worker.d.ts +2 -2
  72. package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
  73. package/types/server/create-http-server.d.ts +5 -0
  74. package/types/server/crypto.d.ts +4 -0
  75. package/types/server/index.d.ts +51 -3
  76. package/.eslintrc +0 -15
  77. package/.github/workflows/gh-pages.yml +0 -31
  78. package/.github/workflows/unit-tests.yml +0 -18
  79. package/src/client/ContextManager.js +0 -111
  80. package/src/client/SharedState.js +0 -84
  81. package/src/client/SharedStateCollection.js +0 -35
  82. package/src/client/Socket.js +0 -402
  83. package/src/common/BaseSharedState.js +0 -583
  84. package/src/common/BaseSharedStateCollection.js +0 -179
  85. package/src/common/SharedStatePrivate.js +0 -192
  86. package/src/common/promise-store.js +0 -25
  87. package/src/server/Client.js +0 -55
  88. package/src/server/PluginManager.js +0 -170
  89. package/src/server/SharedState.js +0 -85
  90. package/src/server/SharedStateCollection.js +0 -36
  91. package/src/server/Socket.js +0 -396
  92. package/src/server/Sockets.js +0 -224
  93. package/src/server/StateManager.js +0 -620
  94. package/tsconfig.json +0 -21
  95. package/types/client/Client.d.ts.map +0 -1
  96. package/types/client/Context.d.ts.map +0 -1
  97. package/types/client/ContextManager.d.ts.map +0 -1
  98. package/types/client/Plugin.d.ts +0 -53
  99. package/types/client/Plugin.d.ts.map +0 -1
  100. package/types/client/PluginManager.d.ts +0 -96
  101. package/types/client/PluginManager.d.ts.map +0 -1
  102. package/types/client/SharedState.d.ts +0 -85
  103. package/types/client/SharedState.d.ts.map +0 -1
  104. package/types/client/Socket.d.ts +0 -138
  105. package/types/client/Socket.d.ts.map +0 -1
  106. package/types/client/StateManager.d.ts.map +0 -1
  107. package/types/client/index.d.ts.map +0 -1
  108. package/types/common/BasePlugin.d.ts.map +0 -1
  109. package/types/common/BasePluginManager.d.ts.map +0 -1
  110. package/types/common/BaseSharedState.d.ts +0 -173
  111. package/types/common/BaseSharedState.d.ts.map +0 -1
  112. package/types/common/BaseStateManager.d.ts.map +0 -1
  113. package/types/common/ParameterBag.d.ts.map +0 -1
  114. package/types/common/SharedStatePrivate.d.ts +0 -20
  115. package/types/common/SharedStatePrivate.d.ts.map +0 -1
  116. package/types/common/constants.d.ts.map +0 -1
  117. package/types/common/logger.d.ts.map +0 -1
  118. package/types/common/promise-store.d.ts +0 -4
  119. package/types/common/promise-store.d.ts.map +0 -1
  120. package/types/common/sockets-utils.d.ts.map +0 -1
  121. package/types/common/utils.d.ts +0 -5
  122. package/types/common/utils.d.ts.map +0 -1
  123. package/types/server/Client.d.ts +0 -40
  124. package/types/server/Client.d.ts.map +0 -1
  125. package/types/server/Context.d.ts +0 -59
  126. package/types/server/Context.d.ts.map +0 -1
  127. package/types/server/ContextManager.d.ts +0 -69
  128. package/types/server/ContextManager.d.ts.map +0 -1
  129. package/types/server/Plugin.d.ts +0 -70
  130. package/types/server/Plugin.d.ts.map +0 -1
  131. package/types/server/PluginManager.d.ts +0 -91
  132. package/types/server/PluginManager.d.ts.map +0 -1
  133. package/types/server/Server.d.ts.map +0 -1
  134. package/types/server/SharedState.d.ts +0 -86
  135. package/types/server/SharedState.d.ts.map +0 -1
  136. package/types/server/Socket.d.ts +0 -128
  137. package/types/server/Socket.d.ts.map +0 -1
  138. package/types/server/Sockets.d.ts +0 -73
  139. package/types/server/Sockets.d.ts.map +0 -1
  140. package/types/server/StateManager.d.ts +0 -190
  141. package/types/server/StateManager.d.ts.map +0 -1
  142. package/types/server/audit-network-latency.worker.d.ts.map +0 -1
  143. package/types/server/audit-schema.d.ts.map +0 -1
  144. package/types/server/index.d.ts.map +0 -1
@@ -1,583 +0,0 @@
1
- import { isPlainObject } from '@ircam/sc-utils';
2
- import ParameterBag from './ParameterBag.js';
3
- import {
4
- DELETE_REQUEST,
5
- DELETE_RESPONSE,
6
- DELETE_ERROR,
7
- DELETE_NOTIFICATION,
8
- DETACH_REQUEST,
9
- DETACH_RESPONSE,
10
- DETACH_ERROR,
11
- UPDATE_REQUEST,
12
- UPDATE_RESPONSE,
13
- UPDATE_ABORT,
14
- UPDATE_NOTIFICATION,
15
- } from './constants.js';
16
- import {
17
- storeRequestPromise,
18
- resolveRequest,
19
- rejectRequest,
20
- } from './promise-store.js';
21
-
22
- class BaseSharedState {
23
- constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
24
- /** @private */
25
- this._id = id;
26
- /** @private */
27
- this._remoteId = remoteId;
28
- /** @private */
29
- this._schemaName = schemaName;
30
- /** @private */
31
- this._isOwner = isOwner; // may be the server or any client
32
- /** @private */
33
- this._client = client;
34
- /** @private */
35
- this._manager = manager;
36
-
37
- this._detached = false;
38
-
39
- try {
40
- /** @private */
41
- this._parameters = new ParameterBag(schema, initValues);
42
- } catch (err) {
43
- console.error(err.stack);
44
-
45
- throw new Error(`Error creating or attaching state "${schemaName}" w/ values:\n
46
- ${JSON.stringify(initValues, null, 2)}`);
47
- }
48
-
49
- /** @private */
50
- this._onUpdateCallbacks = new Set();
51
- /** @private */
52
- this._onDetachCallbacks = new Set();
53
- /** @private */
54
- this._onDeleteCallbacks = new Set();
55
-
56
- // add listener for state updates
57
- this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
58
- const updated = await this._commit(updates, context, true, true);
59
- resolveRequest(reqId, updated);
60
- });
61
-
62
- // retrieve values but do not propagate to subscriptions
63
- this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
64
- const updated = await this._commit(updates, context, false, true);
65
- resolveRequest(reqId, updated);
66
- });
67
-
68
- this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
69
- // https://github.com/collective-soundworks/soundworks/issues/18
70
- //
71
- // # note: 2002-10-03
72
- //
73
- // `setTimeout(async () => this._commit(updates, context, true, false));`
74
- // appears to be the only way to push the update commit in the next event
75
- // cycle so that `attach` can resolve before the update notification is
76
- // actually dispatched. The alternative:
77
- // `Promise.resolve().then(() => this._commit(updates, context, true, false))``
78
- // does not behave as expected...
79
- //
80
- // However this breaks the reliability of:
81
- // ```
82
- // /* given a0, a1 and a2 being 3 similar attached states */
83
- // await state.set({ int: i });
84
- //
85
- // assert.equal(a0.get('int'), i);
86
- // assert.equal(a1.get('int'), i);
87
- // assert.equal(a2.get('int'), i);
88
- // ```
89
- // which is far more important than the edge case reported in the issue
90
- // therefore this wont be fixed for now
91
- this._commit(updates, context, true, false);
92
- });
93
-
94
- // ---------------------------------------------
95
- // state has been deleted by its creator or the schema has been deleted
96
- // ---------------------------------------------
97
- this._client.transport.addListener(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`, async () => {
98
- this._manager._statesById.delete(this.id);
99
- this._clearTransport();
100
-
101
- for (let callback of this._onDetachCallbacks) {
102
- try {
103
- await callback();
104
- } catch(err) {
105
- console.error(err.message);
106
- }
107
- }
108
-
109
- if (this._isOwner) {
110
- for (let callback of this._onDeleteCallbacks) {
111
- await callback();
112
- }
113
- }
114
-
115
- this._clearDetach();
116
- });
117
-
118
-
119
- if (this._isOwner) {
120
- // ---------------------------------------------
121
- // the creator has called `.delete()`
122
- // ---------------------------------------------
123
- this._client.transport.addListener(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId) => {
124
- this._manager._statesById.delete(this.id);
125
- this._clearTransport();
126
-
127
- for (let callback of this._onDetachCallbacks) {
128
- await callback();
129
- }
130
-
131
- for (let callback of this._onDeleteCallbacks) {
132
- await callback();
133
- }
134
-
135
- this._clearDetach();
136
- resolveRequest(reqId, this);
137
- });
138
-
139
- this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
140
- rejectRequest(reqId, msg);
141
- });
142
-
143
- } else {
144
- // ---------------------------------------------
145
- // the attached node has called `.detach()`
146
- // ---------------------------------------------
147
- this._client.transport.addListener(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId) => {
148
- this._manager._statesById.delete(this.id);
149
- this._clearTransport();
150
-
151
- for (let callback of this._onDetachCallbacks) {
152
- await callback();
153
- }
154
-
155
- this._clearDetach();
156
- resolveRequest(reqId, this);
157
- });
158
-
159
- // the state does not exists anymore in the server (should not happen)
160
- this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
161
- rejectRequest(reqId, msg);
162
- });
163
- }
164
- }
165
-
166
- /**
167
- * Id of the state
168
- * @type {Number}
169
- * @readonly
170
- */
171
- get id() {
172
- return this._id;
173
- }
174
-
175
- /**
176
- * Unique id of the state for the current node
177
- * @readonly
178
- * @type {Number}
179
- * @private
180
- */
181
- get remoteId() {
182
- return this._remoteId;
183
- }
184
-
185
- /**
186
- * Name of the schema
187
- * @type {String}
188
- * @readonly
189
- */
190
- get schemaName() {
191
- return this._schemaName;
192
- }
193
-
194
- /**
195
- * Indicates if the node is the owner of the state
196
- * @type {Boolean}
197
- * @readonly
198
- */
199
- get isOwner() {
200
- return this._isOwner;
201
- }
202
-
203
- /** @private */
204
- _clearDetach() {
205
- this._onDetachCallbacks.clear();
206
- this._onDeleteCallbacks.clear();
207
- this._detached = true;
208
- }
209
-
210
- /** @private */
211
- _clearTransport() {
212
- // remove listeners
213
- this._client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
214
- this._client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
215
- this._client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
216
- this._client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
217
- this._client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
218
- }
219
-
220
- /** @private */
221
- async _commit(updates, context, propagate = true, initiator = false) {
222
- const newValues = {};
223
- const oldValues = {};
224
-
225
- for (let name in updates) {
226
- const { immediate, event } = this._parameters.getSchema(name);
227
- // @note 20211209 - we had an issue here server-side, because if the value
228
- // is an object or an array, the reference is shared by everybody, therefore
229
- // `changed` is always false and the new value is never propagated...
230
- // FIXED - `state.get` now returns a deep copy when `type` is `any`
231
- const oldValue = this._parameters.get(name);
232
- const [newValue, changed] = this._parameters.set(name, updates[name]);
233
-
234
- // handle immediate stuff
235
- if (initiator && immediate) {
236
- // @note - we don't need to check filterChange here because the value
237
- // has been updated in parameters on the `set` side so can rely on `changed`
238
- // to avoid retrigger listeners.
239
- // If the value has been overriden by the server, `changed` will true
240
- // anyway so it should behave correctly.
241
- if (!changed || event) {
242
- continue;
243
- }
244
- }
245
-
246
- newValues[name] = newValue;
247
- oldValues[name] = oldValue;
248
- }
249
-
250
- // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
251
- let promises = [];
252
-
253
- if (propagate && Object.keys(newValues).length > 0) {
254
- this._onUpdateCallbacks.forEach(listener => {
255
- promises.push(listener(newValues, oldValues, context));
256
- });
257
- }
258
-
259
- // on a given client, `await state.set(update)` resolves after all
260
- // update callbacks have themselves resolved
261
- await Promise.all(promises);
262
-
263
- // reset events to null after propagation of all listeners
264
- for (let name in newValues) {
265
- const { event } = this._parameters.getSchema(name);
266
-
267
- if (event) {
268
- this._parameters.set(name, null);
269
- }
270
- }
271
-
272
- return newValues;
273
- }
274
-
275
- /**
276
- * Update values of the state.
277
- *
278
- * The returned `Promise` resolves on the applied updates, when all the `onUpdate`
279
- * callbacks have resolved themselves, i.e.:
280
- *
281
- * ```js
282
- * server.stateManager.registerSchema('test', {
283
- * myBool: { type: 'boolean', default: false },
284
- * });
285
- * const a = await server.stateManager.create('a');
286
- * let asyncCallbackCalled = false;
287
- *
288
- * a.onUpdate(updates => {
289
- * return new Promise(resolve => {
290
- * setTimeout(() => {
291
- * asyncCallbackCalled = true;
292
- * resolve();
293
- * }, 100);
294
- * });
295
- * });
296
- *
297
- * const updates = await a.set({ myBool: true });
298
- * assert.equal(asyncCallbackCalled, true);
299
- * assert.deepEqual(updates, { myBool: true });
300
- * ```
301
- *
302
- * @param {object} updates - key / value pairs of updates to apply to the state.
303
- * @param {mixed} [context=null] - optionnal contextual object that will be propagated
304
- * alongside the updates of the state. The context is valid only for the
305
- * current call and will be passed as third argument to all update listeners.
306
- * @returns {Promise<Object>} A promise to the (coerced) updates.
307
- * @example
308
- * const state = await client.state.attach('globals');
309
- * const updates = await state.set({ myParam: Math.random() });
310
- */
311
- async set(updates, context = null) {
312
- if (!isPlainObject(updates)) {
313
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as first parameter`);
314
- }
315
-
316
- if (context !== null && !isPlainObject(context)) {
317
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as second parameter`);
318
- }
319
-
320
- // handle immediate option
321
- const immediateNewValues = {};
322
- const immediateOldValues = {};
323
- let propagateNow = false;
324
-
325
- for (let name in updates) {
326
- // try to coerce value early, so that eventual errors are triggered early
327
- // on the node requesting the update
328
- const _ = this._parameters.coerceValue(name, updates[name]);
329
-
330
- // @note: general idea...
331
- // if immediate=true
332
- // - call listeners if value changed
333
- // - go through normal server path
334
- // - retrigger only if response from server is different from current value
335
- // if immediate=true && (filterChange=false || event=true)
336
- // - call listeners with value regarless it changed
337
- // - go through normal server path
338
- // - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
339
- // to prevent execute the listeners twice
340
-
341
- const { immediate, filterChange, event } = this._parameters.getSchema(name);
342
-
343
- if (immediate) {
344
- const oldValue = this._parameters.get(name);
345
- const [newValue, changed] = this._parameters.set(name, updates[name]);
346
-
347
- if (changed || filterChange === false || event) {
348
- immediateOldValues[name] = oldValue;
349
- immediateNewValues[name] = newValue;
350
- propagateNow = true;
351
- }
352
- }
353
- }
354
-
355
- if (propagateNow) {
356
- this._onUpdateCallbacks.forEach(listener => listener(immediateNewValues, immediateOldValues, context));
357
- }
358
-
359
- // go through server-side normal behavior
360
- return new Promise((resolve, reject) => {
361
- const reqId = storeRequestPromise(resolve, reject);
362
- this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
363
- });
364
- }
365
-
366
- /**
367
- * Get the value of a parameter of the state. If the parameter is of `any` type,
368
- * a deep copy is returned.
369
- *
370
- * @param {string} name - Name of the param.
371
- * @throws Throws if `name` does not correspond to an existing field
372
- * of the state.
373
- * @return {mixed}
374
- * @example
375
- * const value = state.get('paramName');
376
- */
377
- get(name) {
378
- return this._parameters.get(name);
379
- }
380
-
381
- /**
382
- * Similar to `get` but returns a reference to the underlying value in case of
383
- * `any` type. May be usefull if the underlying value is big (e.g. sensors
384
- * recordings, etc.) and deep cloning expensive. Be aware that if changes are
385
- * made on the returned object, the state of your application will become
386
- * inconsistent.
387
- *
388
- * @param {string} name - Name of the param.
389
- * @throws Throws if `name` does not correspond to an existing field
390
- * of the state.
391
- * @return {mixed}
392
- * @example
393
- * const value = state.getUnsafe('paramName');
394
- */
395
- getUnsafe(name) {
396
- return this._parameters.getUnsafe(name);
397
- }
398
-
399
- /**
400
- * Get all the key / value pairs of the state. If a parameter is of `any`
401
- * type, a deep copy is made.
402
- *
403
- * @return {object}
404
- * @example
405
- * const values = state.getValues();
406
- */
407
- getValues() {
408
- return this._parameters.getValues();
409
- }
410
-
411
- /**
412
- * Get all the key / value pairs of the state. If a parameter is of `any`
413
- * type, a deep copy is made.
414
- * Similar to `getValues` but returns a reference to the underlying value in
415
- * case of `any` type. May be usefull if the underlying value is big (e.g.
416
- * sensors recordings, etc.) and deep cloning expensive. Be aware that if
417
- * changes are made on the returned object, the state of your application will
418
- * become inconsistent.
419
- *
420
- * @return {object}
421
- * @example
422
- * const values = state.getValues();
423
- */
424
- getValuesUnsafe() {
425
- return this._parameters.getValuesUnsafe();
426
- }
427
-
428
- /**
429
- * Get the schema from which the state has been created.
430
- *
431
- * @param {string} [name=null] - If given, returns only the definition corresponding
432
- * to the given param name.
433
- * @throws Throws if `name` does not correspond to an existing field
434
- * of the state.
435
- * @return {object}
436
- * @example
437
- * const schema = state.getSchema();
438
- */
439
- getSchema(name = null) {
440
- return this._parameters.getSchema(name);
441
- }
442
-
443
- /**
444
- * Get the values with which the state has been created. May defer from the
445
- * default values declared in the schema.
446
- *
447
- * @return {object}
448
- * @example
449
- * const initValues = state.getInitValues();
450
- */
451
- getInitValues() {
452
- return this._parameters.getInitValues();
453
- }
454
-
455
- /**
456
- * Get the default values as declared in the schema.
457
- *
458
- * @return {object}
459
- * @example
460
- * const defaults = state.getDefaults();
461
- */
462
- getDefaults() {
463
- return this._parameters.getDefaults();
464
- }
465
-
466
- /**
467
- * Detach from the state. If the client is the creator of the state, the state
468
- * is deleted and all attached nodes get notified.
469
- *
470
- * @example
471
- * const state = await client.state.attach('globals');
472
- * // later
473
- * await state.detach();
474
- */
475
- async detach() {
476
- if (this._detached) {
477
- throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
478
- }
479
-
480
- this._onUpdateCallbacks.clear();
481
-
482
- if (this._isOwner) {
483
- return new Promise((resolve, reject) => {
484
- const reqId = storeRequestPromise(resolve, reject);
485
- this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
486
- });
487
- } else {
488
- return new Promise((resolve, reject) => {
489
- const reqId = storeRequestPromise(resolve, reject);
490
- this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
491
- });
492
- }
493
- }
494
-
495
- /**
496
- * Delete the state. Only the creator/owner of the state can use this method.
497
- *
498
- * All nodes attached to the state will be detached, triggering any registered
499
- * `onDetach` callbacks. The creator of the state will also have its own `onDelete`
500
- * callback triggered. The local `onDeatch` and `onDelete` callbacks will be
501
- * executed *before* the returned Promise resolves
502
- *
503
- * @throws Throws if the method is called by a node which is not the owner of
504
- * the state.
505
- * @example
506
- * const state = await client.state.create('my-schema-name');
507
- * // later
508
- * await state.delete();
509
- */
510
- async delete() {
511
- if (this._isOwner) {
512
- if (this._detached) {
513
- throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already deleted, cannot delete twice`);
514
- }
515
-
516
- return this.detach();
517
- } else {
518
- throw new Error(`[SharedState] Cannot delete state "${this.schemaName}", only the owner of the state (i.e. the node that created it) can delete the state. Use "detach" instead.`);
519
- }
520
- }
521
-
522
- /**
523
- * Subscribe to state updates.
524
- *
525
- * @param {client.SharedState~onUpdateCallback|server.SharedState~onUpdateCallback} callback
526
- * Callback to execute when an update is applied on the state.
527
- * @param {Boolean} [executeListener=false] - Execute the callback immediately
528
- * with current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
529
- * @returns {client.SharedState~deleteOnUpdateCallback|server.SharedState~deleteOnUpdateCallback}
530
- * @example
531
- * const unsubscribe = state.onUpdate(async (newValues, oldValues, context) => {
532
- * for (let [key, value] of Object.entries(newValues)) {
533
- * switch (key) {
534
- * // do something
535
- * }
536
- * }
537
- * });
538
- *
539
- * // later
540
- * unsubscribe();
541
- */
542
- onUpdate(listener, executeListener = false) {
543
- this._onUpdateCallbacks.add(listener);
544
-
545
- if (executeListener === true) {
546
- const currentValues = this.getValues();
547
- const oldValues = {};
548
- const context = null;
549
- listener(currentValues, oldValues, context);
550
- }
551
-
552
- return () => {
553
- this._onUpdateCallbacks.delete(listener);
554
- };
555
- }
556
-
557
- /**
558
- * Register a function to execute when detaching from the state. The function
559
- * will be executed before the `detach` promise resolves.
560
- *
561
- * @param {Function} callback - Callback to execute when detaching from the state.
562
- * Whether the client as called `detach`, or the state has been deleted by its
563
- * creator.
564
- */
565
- onDetach(callback) {
566
- this._onDetachCallbacks.add(callback);
567
- return () => this._onDetachCallbacks.delete(callback);
568
- }
569
-
570
- /**
571
- * Register a function to execute when the state is deleted. Only called if the
572
- * node was the creator of the state. Is called after `onDetach` and executed
573
- * before the `delete` Promise resolves.
574
- *
575
- * @param {Function} callback - Callback to execute when the state is deleted.
576
- */
577
- onDelete(callback) {
578
- this._onDeleteCallbacks.add(callback);
579
- return () => this._onDeleteCallbacks.delete(callback);
580
- }
581
- }
582
-
583
- export default BaseSharedState;