@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.
- package/.jsdoc +3 -1
- package/CHANGELOG.md +11 -5
- package/package.json +5 -5
- package/src/client/Client.js +78 -83
- package/src/client/{Context.js → ClientContext.js} +48 -38
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +22 -18
- package/src/client/{PluginManager.js → ClientPluginManager.js} +39 -35
- package/src/client/{Socket.js → ClientSocket.js} +54 -65
- package/src/client/{StateManager.js → ClientStateManager.js} +7 -8
- package/src/client/index.js +17 -41
- package/src/common/BasePlugin.js +56 -51
- package/src/common/BasePluginManager.js +175 -139
- package/src/common/BaseStateManager.js +316 -120
- package/src/common/BatchedTransport.js +24 -24
- package/src/common/ParameterBag.js +18 -6
- package/src/common/PromiseStore.js +1 -0
- package/src/common/{BaseSharedState.js → SharedState.js} +271 -199
- package/src/common/{BaseSharedStateCollection.js → SharedStateCollection.js} +92 -61
- package/src/common/logger.js +5 -0
- package/src/common/shared-state-types.js +76 -0
- package/src/common/version.js +1 -1
- package/src/server/Server.js +391 -354
- package/src/server/ServerClient.js +73 -0
- package/src/server/{Context.js → ServerContext.js} +85 -66
- package/src/server/{ContextManager.js → ServerContextManager.js} +98 -85
- package/src/server/{Plugin.js → ServerPlugin.js} +10 -24
- package/src/server/{PluginManager.js → ServerPluginManager.js} +76 -69
- package/src/server/{Socket.js → ServerSocket.js} +19 -18
- package/src/server/{Sockets.js → ServerSockets.js} +36 -27
- package/src/server/ServerStateManager.js +515 -0
- package/src/{common → server}/SharedStatePrivate.js +61 -26
- package/src/server/audit-schema.js +1 -1
- package/src/server/crypto.js +2 -0
- package/src/server/index.js +18 -21
- package/types/client/Client.d.ts +74 -95
- package/types/client/{Context.d.ts → ClientContext.d.ts} +25 -26
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/{Plugin.d.ts → ClientPlugin.d.ts} +17 -20
- package/types/client/{PluginManager.d.ts → ClientPluginManager.d.ts} +13 -36
- package/types/client/ClientSocket.d.ts +74 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +11 -15
- package/types/client/index.d.ts +26 -2
- package/types/common/BasePlugin.d.ts +52 -46
- package/types/common/BasePluginManager.d.ts +68 -44
- package/types/common/BaseStateManager.d.ts +207 -97
- package/types/common/BatchedTransport.d.ts +1 -5
- package/types/common/ParameterBag.d.ts +16 -18
- package/types/common/PromiseStore.d.ts +1 -1
- package/types/common/SharedState.d.ts +297 -0
- package/types/common/{BaseSharedStateCollection.d.ts → SharedStateCollection.d.ts} +34 -18
- package/types/common/constants.d.ts +0 -2
- package/types/common/logger.d.ts +2 -1
- package/types/common/shared-state-types.d.ts +83 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +1 -2
- package/types/server/Server.d.ts +78 -132
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/{Context.d.ts → ServerContext.d.ts} +53 -43
- package/types/server/ServerContextManager.d.ts +80 -0
- package/types/server/{Plugin.d.ts → ServerPlugin.d.ts} +19 -33
- package/types/server/{PluginManager.d.ts → ServerPluginManager.d.ts} +39 -37
- package/types/server/ServerSocket.d.ts +85 -0
- package/types/server/ServerSockets.d.ts +50 -0
- package/types/server/ServerStateManager.d.ts +189 -0
- package/types/server/SharedStatePrivate.d.ts +21 -0
- package/types/server/audit-network-latency.worker.d.ts +1 -2
- package/types/server/audit-schema.d.ts +0 -1
- package/types/server/crypto.d.ts +2 -1
- package/types/server/index.d.ts +24 -2
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -38
- package/src/server/Client.js +0 -55
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -39
- package/src/server/StateManager.js +0 -696
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/SharedStateCollection.d.ts +0 -41
- package/types/client/SharedStateCollection.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -149
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -244
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseSharedStateCollection.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/BatchedTransport.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/PromiseStore.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -21
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/version.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -45
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/SharedStateCollection.d.ts +0 -41
- package/types/server/SharedStateCollection.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -164
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -80
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -405
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/crypto.d.ts.map +0 -1
- 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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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 "${
|
|
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
|
|
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.
|
|
70
|
-
const updated = await this
|
|
71
|
-
this.
|
|
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.
|
|
76
|
-
const updated = await this
|
|
77
|
-
this.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
110
|
-
this
|
|
111
|
-
this
|
|
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
|
|
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
|
|
122
|
-
for (let callback of this
|
|
191
|
+
if (this.#isOwner) {
|
|
192
|
+
for (let callback of this.#onDeleteCallbacks) {
|
|
123
193
|
await callback();
|
|
124
194
|
}
|
|
125
195
|
}
|
|
126
196
|
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
129
|
-
this.
|
|
197
|
+
this.#onDetachCallbacks.clear();
|
|
198
|
+
this.#onDeleteCallbacks.clear();
|
|
199
|
+
this[kSharedStatePromiseStore].flush();
|
|
130
200
|
});
|
|
131
201
|
|
|
132
202
|
|
|
133
|
-
if (this
|
|
203
|
+
if (this.#isOwner) {
|
|
134
204
|
// ---------------------------------------------
|
|
135
205
|
// the creator has called `.delete()`
|
|
136
206
|
// ---------------------------------------------
|
|
137
|
-
this.
|
|
138
|
-
this
|
|
139
|
-
this
|
|
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
|
|
211
|
+
for (let callback of this.#onDetachCallbacks) {
|
|
142
212
|
await callback();
|
|
143
213
|
}
|
|
144
214
|
|
|
145
|
-
for (let callback of this
|
|
215
|
+
for (let callback of this.#onDeleteCallbacks) {
|
|
146
216
|
await callback();
|
|
147
217
|
}
|
|
148
218
|
|
|
149
|
-
this.
|
|
150
|
-
this.
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
219
|
+
this.#onDetachCallbacks.clear();
|
|
220
|
+
this.#onDeleteCallbacks.clear();
|
|
221
|
+
this[kSharedStatePromiseStore].resolve(reqId, this);
|
|
222
|
+
this[kSharedStatePromiseStore].flush();
|
|
153
223
|
});
|
|
154
224
|
|
|
155
|
-
this.
|
|
156
|
-
this.
|
|
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.
|
|
164
|
-
this
|
|
165
|
-
this
|
|
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
|
|
237
|
+
for (let callback of this.#onDetachCallbacks) {
|
|
168
238
|
await callback();
|
|
169
239
|
}
|
|
170
240
|
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
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.
|
|
179
|
-
this.
|
|
180
|
-
this.
|
|
181
|
-
this.
|
|
182
|
-
this.
|
|
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
|
|
262
|
+
return this.#id;
|
|
194
263
|
}
|
|
195
264
|
|
|
196
265
|
/**
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
199
|
-
* @
|
|
200
|
-
* @private
|
|
266
|
+
* Name of the underlying {@link SharedState} class.
|
|
267
|
+
* @type {String}
|
|
268
|
+
* @deprecated
|
|
201
269
|
*/
|
|
202
|
-
get
|
|
203
|
-
|
|
270
|
+
get schemaName() {
|
|
271
|
+
logger.deprecated('SharedState#schemaName', 'SharedState#className');
|
|
272
|
+
return this.#className;
|
|
204
273
|
}
|
|
205
274
|
|
|
206
275
|
/**
|
|
207
|
-
* Name of the
|
|
276
|
+
* Name of the underlying {@link SharedState} class.
|
|
208
277
|
* @type {String}
|
|
209
|
-
* @readonly
|
|
210
278
|
*/
|
|
211
|
-
get
|
|
212
|
-
return this
|
|
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
|
|
288
|
+
return this.#isOwner;
|
|
222
289
|
}
|
|
223
290
|
|
|
224
|
-
|
|
225
|
-
_clearTransport() {
|
|
291
|
+
#clearTransport() {
|
|
226
292
|
// remove listeners
|
|
227
|
-
this.
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
if (this
|
|
233
|
-
this.
|
|
234
|
-
this.
|
|
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.
|
|
237
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
253
|
-
const [newValue, changed] = this.
|
|
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.
|
|
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.
|
|
351
|
+
const { event } = this.#parameters.getSchema(name);
|
|
287
352
|
|
|
288
353
|
if (event) {
|
|
289
|
-
this.
|
|
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,
|
|
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 -
|
|
324
|
-
* @param {mixed} [context=null] -
|
|
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.
|
|
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
|
|
400
|
+
if (this.#detached) {
|
|
334
401
|
return;
|
|
335
402
|
}
|
|
336
403
|
|
|
337
404
|
if (!isPlainObject(updates)) {
|
|
338
|
-
throw new TypeError(`[SharedState] State "${this
|
|
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
|
|
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.
|
|
425
|
+
this.#parameters.coerceValue(name, updates[name]);
|
|
359
426
|
|
|
360
427
|
// Check that name is in filter list, if any
|
|
361
|
-
if (this
|
|
362
|
-
if (!this.
|
|
363
|
-
throw new DOMException(`[SharedState] State "${this
|
|
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.
|
|
455
|
+
const { local, immediate, filterChange, event } = this.#parameters.getSchema(name);
|
|
389
456
|
|
|
390
457
|
if (immediate || local) {
|
|
391
|
-
const oldValue = this.
|
|
392
|
-
const [newValue, changed] = this.
|
|
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.
|
|
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.
|
|
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.
|
|
435
|
-
this.
|
|
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.
|
|
441
|
-
*
|
|
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 {
|
|
444
|
-
* @
|
|
445
|
-
*
|
|
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.
|
|
452
|
-
throw new ReferenceError(`[SharedState] State "${this
|
|
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
|
|
456
|
-
if (!this.
|
|
457
|
-
throw new DOMException(`[SharedState] State "${this
|
|
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.
|
|
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.
|
|
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 {
|
|
472
|
-
* @
|
|
473
|
-
*
|
|
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.
|
|
480
|
-
throw new ReferenceError(`[SharedState] State "${this
|
|
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
|
|
484
|
-
if (!this.
|
|
485
|
-
throw new DOMException(`[SharedState] State "${this
|
|
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.
|
|
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.
|
|
574
|
+
const values = this.#parameters.getValues();
|
|
503
575
|
|
|
504
|
-
if (this
|
|
576
|
+
if (this.#filter !== null) {
|
|
505
577
|
for (let name in values) {
|
|
506
|
-
if (!this.
|
|
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.
|
|
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.
|
|
601
|
+
const values = this.#parameters.getValuesUnsafe();
|
|
530
602
|
|
|
531
|
-
if (this
|
|
603
|
+
if (this.#filter !== null) {
|
|
532
604
|
for (let name in values) {
|
|
533
|
-
if (!this.
|
|
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
|
-
*
|
|
615
|
+
* Return the underlying {@link SharedStateClassSchema} or the
|
|
616
|
+
* {@link SharedStateParameterDescription} if name is given.
|
|
544
617
|
*
|
|
545
|
-
* @param {string} [name
|
|
546
|
-
*
|
|
547
|
-
* @
|
|
548
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
591
|
-
throw new Error(`[SharedState] State "${this
|
|
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
|
|
595
|
-
this.
|
|
666
|
+
this.#detached = true; // mark detached early
|
|
667
|
+
this.#onUpdateCallbacks.clear();
|
|
596
668
|
|
|
597
|
-
if (this
|
|
669
|
+
if (this.#isOwner) {
|
|
598
670
|
return new Promise((resolve, reject) => {
|
|
599
|
-
const reqId = this.
|
|
600
|
-
this.
|
|
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.
|
|
605
|
-
this.
|
|
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
|
|
627
|
-
if (this
|
|
628
|
-
throw new Error(`[SharedState] State "${this
|
|
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
|
|
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 {
|
|
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 {
|
|
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.
|
|
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.
|
|
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.
|
|
682
|
-
return () => this.
|
|
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.
|
|
694
|
-
return () => this.
|
|
765
|
+
this.#onDeleteCallbacks.add(callback);
|
|
766
|
+
return () => this.#onDeleteCallbacks.delete(callback);
|
|
695
767
|
}
|
|
696
768
|
}
|
|
697
769
|
|
|
698
|
-
export default
|
|
770
|
+
export default SharedState;
|