@soundworks/core 3.7.0 → 4.0.0-alpha.0

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 (124) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +48 -28
  5. package/src/client/Client.js +391 -0
  6. package/src/client/Context.js +225 -0
  7. package/src/client/ContextManager.js +111 -0
  8. package/src/client/Plugin.js +66 -0
  9. package/src/client/PluginManager.js +123 -0
  10. package/src/client/SharedState.js +84 -0
  11. package/src/client/Socket.js +389 -0
  12. package/src/client/StateManager.js +67 -0
  13. package/src/client/index.js +77 -0
  14. package/src/common/BasePlugin.js +163 -0
  15. package/src/common/BasePluginManager.js +227 -0
  16. package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +74 -112
  19. package/{common → src/common}/SharedStatePrivate.js +56 -62
  20. package/src/common/constants.js +51 -0
  21. package/src/common/logger.js +154 -0
  22. package/src/common/promise-store.js +25 -0
  23. package/src/common/sockets-utils.js +73 -0
  24. package/src/common/utils.js +22 -0
  25. package/src/server/Client.js +50 -0
  26. package/src/server/Context.js +99 -0
  27. package/src/server/ContextManager.js +266 -0
  28. package/src/server/Plugin.js +77 -0
  29. package/src/server/PluginManager.js +150 -0
  30. package/src/server/Server.js +849 -0
  31. package/src/server/SharedState.js +86 -0
  32. package/{server → src/server}/Socket.js +114 -88
  33. package/{server → src/server}/Sockets.js +73 -60
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
  35. package/src/server/audit-network-latency.worker.js +57 -0
  36. package/src/server/audit-schema.js +42 -0
  37. package/src/server/index.js +45 -0
  38. package/tsconfig.json +21 -0
  39. package/types/client/Client.d.ts +194 -0
  40. package/types/client/Client.d.ts.map +1 -0
  41. package/types/client/Context.d.ts +139 -0
  42. package/types/client/Context.d.ts.map +1 -0
  43. package/types/client/ContextManager.d.ts +45 -0
  44. package/types/client/ContextManager.d.ts.map +1 -0
  45. package/types/client/Plugin.d.ts +53 -0
  46. package/types/client/Plugin.d.ts.map +1 -0
  47. package/types/client/PluginManager.d.ts +96 -0
  48. package/types/client/PluginManager.d.ts.map +1 -0
  49. package/types/client/SharedState.d.ts +85 -0
  50. package/types/client/SharedState.d.ts.map +1 -0
  51. package/types/client/Socket.d.ts +138 -0
  52. package/types/client/Socket.d.ts.map +1 -0
  53. package/types/client/StateManager.d.ts +72 -0
  54. package/types/client/StateManager.d.ts.map +1 -0
  55. package/types/client/index.d.ts +11 -0
  56. package/types/client/index.d.ts.map +1 -0
  57. package/types/common/BasePlugin.d.ts +145 -0
  58. package/types/common/BasePlugin.d.ts.map +1 -0
  59. package/types/common/BasePluginManager.d.ts +88 -0
  60. package/types/common/BasePluginManager.d.ts.map +1 -0
  61. package/types/common/BaseSharedState.d.ts +173 -0
  62. package/types/common/BaseSharedState.d.ts.map +1 -0
  63. package/types/common/BaseStateManager.d.ts +70 -0
  64. package/types/common/BaseStateManager.d.ts.map +1 -0
  65. package/types/common/ParameterBag.d.ts +143 -0
  66. package/types/common/ParameterBag.d.ts.map +1 -0
  67. package/types/common/SharedStatePrivate.d.ts +20 -0
  68. package/types/common/SharedStatePrivate.d.ts.map +1 -0
  69. package/types/common/constants.d.ts +35 -0
  70. package/types/common/constants.d.ts.map +1 -0
  71. package/types/common/logger.d.ts +16 -0
  72. package/types/common/logger.d.ts.map +1 -0
  73. package/types/common/promise-store.d.ts +4 -0
  74. package/types/common/promise-store.d.ts.map +1 -0
  75. package/types/common/sockets-utils.d.ts +9 -0
  76. package/types/common/sockets-utils.d.ts.map +1 -0
  77. package/types/common/utils.d.ts +5 -0
  78. package/types/common/utils.d.ts.map +1 -0
  79. package/types/server/Client.d.ts +40 -0
  80. package/types/server/Client.d.ts.map +1 -0
  81. package/types/server/Context.d.ts +59 -0
  82. package/types/server/Context.d.ts.map +1 -0
  83. package/types/server/ContextManager.d.ts +69 -0
  84. package/types/server/ContextManager.d.ts.map +1 -0
  85. package/types/server/Plugin.d.ts +70 -0
  86. package/types/server/Plugin.d.ts.map +1 -0
  87. package/types/server/PluginManager.d.ts +91 -0
  88. package/types/server/PluginManager.d.ts.map +1 -0
  89. package/types/server/Server.d.ts +223 -0
  90. package/types/server/Server.d.ts.map +1 -0
  91. package/types/server/SharedState.d.ts +86 -0
  92. package/types/server/SharedState.d.ts.map +1 -0
  93. package/types/server/Socket.d.ts +128 -0
  94. package/types/server/Socket.d.ts.map +1 -0
  95. package/types/server/Sockets.d.ts +73 -0
  96. package/types/server/Sockets.d.ts.map +1 -0
  97. package/types/server/StateManager.d.ts +190 -0
  98. package/types/server/StateManager.d.ts.map +1 -0
  99. package/types/server/audit-network-latency.worker.d.ts +2 -0
  100. package/types/server/audit-network-latency.worker.d.ts.map +1 -0
  101. package/types/server/audit-schema.d.ts +27 -0
  102. package/types/server/audit-schema.d.ts.map +1 -0
  103. package/types/server/index.d.ts +7 -0
  104. package/types/server/index.d.ts.map +1 -0
  105. package/client/AbstractExperience.js +0 -54
  106. package/client/AbstractPlugin.js +0 -230
  107. package/client/Client.js +0 -193
  108. package/client/PluginManager.js +0 -279
  109. package/client/Socket.js +0 -346
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedStateManagerClient.js +0 -194
  113. package/common/Signal.js +0 -40
  114. package/common/SignalAll.js +0 -44
  115. package/common/logger.js +0 -162
  116. package/common/shared-state-utils.js +0 -89
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -214
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -273
  122. package/server/Server.js +0 -785
  123. package/server/index.js +0 -49
  124. package/server.js +0 -28
