@soundworks/core 4.0.0-alpha.28 → 4.0.0-alpha.29

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 (127) hide show
  1. package/.jsdoc +3 -1
  2. package/CHANGELOG.md +11 -5
  3. package/package.json +5 -5
  4. package/src/client/Client.js +78 -83
  5. package/src/client/{Context.js → ClientContext.js} +48 -38
  6. package/src/client/ClientContextManager.js +115 -0
  7. package/src/client/{Plugin.js → ClientPlugin.js} +22 -18
  8. package/src/client/{PluginManager.js → ClientPluginManager.js} +39 -35
  9. package/src/client/{Socket.js → ClientSocket.js} +54 -65
  10. package/src/client/{StateManager.js → ClientStateManager.js} +7 -8
  11. package/src/client/index.js +17 -41
  12. package/src/common/BasePlugin.js +56 -51
  13. package/src/common/BasePluginManager.js +175 -139
  14. package/src/common/BaseStateManager.js +316 -120
  15. package/src/common/BatchedTransport.js +24 -24
  16. package/src/common/ParameterBag.js +18 -6
  17. package/src/common/PromiseStore.js +1 -0
  18. package/src/common/{BaseSharedState.js → SharedState.js} +271 -199
  19. package/src/common/{BaseSharedStateCollection.js → SharedStateCollection.js} +92 -61
  20. package/src/common/logger.js +5 -0
  21. package/src/common/shared-state-types.js +76 -0
  22. package/src/common/version.js +1 -1
  23. package/src/server/Server.js +391 -354
  24. package/src/server/ServerClient.js +73 -0
  25. package/src/server/{Context.js → ServerContext.js} +85 -66
  26. package/src/server/{ContextManager.js → ServerContextManager.js} +98 -85
  27. package/src/server/{Plugin.js → ServerPlugin.js} +10 -24
  28. package/src/server/{PluginManager.js → ServerPluginManager.js} +76 -69
  29. package/src/server/{Socket.js → ServerSocket.js} +19 -18
  30. package/src/server/{Sockets.js → ServerSockets.js} +36 -27
  31. package/src/server/ServerStateManager.js +515 -0
  32. package/src/{common → server}/SharedStatePrivate.js +61 -26
  33. package/src/server/audit-schema.js +1 -1
  34. package/src/server/crypto.js +2 -0
  35. package/src/server/index.js +18 -21
  36. package/types/client/Client.d.ts +74 -95
  37. package/types/client/{Context.d.ts → ClientContext.d.ts} +25 -26
  38. package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
  39. package/types/client/{Plugin.d.ts → ClientPlugin.d.ts} +17 -20
  40. package/types/client/{PluginManager.d.ts → ClientPluginManager.d.ts} +13 -36
  41. package/types/client/ClientSocket.d.ts +74 -0
  42. package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +11 -15
  43. package/types/client/index.d.ts +26 -2
  44. package/types/common/BasePlugin.d.ts +52 -46
  45. package/types/common/BasePluginManager.d.ts +68 -44
  46. package/types/common/BaseStateManager.d.ts +207 -97
  47. package/types/common/BatchedTransport.d.ts +1 -5
  48. package/types/common/ParameterBag.d.ts +16 -18
  49. package/types/common/PromiseStore.d.ts +1 -1
  50. package/types/common/SharedState.d.ts +297 -0
  51. package/types/common/{BaseSharedStateCollection.d.ts → SharedStateCollection.d.ts} +34 -18
  52. package/types/common/constants.d.ts +0 -2
  53. package/types/common/logger.d.ts +2 -1
  54. package/types/common/shared-state-types.d.ts +83 -0
  55. package/types/common/sockets-utils.d.ts +0 -5
  56. package/types/common/version.d.ts +1 -2
  57. package/types/server/Server.d.ts +78 -132
  58. package/types/server/ServerClient.d.ts +45 -0
  59. package/types/server/{Context.d.ts → ServerContext.d.ts} +53 -43
  60. package/types/server/ServerContextManager.d.ts +80 -0
  61. package/types/server/{Plugin.d.ts → ServerPlugin.d.ts} +19 -33
  62. package/types/server/{PluginManager.d.ts → ServerPluginManager.d.ts} +39 -37
  63. package/types/server/ServerSocket.d.ts +85 -0
  64. package/types/server/ServerSockets.d.ts +50 -0
  65. package/types/server/ServerStateManager.d.ts +189 -0
  66. package/types/server/SharedStatePrivate.d.ts +21 -0
  67. package/types/server/audit-network-latency.worker.d.ts +1 -2
  68. package/types/server/audit-schema.d.ts +0 -1
  69. package/types/server/crypto.d.ts +2 -1
  70. package/types/server/index.d.ts +24 -2
  71. package/src/client/ContextManager.js +0 -111
  72. package/src/client/SharedState.js +0 -84
  73. package/src/client/SharedStateCollection.js +0 -38
  74. package/src/server/Client.js +0 -55
  75. package/src/server/SharedState.js +0 -85
  76. package/src/server/SharedStateCollection.js +0 -39
  77. package/src/server/StateManager.js +0 -696
  78. package/types/client/Client.d.ts.map +0 -1
  79. package/types/client/Context.d.ts.map +0 -1
  80. package/types/client/ContextManager.d.ts.map +0 -1
  81. package/types/client/Plugin.d.ts.map +0 -1
  82. package/types/client/PluginManager.d.ts.map +0 -1
  83. package/types/client/SharedState.d.ts +0 -85
  84. package/types/client/SharedState.d.ts.map +0 -1
  85. package/types/client/SharedStateCollection.d.ts +0 -41
  86. package/types/client/SharedStateCollection.d.ts.map +0 -1
  87. package/types/client/Socket.d.ts +0 -149
  88. package/types/client/Socket.d.ts.map +0 -1
  89. package/types/client/StateManager.d.ts.map +0 -1
  90. package/types/client/index.d.ts.map +0 -1
  91. package/types/common/BasePlugin.d.ts.map +0 -1
  92. package/types/common/BasePluginManager.d.ts.map +0 -1
  93. package/types/common/BaseSharedState.d.ts +0 -244
  94. package/types/common/BaseSharedState.d.ts.map +0 -1
  95. package/types/common/BaseSharedStateCollection.d.ts.map +0 -1
  96. package/types/common/BaseStateManager.d.ts.map +0 -1
  97. package/types/common/BatchedTransport.d.ts.map +0 -1
  98. package/types/common/ParameterBag.d.ts.map +0 -1
  99. package/types/common/PromiseStore.d.ts.map +0 -1
  100. package/types/common/SharedStatePrivate.d.ts +0 -21
  101. package/types/common/SharedStatePrivate.d.ts.map +0 -1
  102. package/types/common/constants.d.ts.map +0 -1
  103. package/types/common/logger.d.ts.map +0 -1
  104. package/types/common/sockets-utils.d.ts.map +0 -1
  105. package/types/common/version.d.ts.map +0 -1
  106. package/types/server/Client.d.ts +0 -45
  107. package/types/server/Client.d.ts.map +0 -1
  108. package/types/server/Context.d.ts.map +0 -1
  109. package/types/server/ContextManager.d.ts +0 -69
  110. package/types/server/ContextManager.d.ts.map +0 -1
  111. package/types/server/Plugin.d.ts.map +0 -1
  112. package/types/server/PluginManager.d.ts.map +0 -1
  113. package/types/server/Server.d.ts.map +0 -1
  114. package/types/server/SharedState.d.ts +0 -86
  115. package/types/server/SharedState.d.ts.map +0 -1
  116. package/types/server/SharedStateCollection.d.ts +0 -41
  117. package/types/server/SharedStateCollection.d.ts.map +0 -1
  118. package/types/server/Socket.d.ts +0 -164
  119. package/types/server/Socket.d.ts.map +0 -1
  120. package/types/server/Sockets.d.ts +0 -80
  121. package/types/server/Sockets.d.ts.map +0 -1
  122. package/types/server/StateManager.d.ts +0 -405
  123. package/types/server/StateManager.d.ts.map +0 -1
  124. package/types/server/audit-network-latency.worker.d.ts.map +0 -1
  125. package/types/server/audit-schema.d.ts.map +0 -1
  126. package/types/server/crypto.d.ts.map +0 -1
  127. package/types/server/index.d.ts.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { isPlainObject } from '@ircam/sc-utils';
