@soundworks/core 4.0.0-alpha.13 → 4.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +13 -28
- package/package.json +17 -23
- package/src/client/Context.js +10 -11
- package/src/client/SharedStateCollection.js +5 -2
- package/src/client/Socket.js +0 -1
- package/src/common/BasePlugin.js +1 -1
- package/src/common/BasePluginManager.js +1 -1
- package/src/common/BaseSharedState.js +31 -27
- package/src/common/BaseSharedStateCollection.js +33 -10
- package/src/common/BaseStateManager.js +173 -53
- package/src/common/ParameterBag.js +11 -7
- package/src/common/PromiseStore.js +41 -0
- package/src/common/SharedStatePrivate.js +24 -27
- package/src/common/constants.js +2 -1
- package/src/common/sockets-utils.js +3 -4
- package/src/server/Server.js +2 -3
- package/src/server/SharedStateCollection.js +5 -2
- package/src/server/Socket.js +1 -1
- package/src/server/StateManager.js +34 -22
- package/src/server/audit-network-latency.worker.js +1 -1
- package/src/server/crypto.js +6 -7
- package/src/common/promise-store.js +0 -25
- package/tsconfig.json +0 -21
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -3,57 +3,38 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/@soundworks%2Fcore)
|
|
4
4
|
[](http://commitizen.github.io/cz-cli/)
|
|
5
5
|
|
|
6
|
-

|
|
6
|
+

|
|
7
7
|
|
|
8
8
|
Open-source creative coding framework for distributed applications based on Web technologies.
|
|
9
9
|
|
|
10
10
|
Primarily focused on music, **soundworks** aims at supporting rapid development of real-time distributed applications using _JavaScript_. It provides abstractions to hide the complexity of the network and to foster very rapid-prototyping and trial-and-error workflows that are typical in artistic practices.
|
|
11
11
|
|
|
12
|
-
*__WARNING: The version 4 of `@soundworks/core` is under heavy development.__*
|
|
13
|
-
|
|
14
|
-
## Getting Started
|
|
15
|
-
|
|
16
|
-
The best and most simple way to start using **soundworks** is to use the `@soundworks/create` wizard.
|
|
17
|
-
|
|
18
|
-
```sh
|
|
19
|
-
npx @soundworks/create@latest
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
See [https://soundworks.dev/tutorials/getting-started.html](https://soundworks.dev/tutorials/getting-started.html) for more informations on the wizard and how to start with **soundworks**.
|
|
25
|
-
|
|
26
|
-
<!--
|
|
27
12
|
## Documentation
|
|
28
13
|
|
|
29
14
|
- Guides and Tutorials: [https://soundworks.dev](https://soundworks.dev)
|
|
30
15
|
- API: [https://soundworks.dev/api](https://soundworks.dev/api)
|
|
31
|
-
-->
|
|
32
16
|
|
|
33
|
-
##
|
|
17
|
+
## Getting started
|
|
34
18
|
|
|
35
|
-
The
|
|
19
|
+
The best and most simple way to start using **soundworks** is to use the `@soundworks/create` wizard.
|
|
36
20
|
|
|
37
21
|
```sh
|
|
38
|
-
|
|
39
|
-
cd soundworks
|
|
40
|
-
git checkout v4
|
|
41
|
-
serve docs
|
|
22
|
+
npx @soundworks/create@latest
|
|
42
23
|
```
|
|
43
24
|
|
|
44
|
-
|
|
25
|
+

|
|
45
26
|
|
|
46
|
-
|
|
27
|
+
See [https://soundworks.dev/tutorials/getting-started.html](https://soundworks.dev/tutorials/getting-started.html) for more informations on the wizard and how to start using **soundworks**.
|
|
47
28
|
|
|
48
|
-
|
|
29
|
+
## Misc
|
|
49
30
|
|
|
50
|
-
|
|
31
|
+
### TypeScript support
|
|
51
32
|
|
|
52
33
|
Basic TypeScript support will be proposed in a (hopefully) near future.
|
|
53
34
|
|
|
54
35
|
However, for maintenance reasons, we aim at following the TC39 and W3C specifications as close as possible. Therefore, we will wait for the https://github.com/tc39/proposal-type-annotations proposal to reach stage 3 to update the source code in a more integrated manner.
|
|
55
36
|
|
|
56
|
-
|
|
37
|
+
### Manual install
|
|
57
38
|
|
|
58
39
|
Note that the `@soundworks/core` package is automatically installed when creating an application using the `@soundworks/create` wizard, so most of the time you should not have to install this package manually. See [https://soundworks.dev/guides/getting-started.html](https://soundworks.dev/guides/getting-started.html) for more informations on the **soundworks** wizard.
|
|
59
40
|
|
|
@@ -61,6 +42,10 @@ Note that the `@soundworks/core` package is automatically installed when creatin
|
|
|
61
42
|
npm install @soundworks/core
|
|
62
43
|
```
|
|
63
44
|
|
|
45
|
+
## Share with Us
|
|
46
|
+
|
|
47
|
+
If you made an application using **soundworks** please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
|
|
48
|
+
|
|
64
49
|
## Credits
|
|
65
50
|
|
|
66
51
|
[https://soundworks.dev/credits.html](https://soundworks.dev/credits.html)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soundworks/core",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.15",
|
|
4
4
|
"description": "Open-source creative coding framework for distributed applications based on Web technologies",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Benjamin Matuszewski",
|
|
@@ -43,51 +43,45 @@
|
|
|
43
43
|
"node": ">=16.0.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
-
"doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
|
|
46
|
+
"doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R misc/assets docs/",
|
|
47
47
|
"lint": "npx eslint src",
|
|
48
|
-
"test": "mocha",
|
|
49
|
-
"
|
|
48
|
+
"test": "npx mocha",
|
|
49
|
+
"test:all": "npx mocha tests/*/*.spec.js",
|
|
50
50
|
"types": "rm -Rf types && tsc",
|
|
51
|
-
"
|
|
51
|
+
"postversion": "node ./misc/scripts/check-changelog.js"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@ircam/sc-
|
|
55
|
-
"@ircam/sc-utils": "^1.1.1",
|
|
54
|
+
"@ircam/sc-utils": "^1.3.2",
|
|
56
55
|
"chalk": "^5.3.0",
|
|
57
56
|
"columnify": "^1.6.0",
|
|
58
57
|
"compression": "^1.7.1",
|
|
59
|
-
"cross-fetch": "^3.1.8",
|
|
60
58
|
"express": "^4.18.1",
|
|
61
59
|
"fast-deep-equal": "^3.1.3",
|
|
62
60
|
"fast-text-encoding": "^1.0.0",
|
|
63
61
|
"isomorphic-ws": "^5.0.0",
|
|
64
|
-
"keyv": "^4.
|
|
65
|
-
"keyv-file": "^0.
|
|
66
|
-
"lodash
|
|
67
|
-
"lodash.merge": "^4.6.2",
|
|
62
|
+
"keyv": "^4.5.4",
|
|
63
|
+
"keyv-file": "^0.3.0",
|
|
64
|
+
"lodash": "^4.17.21",
|
|
68
65
|
"pem": "^1.14.8",
|
|
69
66
|
"template-literal": "^1.0.4",
|
|
70
|
-
"uuid": "^9.0.
|
|
71
|
-
"
|
|
72
|
-
"ws": "^8.12.0"
|
|
67
|
+
"uuid": "^9.0.1",
|
|
68
|
+
"ws": "^8.15.0"
|
|
73
69
|
},
|
|
74
70
|
"devDependencies": {
|
|
75
71
|
"@ircam/eslint-config": "^1.3.0",
|
|
76
|
-
"chai": "^4.3.
|
|
72
|
+
"chai": "^4.3.10",
|
|
77
73
|
"chai-shallow-deep-equal": "^1.4.6",
|
|
78
74
|
"commitizen": "^4.2.5",
|
|
79
75
|
"cz-conventional-changelog": "^3.3.0",
|
|
80
|
-
"docdash": "^2.0.
|
|
76
|
+
"docdash": "^2.0.2",
|
|
81
77
|
"dotenv": "^16.3.1",
|
|
82
|
-
"eslint": "^8.
|
|
83
|
-
"eslint-plugin-jsdoc": "^46.
|
|
84
|
-
"generate-changelog": "^1.8.0",
|
|
78
|
+
"eslint": "^8.55.0",
|
|
79
|
+
"eslint-plugin-jsdoc": "^46.9.0",
|
|
85
80
|
"jsdoc": "^4.0.0",
|
|
86
|
-
"markdown-toc": "^1.2.0",
|
|
87
81
|
"mocha": "^10.2.0",
|
|
88
|
-
"puppeteer": "^
|
|
82
|
+
"puppeteer": "^21.6.0",
|
|
89
83
|
"tcp-ping": "^0.1.1",
|
|
90
|
-
"typescript": "^5.
|
|
84
|
+
"typescript": "^5.3.3"
|
|
91
85
|
},
|
|
92
86
|
"optionalDependencies": {
|
|
93
87
|
"bufferutil": "^4.0.5",
|
package/src/client/Context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Client from './Client.js';
|
|
2
|
+
import PromiseStore from '../common/PromiseStore.js';
|
|
2
3
|
import {
|
|
3
4
|
CONTEXT_ENTER_REQUEST,
|
|
4
5
|
CONTEXT_ENTER_RESPONSE,
|
|
@@ -7,11 +8,6 @@ import {
|
|
|
7
8
|
CONTEXT_EXIT_RESPONSE,
|
|
8
9
|
CONTEXT_EXIT_ERROR,
|
|
9
10
|
} from '../common/constants.js';
|
|
10
|
-
import {
|
|
11
|
-
storeRequestPromise,
|
|
12
|
-
resolveRequest,
|
|
13
|
-
rejectRequest,
|
|
14
|
-
} from '../common/promise-store.js';
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Base class to extend in order to implement a new Context.
|
|
@@ -81,12 +77,15 @@ class Context {
|
|
|
81
77
|
*/
|
|
82
78
|
this.status = 'idle';
|
|
83
79
|
|
|
80
|
+
/** @private */
|
|
81
|
+
this._promiseStore = new PromiseStore(this.constructor.name);
|
|
82
|
+
|
|
84
83
|
this.client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {
|
|
85
84
|
if (contextName !== this.name) {
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
this._promiseStore.resolve(reqId);
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
this.client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
|
|
@@ -94,7 +93,7 @@ class Context {
|
|
|
94
93
|
return;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
this._promiseStore.reject(reqId, msg);
|
|
98
97
|
});
|
|
99
98
|
|
|
100
99
|
this.client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
|
|
@@ -102,7 +101,7 @@ class Context {
|
|
|
102
101
|
return;
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
this._promiseStore.resolve(reqId);
|
|
106
105
|
});
|
|
107
106
|
|
|
108
107
|
this.client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
|
|
@@ -110,7 +109,7 @@ class Context {
|
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
|
|
112
|
+
this._promiseStore.reject(reqId, msg);
|
|
114
113
|
});
|
|
115
114
|
|
|
116
115
|
this.client.contextManager.register(this);
|
|
@@ -206,7 +205,7 @@ class Context {
|
|
|
206
205
|
// we need the try/catch block to change the promise rejection into proper error
|
|
207
206
|
try {
|
|
208
207
|
await new Promise((resolve, reject) => {
|
|
209
|
-
const reqId =
|
|
208
|
+
const reqId = this._promiseStore.add(resolve, reject, 'enter-context');
|
|
210
209
|
this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
|
|
211
210
|
});
|
|
212
211
|
} catch (err) {
|
|
@@ -240,7 +239,7 @@ class Context {
|
|
|
240
239
|
// we need the try/catch block to change the promise rejection into proper error
|
|
241
240
|
try {
|
|
242
241
|
await new Promise((resolve, reject) => {
|
|
243
|
-
const reqId =
|
|
242
|
+
const reqId = this._promiseStore.add(resolve, reject, 'exit-context');
|
|
244
243
|
this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
|
|
245
244
|
});
|
|
246
245
|
} catch (err) {
|
|
@@ -15,8 +15,11 @@ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The `SharedStateCollection` interface represent a collection of all states
|
|
18
|
-
* created from a given schema name on the network
|
|
19
|
-
*
|
|
18
|
+
* created from a given schema name on the network.
|
|
19
|
+
*
|
|
20
|
+
* It can optionnaly exclude the states created by the current node.
|
|
21
|
+
*
|
|
22
|
+
* See {@link client.StateManager#getCollection} for factory method API
|
|
20
23
|
*
|
|
21
24
|
* ```
|
|
22
25
|
* const collection = await client.stateManager.getCollection('my-schema');
|
package/src/client/Socket.js
CHANGED
package/src/common/BasePlugin.js
CHANGED
|
@@ -114,7 +114,7 @@ class BasePluginManager {
|
|
|
114
114
|
|
|
115
115
|
this.status = 'inited';
|
|
116
116
|
// instanciate all plugins
|
|
117
|
-
for (let [
|
|
117
|
+
for (let [_id, instance] of this._instances.entries()) {
|
|
118
118
|
instance.onStateChange(_values => this._propagateStateChange(instance));
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isPlainObject } from '@ircam/sc-utils';
|
|
2
2
|
import ParameterBag from './ParameterBag.js';
|
|
3
|
+
import PromiseStore from './PromiseStore.js';
|
|
3
4
|
import {
|
|
4
5
|
DELETE_REQUEST,
|
|
5
6
|
DELETE_RESPONSE,
|
|
@@ -13,11 +14,6 @@ import {
|
|
|
13
14
|
UPDATE_ABORT,
|
|
14
15
|
UPDATE_NOTIFICATION,
|
|
15
16
|
} from './constants.js';
|
|
16
|
-
import {
|
|
17
|
-
storeRequestPromise,
|
|
18
|
-
resolveRequest,
|
|
19
|
-
rejectRequest,
|
|
20
|
-
} from './promise-store.js';
|
|
21
17
|
|
|
22
18
|
class BaseSharedState {
|
|
23
19
|
constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
|
|
@@ -33,8 +29,13 @@ class BaseSharedState {
|
|
|
33
29
|
this._client = client;
|
|
34
30
|
/** @private */
|
|
35
31
|
this._manager = manager;
|
|
36
|
-
|
|
32
|
+
/**
|
|
33
|
+
* true is the state has been detached or deleted
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
37
36
|
this._detached = false;
|
|
37
|
+
/* @private */
|
|
38
|
+
this._promiseStore = new PromiseStore(this.constructor.name);
|
|
38
39
|
|
|
39
40
|
try {
|
|
40
41
|
/** @private */
|
|
@@ -56,13 +57,13 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
56
57
|
// add listener for state updates
|
|
57
58
|
this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
58
59
|
const updated = await this._commit(updates, context, true, true);
|
|
59
|
-
|
|
60
|
+
this._promiseStore.resolve(reqId, updated);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
// retrieve values but do not propagate to subscriptions
|
|
63
64
|
this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
64
65
|
const updated = await this._commit(updates, context, false, true);
|
|
65
|
-
|
|
66
|
+
this._promiseStore.resolve(reqId, updated);
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
|
|
@@ -101,7 +102,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
101
102
|
for (let callback of this._onDetachCallbacks) {
|
|
102
103
|
try {
|
|
103
104
|
await callback();
|
|
104
|
-
} catch(err) {
|
|
105
|
+
} catch (err) {
|
|
105
106
|
console.error(err.message);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -112,7 +113,9 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
this.
|
|
116
|
+
this._onDetachCallbacks.clear();
|
|
117
|
+
this._onDeleteCallbacks.clear();
|
|
118
|
+
this._promiseStore.flush();
|
|
116
119
|
});
|
|
117
120
|
|
|
118
121
|
|
|
@@ -132,12 +135,14 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
132
135
|
await callback();
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
this.
|
|
136
|
-
|
|
138
|
+
this._onDetachCallbacks.clear();
|
|
139
|
+
this._onDeleteCallbacks.clear();
|
|
140
|
+
this._promiseStore.resolve(reqId, this);
|
|
141
|
+
this._promiseStore.flush();
|
|
137
142
|
});
|
|
138
143
|
|
|
139
144
|
this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
|
|
140
|
-
|
|
145
|
+
this._promiseStore.reject(reqId, msg);
|
|
141
146
|
});
|
|
142
147
|
|
|
143
148
|
} else {
|
|
@@ -152,13 +157,18 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
152
157
|
await callback();
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
this.
|
|
156
|
-
|
|
160
|
+
this._onDetachCallbacks.clear();
|
|
161
|
+
this._onDeleteCallbacks.clear();
|
|
162
|
+
this._promiseStore.resolve(reqId, this);
|
|
163
|
+
this._promiseStore.flush();
|
|
157
164
|
});
|
|
158
165
|
|
|
159
166
|
// the state does not exists anymore in the server (should not happen)
|
|
160
167
|
this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
|
|
161
|
-
|
|
168
|
+
this._onDetachCallbacks.clear();
|
|
169
|
+
this._onDeleteCallbacks.clear();
|
|
170
|
+
this._promiseStore.reject(reqId, msg);
|
|
171
|
+
this._promiseStore.flush();
|
|
162
172
|
});
|
|
163
173
|
}
|
|
164
174
|
}
|
|
@@ -200,13 +210,6 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
200
210
|
return this._isOwner;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
|
-
/** @private */
|
|
204
|
-
_clearDetach() {
|
|
205
|
-
this._onDetachCallbacks.clear();
|
|
206
|
-
this._onDeleteCallbacks.clear();
|
|
207
|
-
this._detached = true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
213
|
/** @private */
|
|
211
214
|
_clearTransport() {
|
|
212
215
|
// remove listeners
|
|
@@ -325,7 +328,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
325
328
|
for (let name in updates) {
|
|
326
329
|
// try to coerce value early, so that eventual errors are triggered early
|
|
327
330
|
// on the node requesting the update
|
|
328
|
-
|
|
331
|
+
this._parameters.coerceValue(name, updates[name]);
|
|
329
332
|
|
|
330
333
|
// @note: general idea...
|
|
331
334
|
// if immediate=true
|
|
@@ -358,7 +361,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
358
361
|
|
|
359
362
|
// go through server-side normal behavior
|
|
360
363
|
return new Promise((resolve, reject) => {
|
|
361
|
-
const reqId =
|
|
364
|
+
const reqId = this._promiseStore.add(resolve, reject, 'update-request');
|
|
362
365
|
this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
|
|
363
366
|
});
|
|
364
367
|
}
|
|
@@ -477,16 +480,17 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
477
480
|
throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
|
|
478
481
|
}
|
|
479
482
|
|
|
483
|
+
this._detached = true; // mark detached early
|
|
480
484
|
this._onUpdateCallbacks.clear();
|
|
481
485
|
|
|
482
486
|
if (this._isOwner) {
|
|
483
487
|
return new Promise((resolve, reject) => {
|
|
484
|
-
const reqId =
|
|
488
|
+
const reqId = this._promiseStore.add(resolve, reject, 'delete-request');
|
|
485
489
|
this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
|
|
486
490
|
});
|
|
487
491
|
} else {
|
|
488
492
|
return new Promise((resolve, reject) => {
|
|
489
|
-
const reqId =
|
|
493
|
+
const reqId = this._promiseStore.add(resolve, reject, 'detach-request');
|
|
490
494
|
this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
|
|
491
495
|
});
|
|
492
496
|
}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* @private
|
|
3
3
|
*/
|
|
4
4
|
class BaseSharedStateCollection {
|
|
5
|
-
constructor(stateManager, schemaName) {
|
|
5
|
+
constructor(stateManager, schemaName, options = {}) {
|
|
6
6
|
this._stateManager = stateManager;
|
|
7
7
|
this._schemaName = schemaName;
|
|
8
|
+
this._options = Object.assign({ excludeLocal: false }, options);
|
|
9
|
+
|
|
8
10
|
this._states = [];
|
|
11
|
+
|
|
9
12
|
this._onUpdateCallbacks = new Set();
|
|
10
13
|
this._onAttachCallbacks = new Set();
|
|
11
14
|
this._onDetachCallbacks = new Set();
|
|
@@ -13,7 +16,7 @@ class BaseSharedStateCollection {
|
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
async _init() {
|
|
16
|
-
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId
|
|
19
|
+
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId) => {
|
|
17
20
|
const state = await this._stateManager.attach(schemaName, stateId);
|
|
18
21
|
|
|
19
22
|
this._states.push(state);
|
|
@@ -32,28 +35,36 @@ class BaseSharedStateCollection {
|
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
this._onAttachCallbacks.forEach(callback => callback(state));
|
|
35
|
-
});
|
|
38
|
+
}, this._options);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
|
-
* Size of the collection
|
|
42
|
+
* Size of the collection, alias `size`
|
|
40
43
|
* @type {number}
|
|
41
44
|
*/
|
|
42
45
|
get length() {
|
|
43
46
|
return this._states.length;
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Size of the collection, , alias `length`
|
|
51
|
+
* @type {number}
|
|
52
|
+
*/
|
|
53
|
+
get size() {
|
|
54
|
+
return this._states.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
/**
|
|
47
58
|
* Detach from the collection, i.e. detach all underlying shared states.
|
|
48
59
|
* @type {number}
|
|
49
60
|
*/
|
|
50
61
|
async detach() {
|
|
51
62
|
this._unobserve();
|
|
63
|
+
this._onUpdateCallbacks.clear();
|
|
52
64
|
|
|
53
|
-
const promises =
|
|
65
|
+
const promises = this._states.map(state => state.detach());
|
|
54
66
|
await Promise.all(promises);
|
|
55
67
|
|
|
56
|
-
this._onUpdateCallbacks.clear();
|
|
57
68
|
this._onDetachCallbacks.clear();
|
|
58
69
|
}
|
|
59
70
|
|
|
@@ -118,9 +129,18 @@ class BaseSharedStateCollection {
|
|
|
118
129
|
*
|
|
119
130
|
* @param {Function} callback - callback to execute when a state is added to
|
|
120
131
|
* the collection.
|
|
132
|
+
* @param {Function} executeListener - execute the callback with the states
|
|
133
|
+
* already present in the collection.
|
|
134
|
+
* @returns {Function} - Function that delete the registered listener.
|
|
121
135
|
*/
|
|
122
|
-
onAttach(callback) {
|
|
136
|
+
onAttach(callback, executeListener = false) {
|
|
137
|
+
if (executeListener === true) {
|
|
138
|
+
this._states.forEach(state => callback(state));
|
|
139
|
+
}
|
|
140
|
+
|
|
123
141
|
this._onAttachCallbacks.add(callback);
|
|
142
|
+
|
|
143
|
+
return () => this._onAttachCallbacks.delete(callback);
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
/**
|
|
@@ -128,9 +148,12 @@ class BaseSharedStateCollection {
|
|
|
128
148
|
*
|
|
129
149
|
* @param {Function} callback - callback to execute when a state is removed
|
|
130
150
|
* from the collection.
|
|
151
|
+
* @returns {Function} - Function that delete the registered listener.
|
|
131
152
|
*/
|
|
132
153
|
onDetach(callback) {
|
|
133
154
|
this._onDetachCallbacks.add(callback);
|
|
155
|
+
|
|
156
|
+
return () => this._onDetachCallbacks.delete(callback);
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
/**
|
|
@@ -188,7 +211,7 @@ class BaseSharedStateCollection {
|
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
/**
|
|
191
|
-
* Iterable API for use in `for .. of` loops
|
|
214
|
+
* Iterable API, e.g. for use in `for .. of` loops
|
|
192
215
|
*/
|
|
193
216
|
[Symbol.iterator]() {
|
|
194
217
|
let index = 0;
|
|
@@ -200,8 +223,8 @@ class BaseSharedStateCollection {
|
|
|
200
223
|
} else {
|
|
201
224
|
return { value: this._states[index++], done: false };
|
|
202
225
|
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
226
|
+
},
|
|
227
|
+
};
|
|
205
228
|
}
|
|
206
229
|
}
|
|
207
230
|
|