@@ -1,65 +1,66 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
9
-
10
- var _sharedStateUtils = require("./shared-state-utils.js");
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- /**
15
- * Representation of a shared state.
16
- *
17
- * @memberof server
18
- *
19
- * @see {server.SharedStateManagerServer}
20
- */
21
-
22
- /**
23
- * Representation of a shared state.
24
- *
25
- * @memberof client
26
- *
27
- * @see {client.SharedStateManagerClient}
28
- */
29
- class SharedState {
1
+ import ParameterBag from './ParameterBag.js';
2
+ import {
3
+ DELETE_REQUEST,
4
+ DELETE_RESPONSE,
5
+ DELETE_ERROR,
6
+ DELETE_NOTIFICATION,
7
+ DETACH_REQUEST,
8
+ DETACH_RESPONSE,
9
+ DETACH_ERROR,
10
+ UPDATE_REQUEST,
11
+ UPDATE_RESPONSE,
12
+ UPDATE_ABORT,
13
+ UPDATE_NOTIFICATION,
14
+ } from './constants.js';
15
+ import {
16
+ storeRequestPromise,
17
+ resolveRequest,
18
+ rejectRequest,
19
+ } from './promise-store.js';
20
+
21
+ class BaseSharedState {
30
22
  constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
31
23
  this.id = id;
32
24
  this.remoteId = remoteId;
33
25
  this.schemaName = schemaName;
34
- this._isOwner = isOwner; // may be the server or any client
35
26
 
27
+ /** @private */
28
+ this._isOwner = isOwner; // may be the server or any client
29
+ /** @private */
36
30
  this._client = client;
31
+ /** @private */
37
32
  this._manager = manager;
38
33
 
39
34
  try {
40
- this._parameters = new _ParameterBag.default(schema, initValues);
41
- } catch (err) {
35
+ /** @private */
36
+ this._parameters = new ParameterBag(schema, initValues);
37
+ } catch(err) {
42
38
  console.error(err.stack);
39
+
43
40
  throw new Error(`Error creating or attaching state "${schemaName}" w/ values:\n
44
41
  ${JSON.stringify(initValues, null, 2)}`);
45
42
  }
46
43
 
47
- this._subscriptions = new Set();
44
+ /** @private */
45
+ this._onUpdateCallbacks = new Set();
46
+ /** @private */
48
47
  this._onDetachCallbacks = new Set();
49
- this._onDeleteCallbacks = new Set(); // add listener for state updates
48
+ /** @private */
49
+ this._onDeleteCallbacks = new Set();
50
50
 
51
- this._client.transport.addListener(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
51
+ // add listener for state updates
52
+ this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
52
53
  const updated = await this._commit(updates, context, true, true);
53
- (0, _sharedStateUtils.resolveRequest)(reqId, updated);
54
- }); // retrieve values but do not propagate to subscriptions
55
-
54
+ resolveRequest(reqId, updated);
55
+ });
56
56
 