2
+
2
3
  import ParameterBag from './ParameterBag.js';
3
4
  import PromiseStore from './PromiseStore.js';
4
5
  import {
@@ -15,78 +16,147 @@ import {
15
16
  UPDATE_NOTIFICATION,
16
17
  } from './constants.js';
17
18
 
18
- // @todo - replace isOwner by `type`:
19
- // enum {
20
- // 'owner'
21
- // 'attached'
22
- // 'collection-controlller'
23
- // 'collection-attached'
24
- // }
25
-
26
- class BaseSharedState {
27
- constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues, filter) {
28
- /** @private */
29
- this._id = id;
30
- /** @private */
31
- this._remoteId = remoteId;
32
- /** @private */
33
- this._schemaName = schemaName;
34
- /** @private */
35
- this._isOwner = isOwner; // may be the server or any client
36
- /** @private */
37
- this._client = client;
38
- /** @private */
39
- this._manager = manager;
40
- /** @private */
41
- this._filter = filter;
42
-
43
- /**
44
- * true is the state has been detached or deleted
45
- * @private
46
- */
47
- this._detached = false;
48
- /* @private */
49
- this._promiseStore = new PromiseStore(this.constructor.name);
19
+ import {
20
+ kStateManagerDeleteState,
21
+ } from './BaseStateManager.js';
22
+
23
+ import logger from './logger.js';
24
+
25
+ // for testing purposes
26
+ export const kSharedStatePromiseStore = Symbol('soundworks:shared-state-promise-store');
27
+
28
+ /**
29
+ * Callback executed when updates are applied on a {@link SharedState}.
30
+ *
31
+ * @callback sharedStateOnUpdateCallback
32
+ * @param {Object} newValues - Key / value pairs of the updates that have been
33
+ * applied to the state.
34
+ * @param {Object} oldValues - Key / value pairs of the updated params before
35
+ * the updates has been applied to the state.
36
+ * @param {Mixed} [context=null] - Optionnal context object that has been passed
37
+ * with the values updates in the `set` call.
38
+ */
39
+
40
+ /**
41
+ * Delete the registered {@link sharedStateOnUpdateCallback}.
42
+ *
43
+ * @callback sharedStateDeleteOnUpdateCallback
44
+ */
45
+
46
+ /**
47
+ * The `SharedState` is one of the most important and versatile abstraction provided
48
+ * by `soundworks`. It represents a set of parameters that are synchronized accross
49
+ * every nodes of the application (clients and server) that declared some interest
50
+ * to the shared state.
51
+ *
52
+ * A `SharedState` instance is created according to a shared state class definition
53
+ * which is composed of a {@link SharedStateClassName} and of a {@link SharedStateClassSchema}
54
+ * registered in the {@link ServerStateManager}. Any number of `SharedState`s
55
+ * can be created from a single class definition.
56
+ *
57
+ * A shared state can be created both by the clients or by the server, in which case
58
+ * it is generally considered as a global state of the application. Similarly any
59
+ * node of the application (clients or server) can declare interest and "attach" to
60
+ * a state created by another node. All node attached to a state can modify its values
61
+ * and/or react to the modifications applied by other nodes.
62
+ *
63
+ * Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
64
+ *
65
+ * ```
66
+ * // server-side
67
+ * import { Server } from '@soundworks/server/index.js';
68
+ *
69
+ * const server = new Server(config);
70
+ * // define a shared state class
71
+ * server.stateManager.registerSchema('some-global-state', {
72
+ * myRandom: {
73
+ * type: 'float',
74
+ * default: 0,
75
+ * }
76
+ * });
77
+ *
78
+ * await server.start();
79
+ *
80
+ * // create a global state server-side
81
+ * const globalState = await server.stateManager.create('some-global-state');
82
+ * // listen and react to the changes made by the clients
83
+ * globalState.onUpdate(updates => console.log(updates));
84
+ * ```
85
+ *
86
+ * ```
87
+ * // client-side
88
+ * import { Client } from '@soundworks/client.index.js';
89
+ *
90
+ * const client = new Client(config);
91
+ * await client.start();
92
+ *
93
+ * // attach to the global state created by the server
94
+ * const globalState = await client.stateManager.attach('some-global-state');
95
+ *
96
+ * // update the value of a `myRandom` parameter every seconds
97
+ * setInterval(() => {
98
+ * globalState.set({ myRandom: Math.random() });
99
+ * }, 1000);
100
+ * ```
101
+ */
102
+ class SharedState {
103
+ #id = null;
104
+ #remoteId = null;
105
+ #className = null;
106
+ #isOwner = null;
107
+ #client = null;
108
+ #manager = null;
109
+ #filter = null;
110
+ // true is the state has been detached or deleted
111
+ #detached = false;
112
+ #parameters = null;
113
+ #onUpdateCallbacks = new Set();
114
+ #onDetachCallbacks = new Set();
115
+ #onDeleteCallbacks = new Set();
116
+
117
+ constructor(id, remoteId, className, schema, client, isOwner, manager, initValues, filter) {
118
+ this.#id = id;
119
+ this.#remoteId = remoteId;
120
+ this.#className = className;
121
+ this.#isOwner = isOwner; // may be any node
122
+ this.#client = client;
123
+ this.#manager = manager;
124
+ this.#filter = filter;
50
125
 
51
126
  try {
52
- /** @private */
53
- this._parameters = new ParameterBag(schema, initValues);
127
+ this.#parameters = new ParameterBag(schema, initValues);
54
128
  } catch (err) {
55
129
  console.error(err.stack);
56
130
 
57
- throw new Error(`Error creating or attaching state "${schemaName}" w/ values:\n
131
+ throw new Error(`Error creating or attaching state "${className}" w/ values:\n
58
132
  ${JSON.stringify(initValues, null, 2)}`);
59
133
  }
60
134
 
61
135
  /** @private */
62
- this._onUpdateCallbacks = new Set();
63
- /** @private */
64
- this._onDetachCallbacks = new Set();
65
- /** @private */
66
- this._onDeleteCallbacks = new Set();
136
+ this[kSharedStatePromiseStore] = new PromiseStore(this.constructor.name);
67
137
 
68
138
  // add listener for state updates
69
- this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
70
- const updated = await this._commit(updates, context, true, true);
71
- this._promiseStore.resolve(reqId, updated);
139
+ this.#client.transport.addListener(`${UPDATE_RESPONSE}-${this.#id}-${this.#remoteId}`, async (reqId, updates, context) => {
140
+ const updated = await this.#commit(updates, context, true, true);
141
+ this[kSharedStatePromiseStore].resolve(reqId, updated);
72
142
  });
73
143
 
74
144
  // retrieve values but do not propagate to subscriptions
75
- this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
76
- const updated = await this._commit(updates, context, false, true);
77
- this._promiseStore.resolve(reqId, updated);
145
+ this.#client.transport.addListener(`${UPDATE_ABORT}-${this.#id}-${this.#remoteId}`, async (reqId, updates, context) => {
146
+ const updated = await this.#commit(updates, context, false, true);
147
+ this[kSharedStatePromiseStore].resolve(reqId, updated);
78
148
  });
79
149
 
80
- this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
150
+ this.#client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.#id}-${this.#remoteId}`, async (updates, context) => {
81
151
  // https://github.com/collective-soundworks/soundworks/issues/18
82
152
  //
83
153
  // # note: 2002-10-03
84
154
  //
85
- // `setTimeout(async () => this._commit(updates, context, true, false));`
155
+ // `setTimeout(async () => this.#commit(updates, context, true, false));`
86
156
  // appears to be the only way to push the update commit in the next event
87
157
  // cycle so that `attach` can resolve before the update notification is
88
158
  // actually dispatched. The alternative:
89
- // `Promise.resolve().then(() => this._commit(updates, context, true, false))``
159
+ // `Promise.resolve().then(() => this.#commit(updates, context, true, false))``
90
160
  // does not behave as expected...
91
161
  //
92
162
  // However this breaks the reliability of:
@@ -100,17 +170,17 @@ ${JSON.stringify(initValues, null, 2)}`);
100
170
  // ```
101
171
  // which is far more important than the edge case reported in the issue
102
172
  // therefore this wont be fixed for now
103
- this._commit(updates, context, true, false);
173
+ this.#commit(updates, context, true, false);
104
174
  });
105
175
 
106
176
  // ---------------------------------------------
107
177
  // state has been deleted by its creator or the schema has been deleted
108
178
  // ---------------------------------------------
109
- this._client.transport.addListener(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`, async () => {
110
- this._manager._statesById.delete(this.id);
111
- this._clearTransport();
179
+ this.#client.transport.addListener(`${DELETE_NOTIFICATION}-${this.#id}-${this.#remoteId}`, async () => {
180
+ this.#manager[kStateManagerDeleteState](this.#id);
181
+ this.#clearTransport();
112
182
 
113
- for (let callback of this._onDetachCallbacks) {
183
+ for (let callback of this.#onDetachCallbacks) {
114
184
  try {
115
185
  await callback();
116
186
  } catch (err) {
@@ -118,68 +188,68 @@ ${JSON.stringify(initValues, null, 2)}`);
118
188
  }
119
189
  }
120
190
 
121
- if (this._isOwner) {
122
- for (let callback of this._onDeleteCallbacks) {
191
+ if (this.#isOwner) {
192
+ for (let callback of this.#onDeleteCallbacks) {
123
193
  await callback();
124
194
  }
125
195
  }
126
196
 
127
- this._onDetachCallbacks.clear();
128
- this._onDeleteCallbacks.clear();
129
- this._promiseStore.flush();
197
+ this.#onDetachCallbacks.clear();
198
+ this.#onDeleteCallbacks.clear();
199
+ this[kSharedStatePromiseStore].flush();
130
200
  });
131
201
 
132
202
 
133
- if (this._isOwner) {
203
+ if (this.#isOwner) {
134
204
  // ---------------------------------------------
135
205
  // the creator has called `.delete()`
136
206
  // ---------------------------------------------
137
- this._client.transport.addListener(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId) => {
138
- this._manager._statesById.delete(this.id);
139
- this._clearTransport();
207
+ this.#client.transport.addListener(`${DELETE_RESPONSE}-${this.#id}-${this.#remoteId}`, async (reqId) => {
208
+ this.#manager[kStateManagerDeleteState](this.#id);
209
+ this.#clearTransport();
140
210
 
141
- for (let callback of this._onDetachCallbacks) {
211
+ for (let callback of this.#onDetachCallbacks) {
142
212
  await callback();
143
213
  }
144
214
 
145
- for (let callback of this._onDeleteCallbacks) {
215
+ for (let callback of this.#onDeleteCallbacks) {
146
216
  await callback();
147
217
  }
148
218
 
149
- this._onDetachCallbacks.clear();
150
- this._onDeleteCallbacks.clear();
151
- this._promiseStore.resolve(reqId, this);
152
- this._promiseStore.flush();
219
+ this.#onDetachCallbacks.clear();
220
+ this.#onDeleteCallbacks.clear();
221
+ this[kSharedStatePromiseStore].resolve(reqId, this);
222
+ this[kSharedStatePromiseStore].flush();
153
223
  });
154
224
 
155
- this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
156
- this._promiseStore.reject(reqId, msg);
225
+ this.#client.transport.addListener(`${DELETE_ERROR}-${this.#id}`, (reqId, msg) => {
226
+ this[kSharedStatePromiseStore].reject(reqId, msg);
157
227
  });
158
228
 
159
229
  } else {
160
230
  // ---------------------------------------------
161
231
  // the attached node has called `.detach()`
162
232
  // ---------------------------------------------
163
- this._client.transport.addListener(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId) => {
164
- this._manager._statesById.delete(this.id);
165
- this._clearTransport();
233
+ this.#client.transport.addListener(`${DETACH_RESPONSE}-${this.#id}-${this.#remoteId}`, async (reqId) => {
234
+ this.#manager[kStateManagerDeleteState](this.#id);
235
+ this.#clearTransport();
166
236
 
167
- for (let callback of this._onDetachCallbacks) {
237
+ for (let callback of this.#onDetachCallbacks) {
168
238
  await callback();
169
239
  }
170
240
 
171
- this._onDetachCallbacks.clear();
172
- this._onDeleteCallbacks.clear();
173
- this._promiseStore.resolve(reqId, this);
174
- this._promiseStore.flush();
241
+ this.#onDetachCallbacks.clear();
242
+ this.#onDeleteCallbacks.clear();
243
+ this[kSharedStatePromiseStore].resolve(reqId, this);
244
+ this[kSharedStatePromiseStore].flush();
175
245
  });
176
246
 
177
247
  // the state does not exists anymore in the server (should not happen)
178
- this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
179
- this._onDetachCallbacks.clear();
180
- this._onDeleteCallbacks.clear();
181
- this._promiseStore.reject(reqId, msg);
182
- this._promiseStore.flush();
248
+ this.#client.transport.addListener(`${DETACH_ERROR}-${this.#id}`, (reqId, msg) => {
249
+ this.#onDetachCallbacks.clear();
250
+ this.#onDeleteCallbacks.clear();
251
+ this[kSharedStatePromiseStore].reject(reqId, msg);
252
+ this[kSharedStatePromiseStore].flush();
183
253
  });
184
254
  }
185
255
  }
@@ -187,70 +257,65 @@ ${JSON.stringify(initValues, null, 2)}`);
187
257
  /**
188
258
  * Id of the state
189
259
  * @type {Number}
190
- * @readonly
191
260
  */
192
261
  get id() {
193
- return this._id;
262
+ return this.#id;
194
263
  }
195
264
 
196
265
  /**
197
- * Unique id of the state for the current node
198
- * @readonly
199
- * @type {Number}
200
- * @private
266
+ * Name of the underlying {@link SharedState} class.
267
+ * @type {String}
268
+ * @deprecated
201
269
  */
202
- get remoteId() {
203
- return this._remoteId;
270
+ get schemaName() {
271
+ logger.deprecated('SharedState#schemaName', 'SharedState#className');
272
+ return this.#className;
204
273
  }
205
274
 
206
275
  /**
207
- * Name of the schema
276
+ * Name of the underlying {@link SharedState} class.
208
277
  * @type {String}
209
- * @readonly
210
278
  */
211
- get schemaName() {
212
- return this._schemaName;
279
+ get className() {
280
+ return this.#className;
213
281
  }
214
282
 
215
283
  /**
216
- * Indicates if the node is the owner of the state
284
+ * Indicates if the node is the owner of the state, i.e. if it created the state.
217
285
  * @type {Boolean}
218
- * @readonly
219
286
  */
220
287
  get isOwner() {
221
- return this._isOwner;
288
+ return this.#isOwner;
222
289
  }
223
290
 
224
- /** @private */
225
- _clearTransport() {
291
+ #clearTransport() {
226
292
  // remove listeners
227
- this._client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
228
- this._client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
229
- this._client.transport.removeAllListeners(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`);
230
- this._client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
231
-
232
- if (this._isOwner) {
233
- this._client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
234
- this._client.transport.removeAllListeners(`${DELETE_ERROR}-${this.id}-${this.remoteId}`);
293
+ this.#client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.#id}-${this.#remoteId}`);
294
+ this.#client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.#id}-${this.#remoteId}`);
295
+ this.#client.transport.removeAllListeners(`${UPDATE_ABORT}-${this.#id}-${this.#remoteId}`);
296
+ this.#client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.#id}-${this.#remoteId}`);
297
+
298
+ if (this.#isOwner) {
299
+ this.#client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.#id}-${this.#remoteId}`);
300
+ this.#client.transport.removeAllListeners(`${DELETE_ERROR}-${this.#id}-${this.#remoteId}`);
235
301
  } else {
236
- this._client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
237
- this._client.transport.removeAllListeners(`${DETACH_ERROR}-${this.id}-${this.remoteId}`);
302
+ this.#client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.#id}-${this.#remoteId}`);
303
+ this.#client.transport.removeAllListeners(`${DETACH_ERROR}-${this.#id}-${this.#remoteId}`);
238
304
  }
239
305
  }
