@soundworks/core 4.0.0-alpha.14 → 4.0.0-alpha.16
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 +57 -0
- package/CHANGELOG.md +8 -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 +43 -31
- package/src/common/BaseSharedStateCollection.js +75 -16
- package/src/common/BaseStateManager.js +197 -54
- package/src/common/ParameterBag.js +11 -7
- package/src/common/PromiseStore.js +41 -0
- package/src/common/SharedStatePrivate.js +100 -42
- 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 +130 -88
- 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/.jsdoc
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": {
|
|
3
|
+
"include": [
|
|
4
|
+
"misc/assets/DOC.md",
|
|
5
|
+
"src/client",
|
|
6
|
+
"src/server",
|
|
7
|
+
"src/common"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
"plugins/markdown"
|
|
12
|
+
],
|
|
13
|
+
"opts": {
|
|
14
|
+
"projectTitle": "Soundworks",
|
|
15
|
+
"destination": "./docs",
|
|
16
|
+
"encoding": "utf8",
|
|
17
|
+
"template": "./node_modules/docdash"
|
|
18
|
+
},
|
|
19
|
+
"templates": {
|
|
20
|
+
"default": {
|
|
21
|
+
"useLongnameInNav": true
|
|
22
|
+
},
|
|
23
|
+
"footer": ""
|
|
24
|
+
},
|
|
25
|
+
"docdash": {
|
|
26
|
+
"sectionOrder": [
|
|
27
|
+
"Namespaces",
|
|
28
|
+
"Classes",
|
|
29
|
+
"Modules",
|
|
30
|
+
"Externals",
|
|
31
|
+
"Events",
|
|
32
|
+
"Mixins",
|
|
33
|
+
"Tutorials",
|
|
34
|
+
"Interfaces"
|
|
35
|
+
],
|
|
36
|
+
"scripts": [
|
|
37
|
+
"assets/custom.css"
|
|
38
|
+
],
|
|
39
|
+
"meta": {
|
|
40
|
+
"title": "soundworks",
|
|
41
|
+
"description": "Documentation of soundworks, a full-stack JavaScript framework for distributed WebAudio and multimedia applications"
|
|
42
|
+
},
|
|
43
|
+
"search": true,
|
|
44
|
+
"menu": {
|
|
45
|
+
"Website": {
|
|
46
|
+
"href":"http://soundworks.dev/",
|
|
47
|
+
"target":"_blank",
|
|
48
|
+
"class":"menu-item"
|
|
49
|
+
},
|
|
50
|
+
"Github": {
|
|
51
|
+
"href": "https://github.com/collective-soundworks/soundworks",
|
|
52
|
+
"target":"_blank",
|
|
53
|
+
"class":"menu-item"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## v4.0.0-alpha.16 - 15/12/2023
|
|
2
|
+
|
|
3
|
+
- improve StateCollection performances
|
|
4
|
+
|
|
5
|
+
## v4.0.0-alpha.15 - 13/12/2023
|
|
6
|
+
|
|
7
|
+
- Fix several issues with shared states
|
|
8
|
+
- **Breaking Change**: by default, `StateManager::observe` and `SharedStateCollection` now retrieve locally created states
|
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.16",
|
|
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
|
|
46
|
+
"doc": "rm -Rf docs && jsdoc -c .jsdoc --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,14 @@ import {
|
|
|
13
14
|
UPDATE_ABORT,
|
|
14
15
|
UPDATE_NOTIFICATION,
|
|
15
16
|
} from './constants.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
// @todo - replace isOwner by `type`:
|
|
19
|
+
// enum {
|
|
20
|
+
// 'owner'
|
|
21
|
+
// 'attached'
|
|
22
|
+
// 'collection-controlller'
|
|
23
|
+
// 'collection-attached'
|
|
24
|
+
// }
|
|
21
25
|
|
|
22
26
|
class BaseSharedState {
|
|
23
27
|
constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
|
|
@@ -33,8 +37,13 @@ class BaseSharedState {
|
|
|
33
37
|
this._client = client;
|
|
34
38
|
/** @private */
|
|
35
39
|
this._manager = manager;
|
|
36
|
-
|
|
40
|
+
/**
|
|
41
|
+
* true is the state has been detached or deleted
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
37
44
|
this._detached = false;
|
|
45
|
+
/* @private */
|
|
46
|
+
this._promiseStore = new PromiseStore(this.constructor.name);
|
|
38
47
|
|
|
39
48
|
try {
|
|
40
49
|
/** @private */
|
|
@@ -56,13 +65,13 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
56
65
|
// add listener for state updates
|
|
57
66
|
this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
58
67
|
const updated = await this._commit(updates, context, true, true);
|
|
59
|
-
|
|
68
|
+
this._promiseStore.resolve(reqId, updated);
|
|
60
69
|
});
|
|
61
70
|
|
|
62
71
|
// retrieve values but do not propagate to subscriptions
|
|
63
72
|
this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
64
73
|
const updated = await this._commit(updates, context, false, true);
|
|
65
|
-
|
|
74
|
+
this._promiseStore.resolve(reqId, updated);
|
|
66
75
|
});
|
|
67
76
|
|
|
68
77
|
this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
|
|
@@ -101,7 +110,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
101
110
|
for (let callback of this._onDetachCallbacks) {
|
|
102
111
|
try {
|
|
103
112
|
await callback();
|
|
104
|
-
} catch(err) {
|
|
113
|
+
} catch (err) {
|
|
105
114
|
console.error(err.message);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
@@ -112,7 +121,9 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
this.
|
|
124
|
+
this._onDetachCallbacks.clear();
|
|
125
|
+
this._onDeleteCallbacks.clear();
|
|
126
|
+
this._promiseStore.flush();
|
|
116
127
|
});
|
|
117
128
|
|
|
118
129
|
|
|
@@ -132,12 +143,14 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
132
143
|
await callback();
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
this.
|
|
136
|
-
|
|
146
|
+
this._onDetachCallbacks.clear();
|
|
147
|
+
this._onDeleteCallbacks.clear();
|
|
148
|
+
this._promiseStore.resolve(reqId, this);
|
|
149
|
+
this._promiseStore.flush();
|
|
137
150
|
});
|
|
138
151
|
|
|
139
152
|
this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
|
|
140
|
-
|
|
153
|
+
this._promiseStore.reject(reqId, msg);
|
|
141
154
|
});
|
|
142
155
|
|
|
143
156
|
} else {
|
|
@@ -152,13 +165,18 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
152
165
|
await callback();
|
|
153
166
|
}
|
|
154
167
|
|
|
155
|
-
this.
|
|
156
|
-
|
|
168
|
+
this._onDetachCallbacks.clear();
|
|
169
|
+
this._onDeleteCallbacks.clear();
|
|
170
|
+
this._promiseStore.resolve(reqId, this);
|
|
171
|
+
this._promiseStore.flush();
|
|
157
172
|
});
|
|
158
173
|
|
|
159
174
|
// the state does not exists anymore in the server (should not happen)
|
|
160
175
|
this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
|
|
161
|
-
|
|
176
|
+
this._onDetachCallbacks.clear();
|
|
177
|
+
this._onDeleteCallbacks.clear();
|
|
178
|
+
this._promiseStore.reject(reqId, msg);
|
|
179
|
+
this._promiseStore.flush();
|
|
162
180
|
});
|
|
163
181
|
}
|
|
164
182
|
}
|
|
@@ -200,13 +218,6 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
200
218
|
return this._isOwner;
|
|
201
219
|
}
|
|
202
220
|
|
|
203
|
-
/** @private */
|
|
204
|
-
_clearDetach() {
|
|
205
|
-
this._onDetachCallbacks.clear();
|
|
206
|
-
this._onDeleteCallbacks.clear();
|
|
207
|
-
this._detached = true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
221
|
/** @private */
|
|
211
222
|
_clearTransport() {
|
|
212
223
|
// remove listeners
|
|
@@ -325,7 +336,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
325
336
|
for (let name in updates) {
|
|
326
337
|
// try to coerce value early, so that eventual errors are triggered early
|
|
327
338
|
// on the node requesting the update
|
|
328
|
-
|
|
339
|
+
this._parameters.coerceValue(name, updates[name]);
|
|
329
340
|
|
|
330
341
|
// @note: general idea...
|
|
331
342
|
// if immediate=true
|
|
@@ -358,7 +369,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
358
369
|
|
|
359
370
|
// go through server-side normal behavior
|
|
360
371
|
return new Promise((resolve, reject) => {
|
|
361
|
-
const reqId =
|
|
372
|
+
const reqId = this._promiseStore.add(resolve, reject, 'update-request');
|
|
362
373
|
this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
|
|
363
374
|
});
|
|
364
375
|
}
|
|
@@ -426,12 +437,12 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
426
437
|
}
|
|
427
438
|
|
|
428
439
|
/**
|
|
429
|
-
*
|
|
440
|
+
* Definition of schema from which the state has been created.
|
|
430
441
|
*
|
|
431
|
-
* @param {string} [name=null] - If given, returns only the definition
|
|
432
|
-
* to the given param name.
|
|
442
|
+
* @param {string} [name=null] - If given, returns only the definition
|
|
443
|
+
* corresponding to the given param name.
|
|
433
444
|
* @throws Throws if `name` does not correspond to an existing field
|
|
434
|
-
* of the
|
|
445
|
+
* of the schema.
|
|
435
446
|
* @return {object}
|
|
436
447
|
* @example
|
|
437
448
|
* const schema = state.getSchema();
|
|
@@ -477,16 +488,17 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
477
488
|
throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
|
|
478
489
|
}
|
|
479
490
|
|
|
491
|
+
this._detached = true; // mark detached early
|
|
480
492
|
this._onUpdateCallbacks.clear();
|
|
481
493
|
|
|
482
494
|
if (this._isOwner) {
|
|
483
495
|
return new Promise((resolve, reject) => {
|
|
484
|
-
const reqId =
|
|
496
|
+
const reqId = this._promiseStore.add(resolve, reject, 'delete-request');
|
|
485
497
|
this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
|
|
486
498
|
});
|
|
487
499
|
} else {
|
|
488
500
|
return new Promise((resolve, reject) => {
|
|
489
|
-
const reqId =
|
|
501
|
+
const reqId = this._promiseStore.add(resolve, reject, 'detach-request');
|
|
490
502
|
this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
|
|
491
503
|
});
|
|
492
504
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
kCreateCollectionController,
|
|
3
|
+
kAttachInCollection,
|
|
4
|
+
} from './BaseStateManager.js';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* @private
|
|
3
8
|
*/
|
|
4
9
|
class BaseSharedStateCollection {
|
|
5
|
-
constructor(stateManager, schemaName) {
|
|
10
|
+
constructor(stateManager, schemaName, options = {}) {
|
|
6
11
|
this._stateManager = stateManager;
|
|
7
12
|
this._schemaName = schemaName;
|
|
13
|
+
this._options = Object.assign({ excludeLocal: false }, options);
|
|
14
|
+
|
|
15
|
+
this._controller = null;
|
|
8
16
|
this._states = [];
|
|
17
|
+
|
|
9
18
|
this._onUpdateCallbacks = new Set();
|
|
10
19
|
this._onAttachCallbacks = new Set();
|
|
11
20
|
this._onDetachCallbacks = new Set();
|
|
@@ -13,9 +22,15 @@ class BaseSharedStateCollection {
|
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
async _init() {
|
|
16
|
-
this.
|
|
17
|
-
|
|
25
|
+
this._controller = await this._stateManager[kCreateCollectionController](this._schemaName);
|
|
26
|
+
this._controller.onUpdate(async (updates, context) => {
|
|
27
|
+
for (let state of this._states) {
|
|
28
|
+
await state._commit(updates, context, true, false);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
18
31
|
|
|
32
|
+
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId) => {
|
|
33
|
+
const state = await this._stateManager[kAttachInCollection](schemaName, stateId);
|
|
19
34
|
this._states.push(state);
|
|
20
35
|
|
|
21
36
|
state.onDetach(() => {
|
|
@@ -32,11 +47,11 @@ class BaseSharedStateCollection {
|
|
|
32
47
|
});
|
|
33
48
|
|
|
34
49
|
this._onAttachCallbacks.forEach(callback => callback(state));
|
|
35
|
-
});
|
|
50
|
+
}, this._options);
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
/**
|
|
39
|
-
* Size of the collection
|
|
54
|
+
* Size of the collection, alias `size`
|
|
40
55
|
* @type {number}
|
|
41
56
|
*/
|
|
42
57
|
get length() {
|
|
@@ -44,17 +59,46 @@ class BaseSharedStateCollection {
|
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
/**
|
|
47
|
-
*
|
|
62
|
+
* Size of the collection, , alias `length`
|
|
48
63
|
* @type {number}
|
|
49
64
|
*/
|
|
50
|
-
|
|
51
|
-
this.
|
|
65
|
+
get size() {
|
|
66
|
+
return this._states.length;
|
|
67
|
+
}
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Name of the schema from which the collection has been created.
|
|
71
|
+
* @type {String}
|
|
72
|
+
* @readonly
|
|
73
|
+
*/
|
|
74
|
+
get schemaName() {
|
|
75
|
+
return this._controller.schemaName;
|
|
76
|
+
}
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Definition of schema from which the collection has been created.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} [name=null] - If given, returns only the definition
|
|
82
|
+
* corresponding to the given param name.
|
|
83
|
+
* @throws Throws if `name` does not correspond to an existing field
|
|
84
|
+
* of the schema.
|
|
85
|
+
* @return {object}
|
|
86
|
+
* @example
|
|
87
|
+
* const schema = collection.getSchema();
|
|
88
|
+
*/
|
|
89
|
+
getSchema(name = null) {
|
|
90
|
+
return this._controller.getSchema(name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the default values as declared in the schema.
|
|
95
|
+
*
|
|
96
|
+
* @return {object}
|
|
97
|
+
* @example
|
|
98
|
+
* const defaults = state.getDefaults();
|
|
99
|
+
*/
|
|
100
|
+
getDefaults() {
|
|
101
|
+
return this._controller.getDefaults();
|
|
58
102
|
}
|
|
59
103
|
|
|
60
104
|
/**
|
|
@@ -83,8 +127,7 @@ class BaseSharedStateCollection {
|
|
|
83
127
|
* current call and will be passed as third argument to all update listeners.
|
|
84
128
|
*/
|
|
85
129
|
async set(updates, context = null) {
|
|
86
|
-
|
|
87
|
-
return Promise.all(promises);
|
|
130
|
+
return await this._controller.set(updates, context);
|
|
88
131
|
}
|
|
89
132
|
|
|
90
133
|
/**
|
|
@@ -145,6 +188,22 @@ class BaseSharedStateCollection {
|
|
|
145
188
|
return () => this._onDetachCallbacks.delete(callback);
|
|
146
189
|
}
|
|
147
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Detach from the collection, i.e. detach all underlying shared states.
|
|
193
|
+
* @type {number}
|
|
194
|
+
*/
|
|
195
|
+
async detach() {
|
|
196
|
+
this._unobserve();
|
|
197
|
+
this._onUpdateCallbacks.clear();
|
|
198
|
+
|
|
199
|
+
await this._controller.delete();
|
|
200
|
+
|
|
201
|
+
const promises = this._states.map(state => state.detach());
|
|
202
|
+
await Promise.all(promises);
|
|
203
|
+
|
|
204
|
+
this._onDetachCallbacks.clear();
|
|
205
|
+
}
|
|
206
|
+
|
|
148
207
|
/**
|
|
149
208
|
* Execute the given function once for each states of the collection (see `Array.forEach`).
|
|
150
209
|
*
|
|
@@ -212,8 +271,8 @@ class BaseSharedStateCollection {
|
|
|
212
271
|
} else {
|
|
213
272
|
return { value: this._states[index++], done: false };
|
|
214
273
|
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
274
|
+
},
|
|
275
|
+
};
|
|
217
276
|
}
|
|
218
277
|
}
|
|
219
278
|
|