57
- this._client.transport.addListener(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
57
+ // retrieve values but do not propagate to subscriptions
58
+ this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
58
59
  const updated = await this._commit(updates, context, false, true);
59
- (0, _sharedStateUtils.resolveRequest)(reqId, updated);
60
+ resolveRequest(reqId, updated);
60
61
  });
61
62
 
62
- this._client.transport.addListener(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
63
+ this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
63
64
  // https://github.com/collective-soundworks/soundworks/issues/18
64
65
  //
65
66
  // # note: 2002-10-03
@@ -83,113 +84,100 @@ ${JSON.stringify(initValues, null, 2)}`);
83
84
  // which is far more important than the edge case reported in the issue
84
85
  // therefore this wont be fixed for now
85
86
  this._commit(updates, context, true, false);
86
- }); // ---------------------------------------------
87
+ });
88
+
89
+ // ---------------------------------------------
87
90
  // DELETE initiated by creator, or schema deleted
88
91
  // ---------------------------------------------
89
-
90
-
91
- this._client.transport.addListener(`${_sharedStateUtils.DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`, () => {
92
+ this._client.transport.addListener(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`, () => {
92
93
  this._manager._statesById.delete(this.id);
93
-
94
94
  this._clearTransport();
95
95
 
96
96
  this._onDetachCallbacks.forEach(callback => callback());
97
-
98
97
  this._onDeleteCallbacks.forEach(callback => callback());
99
98
 
100
99
  this._clearDetach();
101
100
  });
102
101
 
102
+
103
103
  if (this._isOwner) {
104
104
  // ---------------------------------------------
105
105
  // DELETE (can only delete if creator)
106
106
  // ---------------------------------------------
107
- this._client.transport.addListener(`${_sharedStateUtils.DELETE_RESPONSE}-${this.id}-${this.remoteId}`, reqId => {
107
+ this._client.transport.addListener(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`, (reqId) => {
108
108
  this._manager._statesById.delete(this.id);
109
-
110
109
  this._clearTransport();
111
110
 
112
111
  this._onDetachCallbacks.forEach(callback => callback());
113
-
114
112
  this._onDeleteCallbacks.forEach(callback => callback());
115
113
 
116
114
  this._clearDetach();
117
-
118
- (0, _sharedStateUtils.resolveRequest)(reqId, this);
115
+ resolveRequest(reqId, this);
119
116
  });
120
117
 
121
- this._client.transport.addListener(`${_sharedStateUtils.DELETE_ERROR}-${this.id}`, (reqId, msg) => {
122
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
118
+ this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
119
+ rejectRequest(reqId, msg);
123
120
  });
121
+
124
122
  } else {
125
123
  // ---------------------------------------------
126
124
  // DETACH
127
125
  // ---------------------------------------------
128
- this._client.transport.addListener(`${_sharedStateUtils.DETACH_RESPONSE}-${this.id}-${this.remoteId}`, reqId => {
126
+ this._client.transport.addListener(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`, (reqId) => {
129
127
  this._manager._statesById.delete(this.id);
130
-
131
128
  this._clearTransport();
132
129
 
133
130
  this._onDetachCallbacks.forEach(func => func());
134
131
 
135
132
  this._clearDetach();
133
+ resolveRequest(reqId, this);
134
+ });
136
135
 
137
- (0, _sharedStateUtils.resolveRequest)(reqId, this);
138
- }); // the state does not exists anymore in the server (should not happen)
139
-
140
-
141
- this._client.transport.addListener(`${_sharedStateUtils.DETACH_ERROR}-${this.id}`, (reqId, msg) => {
142
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
136
+ // the state does not exists anymore in the server (should not happen)
137
+ this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
138
+ rejectRequest(reqId, msg);
143
139
  });
140
+
144
141
  }
145
142
  }