240
306
 
241
- /** @private */
242
- async _commit(updates, context, propagate = true, initiator = false) {
307
+ async #commit(updates, context, propagate = true, initiator = false) {
243
308
  const newValues = {};
244
309
  const oldValues = {};
245
310
 
246
311
  for (let name in updates) {
247
- const { immediate, event } = this._parameters.getSchema(name);
312
+ const { immediate, event } = this.#parameters.getSchema(name);
248
313
  // @note 20211209 - we had an issue here server-side, because if the value
249
314
  // is an object or an array, the reference is shared by everybody, therefore
250
315
  // `changed` is always false and the new value is never propagated...
251
316
  // FIXED - `state.get` now returns a deep copy when `type` is `any`
252
- const oldValue = this._parameters.get(name);
253
- const [newValue, changed] = this._parameters.set(name, updates[name]);
317
+ const oldValue = this.#parameters.get(name);
318
+ const [newValue, changed] = this.#parameters.set(name, updates[name]);
254
319
 
255
320
  // handle immediate stuff
256
321
  if (initiator && immediate) {
@@ -272,7 +337,7 @@ ${JSON.stringify(initValues, null, 2)}`);
272
337
  let promises = [];
273
338
 
274
339
  if (propagate && Object.keys(newValues).length > 0) {
275
- this._onUpdateCallbacks.forEach(listener => {
340
+ this.#onUpdateCallbacks.forEach(listener => {
276
341
  promises.push(listener(newValues, oldValues, context));
277
342
  });
278
343
  }
@@ -283,10 +348,10 @@ ${JSON.stringify(initValues, null, 2)}`);
283
348
 
284
349
  // reset events to null after propagation of all listeners
285
350
  for (let name in newValues) {
286
- const { event } = this._parameters.getSchema(name);
351
+ const { event } = this.#parameters.getSchema(name);
287
352
 
288
353
  if (event) {
289
- this._parameters.set(name, null);
354
+ this.#parameters.set(name, null);
290
355
  }
291
356
  }
292
357
 
@@ -296,14 +361,15 @@ ${JSON.stringify(initValues, null, 2)}`);
296
361
  /**
297
362
  * Update values of the state.
298
363
  *
299
- * The returned `Promise` resolves on the applied updates, when all the `onUpdate`
300
- * callbacks have resolved themselves, i.e.:
364
+ * The returned `Promise` resolves on an object that contains the applied updates,
365
+ * and resolves after all the `onUpdate` callbacks have resolved themselves, i.e.:
301
366
  *
302
367
  * ```js
303
368
  * server.stateManager.registerSchema('test', {
304
369
  * myBool: { type: 'boolean', default: false },
305
370
  * });
306
371
  * const a = await server.stateManager.create('a');
372
+ *
307
373
  * let asyncCallbackCalled = false;
308
374
  *
309
375
  * a.onUpdate(updates => {
@@ -320,26 +386,27 @@ ${JSON.stringify(initValues, null, 2)}`);
320
386
  * assert.deepEqual(updates, { myBool: true });
321
387
  * ```
322
388
  *
323
- * @param {object} updates - key / value pairs of updates to apply to the state.
324
- * @param {mixed} [context=null] - optionnal contextual object that will be propagated
389
+ * @param {object} updates - Key / value pairs of updates to apply to the state.
390
+ * @param {mixed} [context=null] - Optionnal contextual object that will be propagated
325
391
  * alongside the updates of the state. The context is valid only for the
326
392
  * current call and will be passed as third argument to all update listeners.
327
393
  * @returns {Promise<Object>} A promise to the (coerced) updates.
394
+ *
328
395
  * @example
329
- * const state = await client.state.attach('globals');
396
+ * const state = await client.stateManager.attach('globals');
330
397
  * const updates = await state.set({ myParam: Math.random() });
331
398
  */
332
399
  async set(updates, context = null) {
333
- if (this._detached) {
400
+ if (this.#detached) {
334
401
  return;
335
402
  }
336
403
 
337
404
  if (!isPlainObject(updates)) {
338
- throw new TypeError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as first parameter`);
405
+ throw new TypeError(`[SharedState] State "${this.#className}": state.set(updates[, context]) should receive an object as first parameter`);
339
406
  }
340
407
 
341
408
  if (context !== null && !isPlainObject(context)) {
342
- throw new TypeError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as second parameter`);
409
+ throw new TypeError(`[SharedState] State "${this.#className}": state.set(updates[, context]) should receive an object as second parameter`);
343
410
  }
344
411
 
345
412
  const newValues = {};
@@ -355,12 +422,12 @@ ${JSON.stringify(initValues, null, 2)}`);
355
422
  // Try to coerce value early, so that eventual errors are triggered early
356
423
  // on the node requesting the update, and not only on the server side
357
424
  // This throws if name does not exists
358
- this._parameters.coerceValue(name, updates[name]);
425
+ this.#parameters.coerceValue(name, updates[name]);
359
426
 
360
427
  // Check that name is in filter list, if any
361
- if (this._filter !== null) {
362
- if (!this._filter.includes(name)) {
363
- throw new DOMException(`[SharedState] State "${this.schemaName}": cannot set parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
428
+ if (this.#filter !== null) {
429
+ if (!this.#filter.includes(name)) {
430
+ throw new DOMException(`[SharedState] State "${this.#className}": cannot set parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
364
431
  }
365
432
  }
366
433
 
@@ -385,11 +452,11 @@ ${JSON.stringify(initValues, null, 2)}`);
385
452
  // response, call update listeners with server response and resolve promise with
386
453
  // the full payload, i.e. reintegrating the local params in the resolve payload
387
454
 
388
- const { local, immediate, filterChange, event } = this._parameters.getSchema(name);
455
+ const { local, immediate, filterChange, event } = this.#parameters.getSchema(name);
389
456
 
390
457
  if (immediate || local) {
391
- const oldValue = this._parameters.get(name);
392
- const [newValue, changed] = this._parameters.set(name, updates[name]);
458
+ const oldValue = this.#parameters.get(name);
459
+ const [newValue, changed] = this.#parameters.set(name, updates[name]);
393
460
 
394
461
  // prepare data for immediate propagation of listeners
395
462
  if (changed || filterChange === false || event) {
@@ -403,7 +470,7 @@ ${JSON.stringify(initValues, null, 2)}`);
403
470
  if (local) {
404
471
  hasLocalParam = true;
405
472
  // get sanitize value for fulfilling promise
406
- localParams[name] = this._parameters.get(name);
473
+ localParams[name] = this.#parameters.get(name);
407
474
  } else {
408
475
  // note that immediate are shared params too
409
476
  hasSharedParam = true;
@@ -413,7 +480,7 @@ ${JSON.stringify(initValues, null, 2)}`);
413
480
 
414
481
  // propagate immediate params if changed
415
482
  if (propagateNow) {
416
- this._onUpdateCallbacks.forEach(listener => listener(newValues, oldValues, context));
483
+ this.#onUpdateCallbacks.forEach(listener => listener(newValues, oldValues, context));
417
484
  }
418
485
 
419
486
  // check if we can resolve immediately or if we need to go through network
@@ -431,62 +498,67 @@ ${JSON.stringify(initValues, null, 2)}`);
431
498
 
432
499
  // go through server-side normal behavior
433
500
  return new Promise((resolve, reject) => {
434
- const reqId = this._promiseStore.add(resolve, reject, 'update-request', forwardParams);
435
- this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
501
+ const reqId = this[kSharedStatePromiseStore].add(resolve, reject, 'update-request', forwardParams);
502
+ this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#remoteId}`, reqId, updates, context);
436
503
  });
437
504
  }
438
505
 
439
506
  /**
440
- * Get the value of a parameter of the state. If the parameter is of `any` type,
441
- * a deep copy is returned.
507
+ * Get the value of a parameter of the state.
508
+ *
509
+ * Be aware that in case of 'any' typethe returned value is deeply copied.
510
+ * While this prevents from pollution of the state by mutating the reference,
511
+ * this can also lead to performance issues when the parameter contains large
512
+ * data. In such cases you should use the {@link SharedState#getUnsafe} method
513
+ * and make sure to treat the returned object as readonly.
442
514
  *
443
- * @param {string} name - Name of the param.
444
- * @throws Throws if `name` does not correspond to an existing field
445
- * of the state.
446
- * @return {mixed}
515
+ * @param {SharedStateParameterName} name - Name of the param.
516
+ * @return {any}
517
+ * @throws Throws if `name` does not exists.
447
518
  * @example
448
519
  * const value = state.get('paramName');
449
520
  */
450
521
  get(name) {
451
- if (!this._parameters.has(name)) {
452
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": Cannot get value of undefined parameter "${name}"`);
522
+ if (!this.#parameters.has(name)) {
523
+ throw new ReferenceError(`[SharedState] State "${this.#className}": Cannot get value of undefined parameter "${name}"`);
453
524
  }
454
525
 
455
- if (this._filter !== null) {
456
- if (!this._filter.includes(name)) {
457
- throw new DOMException(`[SharedState] State "${this.schemaName}": cannot get parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
526
+ if (this.#filter !== null) {
527
+ if (!this.#filter.includes(name)) {
528
+ throw new DOMException(`[SharedState] State "${this.#className}": cannot get parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
458
529
  }
459
530
  }
460
531
 
461
- return this._parameters.get(name);
532
+ return this.#parameters.get(name);
462
533
  }
463
534
 
464
535
  /**
536
+ * Get an unsafe reference to the value of a parameter of the state.
537
+ *
465
538
  * Similar to `get` but returns a reference to the underlying value in case of
466
- * `any` type. May be usefull if the underlying value is big (e.g. sensors
539
+ * `any` type. Can be usefull if the underlying value is large (e.g. sensors
467
540
  * recordings, etc.) and deep cloning expensive. Be aware that if changes are
468
541
  * made on the returned object, the state of your application will become
469
542
  * inconsistent.
470
543
  *
471
- * @param {string} name - Name of the param.
472
- * @throws Throws if `name` does not correspond to an existing field
473
- * of the state.
474
- * @return {mixed}
544
+ * @param {SharedStateParameterName} name - Name of the param.
545
+ * @return {any}
546
+ * @throws Throws if `name` does not exists.
475
547
  * @example
476
548
  * const value = state.getUnsafe('paramName');
477
549
  */
478
550
  getUnsafe(name) {
479
- if (!this._parameters.has(name)) {
480
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": Cannot get value of undefined parameter "${name}"`);
551
+ if (!this.#parameters.has(name)) {
552
+ throw new ReferenceError(`[SharedState] State "${this.#className}": Cannot get value of undefined parameter "${name}"`);
481
553
  }
482
554
 
483
- if (this._filter !== null) {
484
- if (!this._filter.includes(name)) {
485
- throw new DOMException(`[SharedState] State "${this.schemaName}": cannot get parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
555
+ if (this.#filter !== null) {
556
+ if (!this.#filter.includes(name)) {
557
+ throw new DOMException(`[SharedState] State "${this.#className}": cannot get parameter '${name}', parameter is not in filter list`, 'NotSupportedError');
486
558
  }
487
559
  }
488
560
 
489
- return this._parameters.getUnsafe(name);
561
+ return this.#parameters.getUnsafe(name);
490
562
  }
491
563
 
492
564
  /**
@@ -499,11 +571,11 @@ ${JSON.stringify(initValues, null, 2)}`);
499
571
  * const values = state.getValues();
500
572
  */
501
573
  getValues() {
502
- const values = this._parameters.getValues();
574
+ const values = this.#parameters.getValues();
503
575
 
504
- if (this._filter !== null) {
576
+ if (this.#filter !== null) {
505
577
  for (let name in values) {
506
- if (!this._filter.includes(name)) {
578
+ if (!this.#filter.includes(name)) {
507
579
  delete values[name];
508
580
  }
509
581
  }
@@ -516,7 +588,7 @@ ${JSON.stringify(initValues, null, 2)}`);
516
588
  * Get all the key / value pairs of the state.
517
589
  *
518
590
  * Similar to `getValues` but returns a reference to the underlying value in
519
- * case of `any` type. May be usefull if the underlying value is big (e.g.
591
+ * case of `any` type. Can be usefull if the underlying value is big (e.g.
520
592
  * sensors recordings, etc.) and deep cloning expensive. Be aware that if
521
593
  * changes are made on the returned object, the state of your application will
522
594
  * become inconsistent.
@@ -526,11 +598,11 @@ ${JSON.stringify(initValues, null, 2)}`);
526
598
  * const values = state.getValues();
527
599
  */
528
600
  getValuesUnsafe() {
529
- const values = this._parameters.getValuesUnsafe();
601
+ const values = this.#parameters.getValuesUnsafe();
530
602
 
531
- if (this._filter !== null) {
603
+ if (this.#filter !== null) {
532
604
  for (let name in values) {
533
- if (!this._filter.includes(name)) {
605
+ if (!this.#filter.includes(name)) {
534
606
  delete values[name];
535
607
  }
536
608
  }
@@ -540,18 +612,18 @@ ${JSON.stringify(initValues, null, 2)}`);
540
612
  }
541
613
 
542
614
  /**
543
- * Definition of schema from which the state has been created.
615
+ * Return the underlying {@link SharedStateClassSchema} or the
616
+ * {@link SharedStateParameterDescription} if name is given.
544
617
  *
545
- * @param {string} [name=null] - If given, returns only the definition
546
- * corresponding to the given param name.
547
- * @throws Throws if `name` does not correspond to an existing field
548
- * of the schema.
549
- * @return {object}
618
+ * @param {string} [name] - If defined, returns only the parameter description
619
+ * of the given param name.
620
+ * @return {SharedStateClassSchema|SharedStateParameterDescription}
621
+ * @throws Throws if `name` does not exists.
550
622
  * @example
551
623
  * const schema = state.getSchema();
552
624
  */
553
625
  getSchema(name = null) {
554
- return this._parameters.getSchema(name);
626
+ return this.#parameters.getSchema(name);
555
627
  }
556
628
 
557
629
  /**
@@ -563,7 +635,7 @@ ${JSON.stringify(initValues, null, 2)}`);
563
635
  * const initValues = state.getInitValues();
564
636
  */
565
637
  getInitValues() {
566
- return this._parameters.getInitValues();
638
+ return this.#parameters.getInitValues();
567
639
  }
568
640
 
569
641
  /**
@@ -574,7 +646,7 @@ ${JSON.stringify(initValues, null, 2)}`);
574
646
  * const defaults = state.getDefaults();
575
647
  */
576
648
  getDefaults() {
577
- return this._parameters.getDefaults();
649
+ return this.#parameters.getDefaults();
578
650
  }
579
651
 
580
652
  /**
@@ -587,22 +659,22 @@ ${JSON.stringify(initValues, null, 2)}`);
587
659
  * await state.detach();
588
660
  */
589
661
  async detach() {
590
- if (this._detached) {
591
- throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
662
+ if (this.#detached) {
663
+ throw new Error(`[SharedState] State "${this.#className} (${this.#id})" already detached, cannot detach twice`);
592
664
  }
593
665
 
594
- this._detached = true; // mark detached early
595
- this._onUpdateCallbacks.clear();
666
+ this.#detached = true; // mark detached early
667
+ this.#onUpdateCallbacks.clear();
596
668
 
597
- if (this._isOwner) {
669
+ if (this.#isOwner) {
598
670
  return new Promise((resolve, reject) => {
599
- const reqId = this._promiseStore.add(resolve, reject, 'delete-request');
600
- this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
671
+ const reqId = this[kSharedStatePromiseStore].add(resolve, reject, 'delete-request');
672
+ this.#client.transport.emit(`${DELETE_REQUEST}-${this.#id}-${this.#remoteId}`, reqId);
601
673
  });
602
674
  } else {
603
675
  return new Promise((resolve, reject) => {
604
- const reqId = this._promiseStore.add(resolve, reject, 'detach-request');
605
- this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
676
+ const reqId = this[kSharedStatePromiseStore].add(resolve, reject, 'detach-request');
677
+ this.#client.transport.emit(`${DETACH_REQUEST}-${this.#id}-${this.#remoteId}`, reqId);
606
678
  });
607
679
  }
608
680
  }
@@ -623,25 +695,25 @@ ${JSON.stringify(initValues, null, 2)}`);
623
695
  * await state.delete();
624
696
  */
625
697
  async delete() {
626
- if (this._isOwner) {
627
- if (this._detached) {
628
- throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already deleted, cannot delete twice`);
698
+ if (this.#isOwner) {
699
+ if (this.#detached) {
700
+ throw new Error(`[SharedState] State "${this.#className} (${this.#id})" already deleted, cannot delete twice`);
629
701
  }
630
702
 
631
703
  return this.detach();
632
704
  } else {
633
- 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.`);
705
+ throw new Error(`[SharedState] Cannot delete state "${this.#className}", only the owner of the state (i.e. the node that created it) can delete the state. Use "detach" instead.`);
634
706
  }
635
707
  }
636
708
 
637
709
  /**
638
710
  * Subscribe to state updates.
639
711
  *
640
- * @param {client.SharedState~onUpdateCallback|server.SharedState~onUpdateCallback} callback
712
+ * @param {sharedStateOnUpdateCallback} callback
641
713
  * Callback to execute when an update is applied on the state.
642
714
  * @param {Boolean} [executeListener=false] - Execute the callback immediately
643
715
  * with current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
644
- * @returns {client.SharedState~deleteOnUpdateCallback|server.SharedState~deleteOnUpdateCallback}
716
+ * @returns {sharedStateDeleteOnUpdateCallback}
645
717
  * @example
646
718
  * const unsubscribe = state.onUpdate(async (newValues, oldValues, context) => {
647
719
  * for (let [key, value] of Object.entries(newValues)) {
@@ -655,7 +727,7 @@ ${JSON.stringify(initValues, null, 2)}`);
655
727
  * unsubscribe();
656
728
  */
657
729
  onUpdate(listener, executeListener = false) {
658
- this._onUpdateCallbacks.add(listener);
730
+ this.#onUpdateCallbacks.add(listener);
659
731
 
660
732
  if (executeListener === true) {
661
733
  const currentValues = this.getValues();
@@ -665,7 +737,7 @@ ${JSON.stringify(initValues, null, 2)}`);
665
737
  }
666
738
 
667
739
  return () => {
668
- this._onUpdateCallbacks.delete(listener);
740
+ this.#onUpdateCallbacks.delete(listener);
669
741
  };
670
742
  }
671
743
 
@@ -678,8 +750,8 @@ ${JSON.stringify(initValues, null, 2)}`);
678
750
  * creator.
679
751
  */
680
752
  onDetach(callback) {
681
- this._onDetachCallbacks.add(callback);
682
- return () => this._onDetachCallbacks.delete(callback);
753
+ this.#onDetachCallbacks.add(callback);
754
+ return () => this.#onDetachCallbacks.delete(callback);
683
755
  }
684
756
 
685
757
  /**
@@ -690,9 +762,9 @@ ${JSON.stringify(initValues, null, 2)}`);
690
762
  * @param {Function} callback - Callback to execute when the state is deleted.
691
763
  */
692
764
  onDelete(callback) {
693
- this._onDeleteCallbacks.add(callback);
694
- return () => this._onDeleteCallbacks.delete(callback);
765
+ this.#onDeleteCallbacks.add(callback);
766
+ return () => this.#onDeleteCallbacks.delete(callback);
695
767
  }
696
768
  }
697
769
 
698
- export default BaseSharedState;
770
+ export default SharedState;