teamplay 0.3.35 → 0.4.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/orm/Doc.js +36 -99
- package/orm/Query.js +21 -89
- package/orm/SubscriptionState.js +138 -0
- package/package.json +13 -9
package/orm/Doc.js
CHANGED
|
@@ -3,21 +3,27 @@ import { set as _set, del as _del } from './dataTree.js'
|
|
|
3
3
|
import { SEGMENTS } from './Signal.js'
|
|
4
4
|
import { getConnection, fetchOnly } from './connection.js'
|
|
5
5
|
import FinalizationRegistry from '../utils/MockFinalizationRegistry.js'
|
|
6
|
+
import SubscriptionState from './SubscriptionState.js'
|
|
6
7
|
|
|
7
8
|
const ERROR_ON_EXCESSIVE_UNSUBSCRIBES = false
|
|
8
9
|
|
|
9
10
|
class Doc {
|
|
10
|
-
subscribing
|
|
11
|
-
unsubscribing
|
|
12
|
-
subscribed
|
|
13
11
|
initialized
|
|
14
12
|
|
|
15
13
|
constructor (collection, docId) {
|
|
16
14
|
this.collection = collection
|
|
17
15
|
this.docId = docId
|
|
16
|
+
this.lifecycle = new SubscriptionState({
|
|
17
|
+
onSubscribe: () => this._subscribe(),
|
|
18
|
+
onUnsubscribe: () => this._unsubscribe()
|
|
19
|
+
})
|
|
18
20
|
this.init()
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
get subscribed () {
|
|
24
|
+
return this.lifecycle.subscribed
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
init () {
|
|
22
28
|
if (this.initialized) return
|
|
23
29
|
this.initialized = true
|
|
@@ -25,47 +31,16 @@ class Doc {
|
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
async subscribe () {
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
if (this.unsubscribing) {
|
|
32
|
-
try {
|
|
33
|
-
await this.unsubscribing
|
|
34
|
-
} catch (err) {
|
|
35
|
-
// if error happened during unsubscribing, it means that we are still subscribed
|
|
36
|
-
// so we don't need to do anything
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (this.subscribing) {
|
|
41
|
-
try {
|
|
42
|
-
await this.subscribing
|
|
43
|
-
// if we are already subscribing from the previous time, delegate logic to that
|
|
44
|
-
// and if it finished successfully, we are done.
|
|
45
|
-
return
|
|
46
|
-
} catch (err) {
|
|
47
|
-
// if error happened during subscribing, we'll just try subscribing again
|
|
48
|
-
// so we just ignore the error and proceed with subscribing
|
|
49
|
-
this.subscribed = true
|
|
50
|
-
}
|
|
51
|
-
}
|
|
34
|
+
await this.lifecycle.subscribe()
|
|
35
|
+
this.init()
|
|
36
|
+
}
|
|
52
37
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.init()
|
|
60
|
-
} catch (err) {
|
|
61
|
-
console.log('subscription error', [this.collection, this.docId], err)
|
|
62
|
-
this.subscribed = undefined
|
|
63
|
-
throw err
|
|
64
|
-
} finally {
|
|
65
|
-
this.subscribing = undefined
|
|
66
|
-
}
|
|
67
|
-
})()
|
|
68
|
-
await this.subscribing
|
|
38
|
+
async unsubscribe () {
|
|
39
|
+
await this.lifecycle.unsubscribe()
|
|
40
|
+
if (!this.subscribed) {
|
|
41
|
+
this.initialized = undefined
|
|
42
|
+
this._removeData()
|
|
43
|
+
}
|
|
69
44
|
}
|
|
70
45
|
|
|
71
46
|
async _subscribe () {
|
|
@@ -79,66 +54,26 @@ class Doc {
|
|
|
79
54
|
})
|
|
80
55
|
}
|
|
81
56
|
|
|
82
|
-
async unsubscribe () {
|
|
83
|
-
if (!this.subscribed) {
|
|
84
|
-
throw Error('trying to unsubscribe while not subscribed. Doc: ' + [this.collection, this.docId])
|
|
85
|
-
}
|
|
86
|
-
this.subscribed = undefined
|
|
87
|
-
// if we are still handling the subscription, just wait for it to finish and then unsubscribe
|
|
88
|
-
if (this.subscribing) {
|
|
89
|
-
try {
|
|
90
|
-
await this.subscribing
|
|
91
|
-
} catch (err) {
|
|
92
|
-
// if error happened during subscribing, it means that we are still unsubscribed
|
|
93
|
-
// so we don't need to do anything
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// if we are already unsubscribing from the previous time, delegate logic to that
|
|
98
|
-
if (this.unsubscribing) {
|
|
99
|
-
try {
|
|
100
|
-
await this.unsubscribing
|
|
101
|
-
return
|
|
102
|
-
} catch (err) {
|
|
103
|
-
// if error happened during unsubscribing, we'll just try unsubscribing again
|
|
104
|
-
this.subscribed = undefined
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (this.subscribed) return // cancel if we initiated subscribe while waiting
|
|
109
|
-
|
|
110
|
-
this.unsubscribing = (async () => {
|
|
111
|
-
try {
|
|
112
|
-
await this._unsubscribe()
|
|
113
|
-
this.initialized = undefined
|
|
114
|
-
this._removeData()
|
|
115
|
-
} catch (err) {
|
|
116
|
-
console.log('error unsubscribing', [this.collection, this.docId], err)
|
|
117
|
-
this.subscribed = true
|
|
118
|
-
throw err
|
|
119
|
-
} finally {
|
|
120
|
-
this.unsubscribing = undefined
|
|
121
|
-
}
|
|
122
|
-
})()
|
|
123
|
-
await this.unsubscribing
|
|
124
|
-
}
|
|
125
|
-
|
|
126
57
|
async _unsubscribe () {
|
|
127
58
|
const doc = getConnection().get(this.collection, this.docId)
|
|
128
59
|
await new Promise((resolve, reject) => {
|
|
129
|
-
|
|
60
|
+
// First unsubscribe cleanly, then destroy to remove from connection.collections.
|
|
61
|
+
// We can't call destroy() directly because it has a race condition: if connection.get()
|
|
62
|
+
// is called before destroy completes (e.g. rapid unsub/resub), it resets _wantsDestroy
|
|
63
|
+
// creating a corrupted state ("Cannot read properties of null (reading 'callback')").
|
|
64
|
+
// By unsubscribing first and destroying in the callback, the doc is in a clean state.
|
|
65
|
+
doc.unsubscribe(err => {
|
|
130
66
|
if (err) return reject(err)
|
|
131
|
-
|
|
67
|
+
doc.destroy(err => {
|
|
68
|
+
if (err) return reject(err)
|
|
69
|
+
resolve()
|
|
70
|
+
})
|
|
132
71
|
})
|
|
133
72
|
})
|
|
134
73
|
}
|
|
135
74
|
|
|
136
75
|
_initData () {
|
|
137
76
|
const doc = getConnection().get(this.collection, this.docId)
|
|
138
|
-
// TODO: JSON does not have `undefined`, so we'll be receiving `null`.
|
|
139
|
-
// Handle this by converting all `null` to `undefined` in the doc's data tree.
|
|
140
|
-
// To do this we'll probably need to in the `op` event update the data tree
|
|
141
|
-
// and have a clone of the doc in our local data tree.
|
|
142
77
|
this._refData()
|
|
143
78
|
doc.on('load', () => this._refData())
|
|
144
79
|
doc.on('create', () => this._refData())
|
|
@@ -186,11 +121,12 @@ class DocSubscriptions {
|
|
|
186
121
|
let count = this.subCount.get(hash) || 0
|
|
187
122
|
count += 1
|
|
188
123
|
this.subCount.set(hash, count)
|
|
189
|
-
if (count > 1) return this.docs.get(hash).
|
|
124
|
+
if (count > 1) return this.docs.get(hash)._subscribing
|
|
190
125
|
|
|
191
126
|
this.init($doc)
|
|
192
127
|
const doc = this.docs.get(hash)
|
|
193
|
-
|
|
128
|
+
doc._subscribing = doc.subscribe().then(() => { doc._subscribing = undefined })
|
|
129
|
+
return doc._subscribing
|
|
194
130
|
}
|
|
195
131
|
|
|
196
132
|
async unsubscribe ($doc) {
|
|
@@ -207,17 +143,18 @@ class DocSubscriptions {
|
|
|
207
143
|
return
|
|
208
144
|
}
|
|
209
145
|
this.fr.unregister($doc)
|
|
210
|
-
this.destroy(segments)
|
|
146
|
+
await this.destroy(segments)
|
|
211
147
|
}
|
|
212
148
|
|
|
213
149
|
async destroy (segments) {
|
|
214
150
|
const hash = hashDoc(segments)
|
|
215
151
|
const doc = this.docs.get(hash)
|
|
216
152
|
if (!doc) return
|
|
217
|
-
// Wait until after unsubscribe to delete subCount and docs
|
|
218
|
-
if (doc.subscribed) await doc.unsubscribe()
|
|
219
|
-
if (doc.subscribed) return // Subscribed again while unsubscribing
|
|
220
153
|
this.subCount.delete(hash)
|
|
154
|
+
// Always call unsubscribe() - if doc is in SUBSCRIBING state, the state machine
|
|
155
|
+
// will queue a pending unsubscribe to execute after subscribe completes
|
|
156
|
+
await doc.unsubscribe()
|
|
157
|
+
if (doc.subscribed) return // Subscribed again while unsubscribing
|
|
221
158
|
this.docs.delete(hash)
|
|
222
159
|
}
|
|
223
160
|
}
|
package/orm/Query.js
CHANGED
|
@@ -4,6 +4,7 @@ import getSignal from './getSignal.js'
|
|
|
4
4
|
import { getConnection, fetchOnly } from './connection.js'
|
|
5
5
|
import { docSubscriptions } from './Doc.js'
|
|
6
6
|
import FinalizationRegistry from '../utils/MockFinalizationRegistry.js'
|
|
7
|
+
import SubscriptionState from './SubscriptionState.js'
|
|
7
8
|
|
|
8
9
|
const ERROR_ON_EXCESSIVE_UNSUBSCRIBES = false
|
|
9
10
|
export const COLLECTION_NAME = Symbol('query collection name')
|
|
@@ -13,9 +14,6 @@ export const IS_QUERY = Symbol('is query signal')
|
|
|
13
14
|
export const QUERIES = '$queries'
|
|
14
15
|
|
|
15
16
|
export class Query {
|
|
16
|
-
subscribing
|
|
17
|
-
unsubscribing
|
|
18
|
-
subscribed
|
|
19
17
|
initialized
|
|
20
18
|
shareQuery
|
|
21
19
|
|
|
@@ -24,6 +22,14 @@ export class Query {
|
|
|
24
22
|
this.params = params
|
|
25
23
|
this.hash = hashQuery(this.collectionName, this.params)
|
|
26
24
|
this.docSignals = new Set()
|
|
25
|
+
this.lifecycle = new SubscriptionState({
|
|
26
|
+
onSubscribe: () => this._subscribe(),
|
|
27
|
+
onUnsubscribe: () => this._unsubscribe()
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get subscribed () {
|
|
32
|
+
return this.lifecycle.subscribed
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
init () {
|
|
@@ -33,47 +39,16 @@ export class Query {
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
async subscribe () {
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
if (this.unsubscribing) {
|
|
40
|
-
try {
|
|
41
|
-
await this.unsubscribing
|
|
42
|
-
} catch (err) {
|
|
43
|
-
// if error happened during unsubscribing, it means that we are still subscribed
|
|
44
|
-
// so we don't need to do anything
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (this.subscribing) {
|
|
49
|
-
try {
|
|
50
|
-
await this.subscribing
|
|
51
|
-
// if we are already subscribing from the previous time, delegate logic to that
|
|
52
|
-
// and if it finished successfully, we are done.
|
|
53
|
-
return
|
|
54
|
-
} catch (err) {
|
|
55
|
-
// if error happened during subscribing, we'll just try subscribing again
|
|
56
|
-
// so we just ignore the error and proceed with subscribing
|
|
57
|
-
this.subscribed = true
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!this.subscribed) return // cancel if we initiated unsubscribe while waiting
|
|
42
|
+
await this.lifecycle.subscribe()
|
|
43
|
+
this.init()
|
|
44
|
+
}
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log('subscription error', [this.collectionName, this.params], err)
|
|
70
|
-
this.subscribed = undefined
|
|
71
|
-
throw err
|
|
72
|
-
} finally {
|
|
73
|
-
this.subscribing = undefined
|
|
74
|
-
}
|
|
75
|
-
})()
|
|
76
|
-
await this.subscribing
|
|
46
|
+
async unsubscribe () {
|
|
47
|
+
await this.lifecycle.unsubscribe()
|
|
48
|
+
if (!this.subscribed) {
|
|
49
|
+
this.initialized = undefined
|
|
50
|
+
this._removeData()
|
|
51
|
+
}
|
|
77
52
|
}
|
|
78
53
|
|
|
79
54
|
async _subscribe () {
|
|
@@ -86,50 +61,6 @@ export class Query {
|
|
|
86
61
|
})
|
|
87
62
|
}
|
|
88
63
|
|
|
89
|
-
async unsubscribe () {
|
|
90
|
-
if (!this.subscribed) {
|
|
91
|
-
throw Error('trying to unsubscribe while not subscribed. Query: ' + [this.collectionName, this.params])
|
|
92
|
-
}
|
|
93
|
-
this.subscribed = undefined
|
|
94
|
-
// if we are still handling the subscription, just wait for it to finish and then unsubscribe
|
|
95
|
-
if (this.subscribing) {
|
|
96
|
-
try {
|
|
97
|
-
await this.subscribing
|
|
98
|
-
} catch (err) {
|
|
99
|
-
// if error happened during subscribing, it means that we are still unsubscribed
|
|
100
|
-
// so we don't need to do anything
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// if we are already unsubscribing from the previous time, delegate logic to that
|
|
105
|
-
if (this.unsubscribing) {
|
|
106
|
-
try {
|
|
107
|
-
await this.unsubscribing
|
|
108
|
-
return
|
|
109
|
-
} catch (err) {
|
|
110
|
-
// if error happened during unsubscribing, we'll just try unsubscribing again
|
|
111
|
-
this.subscribed = undefined
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (this.subscribed) return // cancel if we initiated subscribe while waiting
|
|
116
|
-
|
|
117
|
-
this.unsubscribing = (async () => {
|
|
118
|
-
try {
|
|
119
|
-
await this._unsubscribe()
|
|
120
|
-
this.initialized = undefined
|
|
121
|
-
this._removeData()
|
|
122
|
-
} catch (err) {
|
|
123
|
-
console.log('error unsubscribing', [this.collectionName, this.params], err)
|
|
124
|
-
this.subscribed = true
|
|
125
|
-
throw err
|
|
126
|
-
} finally {
|
|
127
|
-
this.unsubscribing = undefined
|
|
128
|
-
}
|
|
129
|
-
})()
|
|
130
|
-
await this.unsubscribing
|
|
131
|
-
}
|
|
132
|
-
|
|
133
64
|
async _unsubscribe () {
|
|
134
65
|
if (!this.shareQuery) throw Error('this.shareQuery is not defined. This should never happen')
|
|
135
66
|
await new Promise((resolve, reject) => {
|
|
@@ -220,7 +151,7 @@ export class QuerySubscriptions {
|
|
|
220
151
|
let count = this.subCount.get(hash) || 0
|
|
221
152
|
count += 1
|
|
222
153
|
this.subCount.set(hash, count)
|
|
223
|
-
if (count > 1) return this.queries.get(hash).
|
|
154
|
+
if (count > 1) return this.queries.get(hash)._subscribing
|
|
224
155
|
|
|
225
156
|
this.fr.register($query, { collectionName, params }, $query)
|
|
226
157
|
|
|
@@ -229,7 +160,8 @@ export class QuerySubscriptions {
|
|
|
229
160
|
query = new this.QueryClass(collectionName, params)
|
|
230
161
|
this.queries.set(hash, query)
|
|
231
162
|
}
|
|
232
|
-
|
|
163
|
+
query._subscribing = query.subscribe().then(() => { query._subscribing = undefined })
|
|
164
|
+
return query._subscribing
|
|
233
165
|
}
|
|
234
166
|
|
|
235
167
|
async unsubscribe ($query) {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// State machine for managing subscribe/unsubscribe lifecycle.
|
|
2
|
+
//
|
|
3
|
+
// States: IDLE, SUBSCRIBING, SUBSCRIBED, UNSUBSCRIBING
|
|
4
|
+
//
|
|
5
|
+
// Valid transitions:
|
|
6
|
+
// IDLE -> SUBSCRIBING (subscribe called)
|
|
7
|
+
// SUBSCRIBING -> SUBSCRIBED (subscribe succeeded)
|
|
8
|
+
// SUBSCRIBING -> IDLE (subscribe failed)
|
|
9
|
+
// SUBSCRIBED -> UNSUBSCRIBING (unsubscribe called)
|
|
10
|
+
// UNSUBSCRIBING -> IDLE (unsubscribe succeeded)
|
|
11
|
+
// UNSUBSCRIBING -> SUBSCRIBED (unsubscribe failed, rollback)
|
|
12
|
+
//
|
|
13
|
+
// Rapid sub/unsub handling:
|
|
14
|
+
// If subscribe() is called during UNSUBSCRIBING, we queue a resubscribe.
|
|
15
|
+
// If unsubscribe() is called during SUBSCRIBING, we queue an unsubscribe.
|
|
16
|
+
// Only the latest intent matters - intermediate intents are collapsed.
|
|
17
|
+
|
|
18
|
+
export const STATE = {
|
|
19
|
+
IDLE: 'IDLE',
|
|
20
|
+
SUBSCRIBING: 'SUBSCRIBING',
|
|
21
|
+
SUBSCRIBED: 'SUBSCRIBED',
|
|
22
|
+
UNSUBSCRIBING: 'UNSUBSCRIBING'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default class SubscriptionState {
|
|
26
|
+
#state = STATE.IDLE
|
|
27
|
+
#pendingAction = undefined // 'subscribe' | 'unsubscribe' | undefined
|
|
28
|
+
#activePromise = undefined
|
|
29
|
+
#onSubscribe // async () => void
|
|
30
|
+
#onUnsubscribe // async () => void
|
|
31
|
+
|
|
32
|
+
constructor ({ onSubscribe, onUnsubscribe }) {
|
|
33
|
+
this.#onSubscribe = onSubscribe
|
|
34
|
+
this.#onUnsubscribe = onUnsubscribe
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get state () {
|
|
38
|
+
return this.#state
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get subscribed () {
|
|
42
|
+
return this.#state === STATE.SUBSCRIBED
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async subscribe () {
|
|
46
|
+
// Already subscribed - nothing to do
|
|
47
|
+
if (this.#state === STATE.SUBSCRIBED) return
|
|
48
|
+
|
|
49
|
+
// Already subscribing - if there was a pending unsubscribe, cancel it
|
|
50
|
+
if (this.#state === STATE.SUBSCRIBING) {
|
|
51
|
+
this.#pendingAction = undefined
|
|
52
|
+
return this.#activePromise
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Currently unsubscribing - queue a resubscribe after it finishes
|
|
56
|
+
if (this.#state === STATE.UNSUBSCRIBING) {
|
|
57
|
+
this.#pendingAction = 'subscribe'
|
|
58
|
+
return this.#activePromise
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// IDLE - start subscribing
|
|
62
|
+
return this.#doSubscribe()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async unsubscribe () {
|
|
66
|
+
// Already idle - nothing to do
|
|
67
|
+
if (this.#state === STATE.IDLE) return
|
|
68
|
+
|
|
69
|
+
// Already unsubscribing - if there was a pending subscribe, cancel it
|
|
70
|
+
if (this.#state === STATE.UNSUBSCRIBING) {
|
|
71
|
+
this.#pendingAction = undefined
|
|
72
|
+
return this.#activePromise
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Currently subscribing - queue an unsubscribe after it finishes
|
|
76
|
+
if (this.#state === STATE.SUBSCRIBING) {
|
|
77
|
+
this.#pendingAction = 'unsubscribe'
|
|
78
|
+
return this.#activePromise
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// SUBSCRIBED - start unsubscribing
|
|
82
|
+
return this.#doUnsubscribe()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async #doSubscribe () {
|
|
86
|
+
this.#state = STATE.SUBSCRIBING
|
|
87
|
+
this.#pendingAction = undefined
|
|
88
|
+
|
|
89
|
+
this.#activePromise = (async () => {
|
|
90
|
+
try {
|
|
91
|
+
await this.#onSubscribe()
|
|
92
|
+
this.#state = STATE.SUBSCRIBED
|
|
93
|
+
} catch (err) {
|
|
94
|
+
this.#state = STATE.IDLE
|
|
95
|
+
this.#pendingAction = undefined
|
|
96
|
+
throw err
|
|
97
|
+
} finally {
|
|
98
|
+
this.#activePromise = undefined
|
|
99
|
+
}
|
|
100
|
+
await this.#processPending()
|
|
101
|
+
})()
|
|
102
|
+
|
|
103
|
+
return this.#activePromise
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async #doUnsubscribe () {
|
|
107
|
+
this.#state = STATE.UNSUBSCRIBING
|
|
108
|
+
this.#pendingAction = undefined
|
|
109
|
+
|
|
110
|
+
this.#activePromise = (async () => {
|
|
111
|
+
try {
|
|
112
|
+
await this.#onUnsubscribe()
|
|
113
|
+
this.#state = STATE.IDLE
|
|
114
|
+
} catch (err) {
|
|
115
|
+
this.#state = STATE.SUBSCRIBED
|
|
116
|
+
this.#pendingAction = undefined
|
|
117
|
+
throw err
|
|
118
|
+
} finally {
|
|
119
|
+
this.#activePromise = undefined
|
|
120
|
+
}
|
|
121
|
+
await this.#processPending()
|
|
122
|
+
})()
|
|
123
|
+
|
|
124
|
+
return this.#activePromise
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async #processPending () {
|
|
128
|
+
const action = this.#pendingAction
|
|
129
|
+
this.#pendingAction = undefined
|
|
130
|
+
|
|
131
|
+
if (action === 'subscribe' && this.#state === STATE.IDLE) {
|
|
132
|
+
return this.#doSubscribe()
|
|
133
|
+
}
|
|
134
|
+
if (action === 'unsubscribe' && this.#state === STATE.SUBSCRIBED) {
|
|
135
|
+
return this.#doUnsubscribe()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-alpha.0",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -23,17 +23,20 @@
|
|
|
23
23
|
"test": "npm run test-server && npm run test-client",
|
|
24
24
|
"test-server": "NODE_OPTIONS=\"--expose-gc\" mocha 'test/[!_]*.js'",
|
|
25
25
|
"test-server-only": "NODE_OPTIONS=\"--expose-gc\" mocha --grep '@only' 'test/[!_]*.js'",
|
|
26
|
-
"test-client": "NODE_OPTIONS=\"$NODE_OPTIONS --expose-gc --experimental-vm-modules\" jest"
|
|
26
|
+
"test-client": "NODE_OPTIONS=\"$NODE_OPTIONS --expose-gc --experimental-vm-modules\" jest",
|
|
27
|
+
"coverage-server": "NODE_OPTIONS=\"--expose-gc\" c8 --include 'orm/**' --include 'react/**' --include 'utils/**' mocha 'test/[!_]*.js'",
|
|
28
|
+
"coverage-client": "NODE_OPTIONS=\"$NODE_OPTIONS --expose-gc --experimental-vm-modules\" jest --coverage --coverageDirectory=coverage-client",
|
|
29
|
+
"coverage": "npm run coverage-server && npm run coverage-client"
|
|
27
30
|
},
|
|
28
31
|
"dependencies": {
|
|
29
32
|
"@nx-js/observer-util": "^4.1.3",
|
|
30
33
|
"@startupjs/sharedb-mingo-memory": "^4.0.0-2",
|
|
31
|
-
"@teamplay/backend": "^0.
|
|
32
|
-
"@teamplay/cache": "^0.
|
|
33
|
-
"@teamplay/channel": "^0.
|
|
34
|
-
"@teamplay/debug": "^0.
|
|
35
|
-
"@teamplay/schema": "^0.
|
|
36
|
-
"@teamplay/utils": "^0.
|
|
34
|
+
"@teamplay/backend": "^0.4.0-alpha.0",
|
|
35
|
+
"@teamplay/cache": "^0.4.0-alpha.0",
|
|
36
|
+
"@teamplay/channel": "^0.4.0-alpha.0",
|
|
37
|
+
"@teamplay/debug": "^0.4.0-alpha.0",
|
|
38
|
+
"@teamplay/schema": "^0.4.0-alpha.0",
|
|
39
|
+
"@teamplay/utils": "^0.4.0-alpha.0",
|
|
37
40
|
"diff-match-patch": "^1.0.5",
|
|
38
41
|
"events": "^3.3.0",
|
|
39
42
|
"json0-ot-diff": "^1.1.2",
|
|
@@ -45,6 +48,7 @@
|
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@jest/globals": "^29.7.0",
|
|
47
50
|
"@testing-library/react": "^15.0.7",
|
|
51
|
+
"c8": "^10.1.3",
|
|
48
52
|
"jest": "^29.7.0",
|
|
49
53
|
"jest-environment-jsdom": "^29.7.0",
|
|
50
54
|
"mocha": "^11.0.1",
|
|
@@ -74,5 +78,5 @@
|
|
|
74
78
|
]
|
|
75
79
|
},
|
|
76
80
|
"license": "MIT",
|
|
77
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "fb723714f5c425d763b1a7b45e75adb990a39707"
|
|
78
82
|
}
|