146
143
 
144
+ /** @private */
147
145
  _clearDetach() {
148
146
  this._onDetachCallbacks.clear();
147
+ this._onDeleteCallbacks.clear();
149
148
 
150
- this._onDeleteCallbacks.clear(); // Monkey patch detach so it throws if called twice. Doing nothing blocks
149
+ // Monkey patch detach so it throws if called twice. Doing nothing blocks
151
150
  // the process on a second `detach` call as the Promise never resolves
152
-
153
-
154
151
  this.detach = () => {
155
152
  throw new Error(`[stateManager] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
156
153
  };
157
154
  }
158
155
 
156
+ /** @private */
159
157
  _clearTransport() {
160
158
  // remove listeners
161
- this._client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
162
-
163
- this._client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
164
-
165
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
166
-
167
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
168
-
169
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
159
+ this._client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
160
+ this._client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
161
+ this._client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
162
+ this._client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
163
+ this._client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
170
164
  }
171
- /** @private */
172
-
173
165
 
166
+ /** @private */
174
167
  async _commit(updates, context, propagate = true, initiator = false) {
175
168
  const newValues = {};
176
169
  const oldValues = {};
177
170
 
178
171
  for (let name in updates) {
179
- const {
180
- immediate,
181
- event
182
- } = this._parameters.getSchema(name); // @note 20211209 - we had an issue here server-side, because if the value
172
+ const { immediate, event } = this._parameters.getSchema(name);
173
+ // @note 20211209 - we had an issue here server-side, because if the value
183
174
  // is an object or an array, the reference is shared by everybody, therefore
184
175
  // `changed` is always false and the new value is never propagated...
185
176
  // FIXED - `state.get` now returns a deep copy when `type` is `any`
186
-
187
-
188
177
  const oldValue = this._parameters.get(name);
178
+ const [newValue, changed] = this._parameters.set(name, updates[name]);
189
179
 
190
- const [newValue, changed] = this._parameters.set(name, updates[name]); // handle immediate stuff
191
-
192
-
180
+ // handle immediate stuff
193
181
  if (initiator && immediate) {
194
182
  // @note - we don't need to check filterChange here because the value
195
183
  // has been updated in parameters on the `set` side so can rely on `changed`
@@ -203,25 +191,24 @@ ${JSON.stringify(initValues, null, 2)}`);
203
191
 
204
192
  newValues[name] = newValue;
205
193
  oldValues[name] = oldValue;
206
- } // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
207
-
194
+ }
208
195
 
196
+ // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
209
197
  let promises = [];
210
198
 
211
199
  if (propagate && Object.keys(newValues).length > 0) {
212
- this._subscriptions.forEach(listener => {
200
+ this._onUpdateCallbacks.forEach(listener => {
213
201
  promises.push(listener(newValues, oldValues, context));
214
202
  });
215
- } // on a given client, `await state.set(update)` resolves when all
216
- // subscription have resolved themselves
217
-
203
+ }
218
204
 
219
- await Promise.all(promises); // reset events to null after propagation of all listeners
205
+ // on a given client, `await state.set(update)` resolves after all
206
+ // update callbacks have themselves resolved
207
+ await Promise.all(promises);
220
208
 
209
+ // reset events to null after propagation of all listeners
221
210
  for (let name in newValues) {
222
- const {
223
- event
224
- } = this._parameters.getSchema(name);
211
+ const { event } = this._parameters.getSchema(name);
225
212
 
226
213
  if (event) {
227
214
  this._parameters.set(name, null);
@@ -230,15 +217,21 @@ ${JSON.stringify(initValues, null, 2)}`);
230
217
 
231
218
  return newValues;
232
219
  }
220
+
233
221
  /**
234
- * Updates values of the state. Wait for all subscriptions to be resolved
235
- * before resolving itself, i.e.:
222
+ * Update values of the state.
223
+ *
224
+ * The returned `Promise` resolves on the applied updates, when all the `onUpdate`
225
+ * callbacks have resolved themselves, i.e.:
236
226
  *
237
227
  * ```js
228
+ * server.stateManager.registerSchema('test', {
229
+ * myBool: { type: 'boolean', default: false },
230
+ * });
238
231
  * const a = await server.stateManager.create('a');
239
232
  * let asyncCallbackCalled = false;
240
233
  *
241
- * a.subscribe(updates => {
234
+ * a.onUpdate(updates => {
242
235
  * return new Promise(resolve => {
243
236
  * setTimeout(() => {
244
237
  * asyncCallbackCalled = true;
@@ -247,21 +240,20 @@ ${JSON.stringify(initValues, null, 2)}`);
247
240
  * });
248
241
  * });
249
242
  *
250
- * await a.set({ bool: true });
243
+ * const updates = await a.set({ myBool: true });
251
244
  * assert.equal(asyncCallbackCalled, true);
245
+ * assert.deepEqual(updates, { myBool: true });
252
246
  * ```
253
247
  *
254
- * @async
255
- * @param {Object} updates - key / value pairs of updates to apply to the state.
256
- * @param {Mixed} [context=null] - optionnal context that will be propagated
248
+ * @param {object} updates - key / value pairs of updates to apply to the state.
249
+ * @param {mixed} [context=null] - optionnal contextual object that will be propagated
257
250
  * alongside the updates of the state. The context is valid only for the
258
- * current call and will be passed as third argument to any subscribe listeners.
259
- * @return {Promise<Object>} A promise to the (coerced) updates.
260
- *
261
- * @see {common.SharedState~subscribeCallback}
251
+ * current call and will be passed as third argument to all update listeners.
252
+ * @returns {Promise<Object>} A promise to the (coerced) updates.
253
+ * @example
254
+ * const state = await client.state.attach('globals');
255
+ * const updates = await state.set({ myParam: Math.random() });
262
256
  */
263
-
264
-
265
257
  async set(updates, context = null) {
266
258
  // handle immediate option
267
259
  const immediateNewValues = {};
@@ -272,7 +264,9 @@ ${JSON.stringify(initValues, null, 2)}`);
272
264
  // throw early (client-side and not only server-side) if parameter is undefined
273
265
  if (!this._parameters.has(name)) {
274
266
  throw new ReferenceError(`[stateManager] Cannot set value of undefined parameter "${name}"`);
275
- } // @note: general idea...
267
+ }
268
+
269
+ // @note: general idea...
276
270
  // if immediate=true
277
271
  // - call listeners if value changed
278
272
  // - go through normal server path
@@ -283,16 +277,10 @@ ${JSON.stringify(initValues, null, 2)}`);
283
277
  // - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
284
278
  // to prevent execute the listeners twice
285
279
 
286
-
287
- const {
288
- immediate,
289
- filterChange,
290
- event
291
- } = this._parameters.getSchema(name);
280
+ const { immediate, filterChange, event } = this._parameters.getSchema(name);
292
281
 
293
282
  if (immediate) {
294
283
  const oldValue = this._parameters.get(name);
295
-
296
284
  const [newValue, changed] = this._parameters.set(name, updates[name]);
297
285
 
298
286
  if (changed || filterChange === false || event) {
@@ -304,112 +292,147 @@ ${JSON.stringify(initValues, null, 2)}`);
304
292
  }
305
293
 
306
294
  if (propagateNow) {
307
- this._subscriptions.forEach(listener => listener(immediateNewValues, immediateOldValues, context));
308
- } // go through server-side normal behavior
309
-
295
+ this._onUpdateCallbacks.forEach(listener => listener(immediateNewValues, immediateOldValues, context));
296
+ }
310
297
 
298
+ // go through server-side normal behavior
311
299
  return new Promise((resolve, reject) => {
312
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
313
-
314
- this._client.transport.emit(`${_sharedStateUtils.UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
300
+ const reqId = storeRequestPromise(resolve, reject);
301
+ this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
315
302
  });
316
303
  }
304
+
317
305
  /**
318
- * Get a value of the state by its name
306
+ * Get the value of a paramter of the state.
319
307
  *
320
- * @param {String} name - Name of the param. Throws an error if the name is invalid.
321
- * @return {Mixed}
308
+ * @param {string} name - Name of the param.
309
+ * @throws Throws if `name` does not correspond to an existing field
310
+ * of the state.
311
+ * @return {mixed}
312
+ * @example
313
+ * const value = state.get('name');
322
314
  */
323
-
324
-
325
315
  get(name) {
326
316
  return this._parameters.get(name);
327
317
  }
318
+
328
319
  /**
329
- * Get a all the key / value pairs of the state.
320
+ * Get all the key / value pairs of the state.
330
321
  *
331
- * @return {Object}
322
+ * @return {object}
323
+ * @example
324
+ * const values = state.getValues();
332
325
  */
333
-
334
-
335
326
  getValues() {
336
327
  return this._parameters.getValues();
337
328
  }
329
+
338
330
  /**
339
- * Get the schema that describes the state.
331
+ * Get the schema from which the state has been created.
340
332
  *
341
- * @param {String} [name=null] - if given, returns only the definition
342
- * of the given param name. Throws an error if the name is invalid.
343
- * @return {Object}
333
+ * @param {string} [name=null] - If given, returns only the definition corresponding
334
+ * to the given param name.
335
+ * @throws Throws if `name` does not correspond to an existing field
336
+ * of the state.
337
+ * @return {object}
338
+ * @example
339
+ * const schema = state.getSchema();
344
340
  */
345
-
346
-
347
341
  getSchema(name = null) {
348
342
  return this._parameters.getSchema(name);
349
343
  }
344
+
350
345
  /**
351
- * Get the values with which the state has been initialized.
346
+ * Get the values with which the state has been created. May defer from the
347
+ * default values declared in the schema.
352
348
  *
353
- * @return {Object}
349
+ * @return {object}
350
+ * @example
351
+ * const initValues = state.getInitValues();
354
352
  */
355
-
356
-
357
353
  getInitValues() {
358
354
  return this._parameters.getInitValues();
359
355
  }
356
+
360
357
  /**
361
- * Get the default values that has been declared in the schema.
358
+ * Get the default values as declared in the schema.
362
359
  *
363
- * @return {Object}
360
+ * @return {object}
361
+ * @example
362
+ * const defaults = state.getDefaults();
364
363
  */
365
-
366
-
367
364
  getDefaults() {
368
365
  return this._parameters.getDefaults();
369
366
  }
367
+
370
368
  /**
371
- * @callback common.SharedState~subscribeCallback
372
- * @param {Object} newValues - key / value pairs of the updates that have been
373
- * applied to the state.
374
- * @param {Object} oldValues - key / value pairs of the related params before
375
- * the updates has been applied to the state.
376
- * @param {Mixed} [context=null] - Optionnal context data that has been passed
377
- * with the updates in the `set` call.
369
+ * Detach from the state. If the client is the creator of the state, the state
370
+ * is deleted and all attached nodes get notified
378
371
  *
379
372
  * @example
380
- * state.subscribe(async (newValues, oldValues[, context=null]) => {
381
- * for (let [key, value] of Object.entries(newValues)) {
382
- * switch (key) {
383
- * // do something
384
- * }
385
- * }
386
- * });
387
- *
388
- * @see {common.SharedState#set}
389
- * @see {common.SharedState#subscribe}
373
+ * const state = await client.state.attach('globals');
374
+ * // later
375
+ * await state.detach();
390
376
  */
377
+ async detach() {
378
+ this._onUpdateCallbacks.clear();
379
+
380
+ if (this._isOwner) {
381
+ return new Promise((resolve, reject) => {
382
+ const reqId = storeRequestPromise(resolve, reject);
383
+ this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
384
+ });
385
+ } else {
386
+ return new Promise((resolve, reject) => {
387
+ const reqId = storeRequestPromise(resolve, reject);
388
+ this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
389
+ });
390
+ }
391
+ }
391
392
 
392
393
  /**
393
- * Subscribe to state updates
394
+ * Delete the state. Only the creator/owner of the state can use this method.
395
+ * All nodes attached to the state will be deteched, triggering the `onDetach`
396
+ * callback. The creator of the state will also have its `onDelete` callback
397
+ * triggered.
394
398
  *
395
- * @param {common.SharedState~subscribeCallback} callback - callback to execute
396
- * when an update is applied on the state.
397
- * @param {Boolean} [executeListener=false] - execute the given listener with
398
- * current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
399
+ * @throws Throws if the method is called by a node which is not the owner of
400
+ * the state.
401
+ * @example
402
+ * const state = await client.state.create('my-schema-name');
403
+ * // later
404
+ * await state.delete();
405
+ */
406
+ async delete() {
407
+ if (this._isOwner) {
408
+ return this.detach();
409
+ } else {
410
+ throw new Error(`[stateManager] can delete state "${this.schemaName}", only owner of the state (i.e. the node that "create[d]" it) can delete it`);
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Subscribe to state updates.
399
416
  *
417
+ * @param {client.SharedState~onUpdateCallback|client.SharedState~onUpdateCallback} callback
418
+ * Callback to execute when an update is applied on the state.
419
+ * @param {Boolean} [executeListener=false] - Execute the callback immediately
420
+ * with current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
421
+ * @returns {client.SharedState~deleteOnUpdateCallback|server.SharedState~deleteOnUpdateCallback}
400
422
  * @example
401
- * state.subscribe(async (newValues, oldValues) => {
423
+ * const unsubscribe = state.onUpdate(async (newValues, oldValues, context) => {
402
424
  * for (let [key, value] of Object.entries(newValues)) {
403
425
  * switch (key) {
404
426
  * // do something
405
427
  * }
406
428
  * }
407
429
  * });
430
+ *
431
+ * // later
432
+ * unsubscribe();
408
433
  */
409
-
410
-
411
- subscribe(listener, executeListener = false) {
412
- this._subscriptions.add(listener);
434
+ onUpdate(listener, executeListener = false) {
435
+ this._onUpdateCallbacks.add(listener);
413
436
 
414
437
  if (executeListener === true) {
415
438
  const currentValues = this.getValues();
@@ -419,62 +442,10 @@ ${JSON.stringify(initValues, null, 2)}`);
419
442
  }
420
443
 
421
444
  return () => {
422
- this._subscriptions.delete(listener);
445
+ this._onUpdateCallbacks.delete(listener);
423
446
  };
424
447
  }
425
- /**
426
- * Detach from the state. If the client is the creator of the state, the state
427
- * is deleted and all attached nodes get notified
428
- *
429
- * @async
430
- * @see {common.SharedState#onDetach}
431
- * @see {common.SharedState#onDelete}
432
- * @see {client.SharedStateManagerClient#create}
433
- * @see {server.SharedStateManagerClient#create}
434
- * @see {client.SharedStateManagerClient#attach}
435
- * @see {server.SharedStateManagerClient#attach}
436
- */
437
-
438
-
439
- async detach() {
440
- this._subscriptions.clear();
441
-
442
- if (this._isOwner) {
443
- return new Promise((resolve, reject) => {
444
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
445
-
446
- this._client.transport.emit(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
447
- });
448
- } else {
449
- return new Promise((resolve, reject) => {
450
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
451
-
452
- this._client.transport.emit(`${_sharedStateUtils.DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
453
- });
454
- }
455
- }
456
- /**
457
- * Delete the state. Only the creator/owner of the state (i.e. a state created using
458
- * `create`) can use this method. If a non-owner call this method (i.e. a
459
- * state created using `attach`), an error will be thrown.
460
- *
461
- * @async
462
- * @see {common.SharedState#onDetach}
463
- * @see {common.SharedState#onDelete}
464
- * @see {client.SharedStateManagerClient#create}
465
- * @see {server.SharedStateManagerClient#create}
466
- * @see {client.SharedStateManagerClient#attach}
467
- * @see {server.SharedStateManagerClient#attach}
468
- */
469
-
470
448
 
471
- async delete() {
472
- if (this._isOwner) {
473
- return this.detach();
474
- } else {
475
- throw new Error(`[stateManager] can delete state "${this.schemaName}", only owner of the state (i.e. the node that "create[d]" it) can delete it`);
476
- }
477
- }
478
449
  /**
479
450
  * Register a function to execute when detaching from the state
480
451
  *
@@ -482,28 +453,21 @@ ${JSON.stringify(initValues, null, 2)}`);
482
453
  * wether the client as called `detach`, or the state has been deleted by its
483
454
  * creator.
484
455
  */
485
-
486
-
487
456
  onDetach(callback) {
488
457
  this._onDetachCallbacks.add(callback);
489
-
490
458
  return () => this._onDetachCallbacks.delete(callback);
491
459
  }
460
+
492
461
  /**
493
462
  * Register a function to execute when the state is deleted. Only called if the
494
463
  * node was the creator of the state. Is called after `onDetach`
495
464
  *
496
465
  * @param {Function} callback - callback to execute when the state is deleted.
497
466
  */
498
-
499
-
500
467
  onDelete(callback) {
501
468
  this._onDeleteCallbacks.add(callback);
502
-
503
469
  return () => this._onDeleteCallbacks.delete(callback);
504
470
  }
505
-
506
471
  }
507
472
 
508
- var _default = SharedState;
509
- exports.default = _default;
473
+ export default BaseSharedState;