teamplay 0.4.0-alpha.81 → 0.4.0-alpha.83
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/index.js +9 -1
- package/orm/Compat/SignalCompat.js +27 -39
- package/orm/Doc.js +184 -47
- package/orm/Query.js +229 -48
- package/orm/Root.js +20 -1
- package/orm/SignalBase.js +13 -13
- package/orm/connection.js +17 -2
- package/orm/rootContext.js +13 -3
- package/package.json +2 -2
- package/server.js +2 -2
package/index.js
CHANGED
|
@@ -65,7 +65,15 @@ export {
|
|
|
65
65
|
useOnce,
|
|
66
66
|
useSyncEffect
|
|
67
67
|
} from './react/helpers.js'
|
|
68
|
-
export {
|
|
68
|
+
export {
|
|
69
|
+
connection,
|
|
70
|
+
setConnection,
|
|
71
|
+
getConnection,
|
|
72
|
+
getDefaultFetchOnly,
|
|
73
|
+
setDefaultFetchOnly,
|
|
74
|
+
publicOnly,
|
|
75
|
+
setPublicOnly
|
|
76
|
+
} from './orm/connection.js'
|
|
69
77
|
export { getSubscriptionGcDelay, setSubscriptionGcDelay } from './orm/subscriptionGcDelay.js'
|
|
70
78
|
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
|
|
71
79
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isPublicDocumentSignal
|
|
10
10
|
} from '../SignalBase.js'
|
|
11
11
|
import { getRoot, ROOT, ROOT_ID, getRootSignal, GLOBAL_ROOT_ID, unregisterRootFinalizer } from '../Root.js'
|
|
12
|
-
import {
|
|
12
|
+
import { isPrivateMutationForbidden } from '../connection.js'
|
|
13
13
|
import { docSubscriptions } from '../Doc.js'
|
|
14
14
|
import { IS_QUERY, getQuerySignal, querySubscriptions } from '../Query.js'
|
|
15
15
|
import { IS_AGGREGATION, aggregationSubscriptions, getAggregationSignal } from '../Aggregation.js'
|
|
@@ -123,15 +123,13 @@ class SignalCompat extends Signal {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
fetch (...items) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return subscribeSelf(this)
|
|
129
|
-
})
|
|
126
|
+
if (items.length > 0) return subscribeMany(items, 'subscribe', 'fetch')
|
|
127
|
+
return subscribeSelf(this, 'fetch')
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
unfetch (...items) {
|
|
133
|
-
if (items.length > 0) return subscribeMany(items, 'unsubscribe')
|
|
134
|
-
return unsubscribeSelf(this)
|
|
131
|
+
if (items.length > 0) return subscribeMany(items, 'unsubscribe', 'fetch')
|
|
132
|
+
return unsubscribeSelf(this, 'fetch')
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
getExtra () {
|
|
@@ -1108,7 +1106,7 @@ async function setReplaceOnSignal ($signal, value) {
|
|
|
1108
1106
|
mirrorRefMutationFromTarget(segments, value)
|
|
1109
1107
|
return result
|
|
1110
1108
|
}
|
|
1111
|
-
if (
|
|
1109
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1112
1110
|
const result = setReplacePrivateData(getOwningRootId($signal), segments, value)
|
|
1113
1111
|
mirrorRefMutationFromTarget(segments, value)
|
|
1114
1112
|
return result
|
|
@@ -1128,7 +1126,7 @@ async function incrementOnSignal ($signal, byNumber) {
|
|
|
1128
1126
|
await _incrementPublic(segments, byNumber)
|
|
1129
1127
|
return currentValue + byNumber
|
|
1130
1128
|
}
|
|
1131
|
-
if (
|
|
1129
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1132
1130
|
setReplacePrivateData(getOwningRootId($signal), segments, currentValue + byNumber)
|
|
1133
1131
|
return currentValue + byNumber
|
|
1134
1132
|
}
|
|
@@ -1161,7 +1159,7 @@ async function arrayPushOnSignal ($signal, value) {
|
|
|
1161
1159
|
const idFields = getIdFieldsForSegments(segments)
|
|
1162
1160
|
if (isIdFieldPath(segments, idFields)) return
|
|
1163
1161
|
if (isPublicCollection(segments[0])) return _arrayPushPublic(segments, value)
|
|
1164
|
-
if (
|
|
1162
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1165
1163
|
return arrayPushPrivateData(getOwningRootId($signal), segments, value)
|
|
1166
1164
|
}
|
|
1167
1165
|
|
|
@@ -1170,7 +1168,7 @@ async function arrayUnshiftOnSignal ($signal, value) {
|
|
|
1170
1168
|
const idFields = getIdFieldsForSegments(segments)
|
|
1171
1169
|
if (isIdFieldPath(segments, idFields)) return
|
|
1172
1170
|
if (isPublicCollection(segments[0])) return _arrayUnshiftPublic(segments, value)
|
|
1173
|
-
if (
|
|
1171
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1174
1172
|
return arrayUnshiftPrivateData(getOwningRootId($signal), segments, value)
|
|
1175
1173
|
}
|
|
1176
1174
|
|
|
@@ -1179,7 +1177,7 @@ async function arrayInsertOnSignal ($signal, index, values) {
|
|
|
1179
1177
|
const idFields = getIdFieldsForSegments(segments)
|
|
1180
1178
|
if (isIdFieldPath(segments, idFields)) return
|
|
1181
1179
|
if (isPublicCollection(segments[0])) return _arrayInsertPublic(segments, index, values)
|
|
1182
|
-
if (
|
|
1180
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1183
1181
|
return arrayInsertPrivateData(getOwningRootId($signal), segments, index, values)
|
|
1184
1182
|
}
|
|
1185
1183
|
|
|
@@ -1188,7 +1186,7 @@ async function arrayPopOnSignal ($signal) {
|
|
|
1188
1186
|
const idFields = getIdFieldsForSegments(segments)
|
|
1189
1187
|
if (isIdFieldPath(segments, idFields)) return
|
|
1190
1188
|
if (isPublicCollection(segments[0])) return _arrayPopPublic(segments)
|
|
1191
|
-
if (
|
|
1189
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1192
1190
|
return arrayPopPrivateData(getOwningRootId($signal), segments)
|
|
1193
1191
|
}
|
|
1194
1192
|
|
|
@@ -1197,7 +1195,7 @@ async function arrayShiftOnSignal ($signal) {
|
|
|
1197
1195
|
const idFields = getIdFieldsForSegments(segments)
|
|
1198
1196
|
if (isIdFieldPath(segments, idFields)) return
|
|
1199
1197
|
if (isPublicCollection(segments[0])) return _arrayShiftPublic(segments)
|
|
1200
|
-
if (
|
|
1198
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1201
1199
|
return arrayShiftPrivateData(getOwningRootId($signal), segments)
|
|
1202
1200
|
}
|
|
1203
1201
|
|
|
@@ -1206,7 +1204,7 @@ async function arrayRemoveOnSignal ($signal, index, howMany) {
|
|
|
1206
1204
|
const idFields = getIdFieldsForSegments(segments)
|
|
1207
1205
|
if (isIdFieldPath(segments, idFields)) return
|
|
1208
1206
|
if (isPublicCollection(segments[0])) return _arrayRemovePublic(segments, index, howMany)
|
|
1209
|
-
if (
|
|
1207
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1210
1208
|
return arrayRemovePrivateData(getOwningRootId($signal), segments, index, howMany)
|
|
1211
1209
|
}
|
|
1212
1210
|
|
|
@@ -1215,7 +1213,7 @@ async function arrayMoveOnSignal ($signal, from, to, howMany) {
|
|
|
1215
1213
|
const idFields = getIdFieldsForSegments(segments)
|
|
1216
1214
|
if (isIdFieldPath(segments, idFields)) return
|
|
1217
1215
|
if (isPublicCollection(segments[0])) return _arrayMovePublic(segments, from, to, howMany)
|
|
1218
|
-
if (
|
|
1216
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1219
1217
|
return arrayMovePrivateData(getOwningRootId($signal), segments, from, to, howMany)
|
|
1220
1218
|
}
|
|
1221
1219
|
|
|
@@ -1224,7 +1222,7 @@ async function stringInsertOnSignal ($signal, index, text) {
|
|
|
1224
1222
|
const idFields = getIdFieldsForSegments(segments)
|
|
1225
1223
|
if (isIdFieldPath(segments, idFields)) return
|
|
1226
1224
|
if (isPublicCollection(segments[0])) return _stringInsertPublic(segments, index, text)
|
|
1227
|
-
if (
|
|
1225
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1228
1226
|
return stringInsertPrivateData(getOwningRootId($signal), segments, index, text)
|
|
1229
1227
|
}
|
|
1230
1228
|
|
|
@@ -1233,7 +1231,7 @@ async function stringRemoveOnSignal ($signal, index, howMany) {
|
|
|
1233
1231
|
const idFields = getIdFieldsForSegments(segments)
|
|
1234
1232
|
if (isIdFieldPath(segments, idFields)) return
|
|
1235
1233
|
if (isPublicCollection(segments[0])) return _stringRemovePublic(segments, index, howMany)
|
|
1236
|
-
if (
|
|
1234
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1237
1235
|
return stringRemovePrivateData(getOwningRootId($signal), segments, index, howMany)
|
|
1238
1236
|
}
|
|
1239
1237
|
|
|
@@ -1297,17 +1295,7 @@ function withQueryScopeOptions (options, $root) {
|
|
|
1297
1295
|
return nextOptions
|
|
1298
1296
|
}
|
|
1299
1297
|
|
|
1300
|
-
function
|
|
1301
|
-
const prevFetchOnly = fetchOnly
|
|
1302
|
-
setFetchOnly(true)
|
|
1303
|
-
try {
|
|
1304
|
-
return fn()
|
|
1305
|
-
} finally {
|
|
1306
|
-
setFetchOnly(prevFetchOnly)
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function subscribeMany (items, action) {
|
|
1298
|
+
function subscribeMany (items, action, intent = 'subscribe') {
|
|
1311
1299
|
const targets = flattenItems(items)
|
|
1312
1300
|
const promises = []
|
|
1313
1301
|
for (const target of targets) {
|
|
@@ -1316,8 +1304,8 @@ function subscribeMany (items, action) {
|
|
|
1316
1304
|
throw Error(`Signal.${action}() accepts only Signal instances. Got: ${target}`)
|
|
1317
1305
|
}
|
|
1318
1306
|
const result = action === 'subscribe'
|
|
1319
|
-
? subscribeSelf(target)
|
|
1320
|
-
: unsubscribeSelf(target)
|
|
1307
|
+
? subscribeSelf(target, intent)
|
|
1308
|
+
: unsubscribeSelf(target, intent)
|
|
1321
1309
|
if (result?.then) promises.push(result)
|
|
1322
1310
|
}
|
|
1323
1311
|
if (promises.length) return Promise.all(promises)
|
|
@@ -1335,20 +1323,20 @@ function flattenItems (items, result = []) {
|
|
|
1335
1323
|
return result
|
|
1336
1324
|
}
|
|
1337
1325
|
|
|
1338
|
-
function subscribeSelf ($signal) {
|
|
1326
|
+
function subscribeSelf ($signal, intent = 'subscribe') {
|
|
1339
1327
|
if ($signal[IS_QUERY]) {
|
|
1340
1328
|
return (async () => {
|
|
1341
|
-
await querySubscriptions.subscribe($signal)
|
|
1329
|
+
await querySubscriptions.subscribe($signal, { intent })
|
|
1342
1330
|
await waitForImperativeQueryReady($signal)
|
|
1343
1331
|
})()
|
|
1344
1332
|
}
|
|
1345
1333
|
if ($signal[IS_AGGREGATION]) {
|
|
1346
1334
|
return (async () => {
|
|
1347
|
-
await aggregationSubscriptions.subscribe($signal)
|
|
1335
|
+
await aggregationSubscriptions.subscribe($signal, { intent })
|
|
1348
1336
|
await waitForImperativeQueryReady($signal)
|
|
1349
1337
|
})()
|
|
1350
1338
|
}
|
|
1351
|
-
if (isPublicDocumentSignal($signal)) return docSubscriptions.subscribe($signal)
|
|
1339
|
+
if (isPublicDocumentSignal($signal)) return docSubscriptions.subscribe($signal, { intent })
|
|
1352
1340
|
if (isPublicCollectionSignal($signal)) {
|
|
1353
1341
|
throw Error('Signal.subscribe() expects a query signal. Use .query() for collections.')
|
|
1354
1342
|
}
|
|
@@ -1358,10 +1346,10 @@ function subscribeSelf ($signal) {
|
|
|
1358
1346
|
throw Error('Signal.subscribe() expects a document or query signal')
|
|
1359
1347
|
}
|
|
1360
1348
|
|
|
1361
|
-
function unsubscribeSelf ($signal) {
|
|
1362
|
-
if ($signal[IS_QUERY]) return querySubscriptions.unsubscribe($signal)
|
|
1363
|
-
if ($signal[IS_AGGREGATION]) return aggregationSubscriptions.unsubscribe($signal)
|
|
1364
|
-
if (isPublicDocumentSignal($signal)) return docSubscriptions.unsubscribe($signal)
|
|
1349
|
+
function unsubscribeSelf ($signal, intent = 'subscribe') {
|
|
1350
|
+
if ($signal[IS_QUERY]) return querySubscriptions.unsubscribe($signal, { intent })
|
|
1351
|
+
if ($signal[IS_AGGREGATION]) return aggregationSubscriptions.unsubscribe($signal, { intent })
|
|
1352
|
+
if (isPublicDocumentSignal($signal)) return docSubscriptions.unsubscribe($signal, { intent })
|
|
1365
1353
|
if (isPublicCollectionSignal($signal)) {
|
|
1366
1354
|
throw Error('Signal.unsubscribe() expects a query signal. Use .query() for collections.')
|
|
1367
1355
|
}
|
package/orm/Doc.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { isObservable, observable, raw } from '@nx-js/observer-util'
|
|
2
2
|
import { set as _set, del as _del, getRaw as _getRaw } from './dataTree.js'
|
|
3
3
|
import { SEGMENTS } from './Signal.js'
|
|
4
|
-
import { getConnection
|
|
4
|
+
import { getConnection } from './connection.js'
|
|
5
5
|
import FinalizationRegistry from '../utils/MockFinalizationRegistry.js'
|
|
6
6
|
import SubscriptionState from './SubscriptionState.js'
|
|
7
7
|
import { getIdFieldsForSegments, injectIdFields, isPlainObject } from './idFields.js'
|
|
8
8
|
import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js'
|
|
9
9
|
import { getSubscriptionGcDelay } from './subscriptionGcDelay.js'
|
|
10
10
|
import { isMissingShareDoc } from './missingDoc.js'
|
|
11
|
-
import { getRoot, ROOT_ID, GLOBAL_ROOT_ID } from './Root.js'
|
|
11
|
+
import { getRoot, ROOT_ID, GLOBAL_ROOT_ID, getRootTransportMode } from './Root.js'
|
|
12
12
|
import {
|
|
13
13
|
registerRootOwnedDirectDocSubscription,
|
|
14
14
|
unregisterRootOwnedDirectDocSubscription,
|
|
@@ -45,6 +45,8 @@ class Doc {
|
|
|
45
45
|
onSubscribe: () => this._subscribe(),
|
|
46
46
|
onUnsubscribe: () => this._unsubscribe()
|
|
47
47
|
})
|
|
48
|
+
this.requestedTransportMode = 'subscribe'
|
|
49
|
+
this.activeTransportMode = 'idle'
|
|
48
50
|
this.init()
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -58,7 +60,8 @@ class Doc {
|
|
|
58
60
|
this._initData()
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
async subscribe () {
|
|
63
|
+
async subscribe ({ mode } = {}) {
|
|
64
|
+
if (mode) this.requestedTransportMode = mode
|
|
62
65
|
await this.lifecycle.subscribe()
|
|
63
66
|
this.init()
|
|
64
67
|
}
|
|
@@ -69,10 +72,12 @@ class Doc {
|
|
|
69
72
|
|
|
70
73
|
async _subscribe () {
|
|
71
74
|
const doc = getConnection().get(this.collection, this.docId)
|
|
75
|
+
const mode = this.requestedTransportMode
|
|
72
76
|
await new Promise((resolve, reject) => {
|
|
73
|
-
const method =
|
|
77
|
+
const method = mode === 'fetch' ? 'fetch' : 'subscribe'
|
|
74
78
|
doc[method](err => {
|
|
75
79
|
if (err) return reject(err)
|
|
80
|
+
this.activeTransportMode = mode
|
|
76
81
|
resolve()
|
|
77
82
|
})
|
|
78
83
|
})
|
|
@@ -81,8 +86,12 @@ class Doc {
|
|
|
81
86
|
async _unsubscribe () {
|
|
82
87
|
const doc = getConnection().get(this.collection, this.docId)
|
|
83
88
|
await new Promise((resolve, reject) => {
|
|
84
|
-
doc.
|
|
89
|
+
const method = this.activeTransportMode === 'fetch' && typeof doc.unfetch === 'function'
|
|
90
|
+
? 'unfetch'
|
|
91
|
+
: 'unsubscribe'
|
|
92
|
+
doc[method](err => {
|
|
85
93
|
if (err) return reject(err)
|
|
94
|
+
this.activeTransportMode = 'idle'
|
|
86
95
|
resolve()
|
|
87
96
|
})
|
|
88
97
|
})
|
|
@@ -167,10 +176,15 @@ class Doc {
|
|
|
167
176
|
export class DocSubscriptions {
|
|
168
177
|
constructor (DocClass = Doc) {
|
|
169
178
|
this.DocClass = DocClass
|
|
170
|
-
this.subCount = new Map()
|
|
179
|
+
this.subCount = new Map() // transportHash -> total ref count (owners + retained docs)
|
|
180
|
+
this.ownerFetchCount = new Map() // ownerKey -> fetch intent count
|
|
181
|
+
this.ownerSubscribeCount = new Map() // ownerKey -> subscribe intent count
|
|
182
|
+
this.ownerMeta = new Map() // ownerKey -> { hash, segments, rootId }
|
|
183
|
+
this.ownerKeysByHash = new Map() // transportHash -> Set(ownerKey)
|
|
171
184
|
this.docs = new Map()
|
|
172
185
|
this.pendingDestroyTimers = new Map()
|
|
173
|
-
this.
|
|
186
|
+
this.transportTasks = new Map()
|
|
187
|
+
this.fr = new FinalizationRegistry(({ hash, ownerKey }) => this.destroyByOwnerKey(ownerKey, { hash, force: true }))
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
init ($doc) {
|
|
@@ -183,34 +197,36 @@ export class DocSubscriptions {
|
|
|
183
197
|
} else {
|
|
184
198
|
doc = new this.DocClass(...segments)
|
|
185
199
|
this.docs.set(hash, doc)
|
|
186
|
-
this.fr.register($doc, segments, getDocFinalizationToken($doc))
|
|
187
200
|
doc.init()
|
|
188
201
|
}
|
|
189
202
|
}
|
|
190
203
|
|
|
191
|
-
subscribe ($doc) {
|
|
204
|
+
subscribe ($doc, { intent = 'subscribe' } = {}) {
|
|
192
205
|
const segments = [...$doc[SEGMENTS]]
|
|
193
206
|
const hash = hashDoc(segments)
|
|
194
207
|
const rootId = getOwningRootId($doc)
|
|
208
|
+
const ownerKey = getDocOwnerKey(rootId, hash)
|
|
209
|
+
const token = getDocFinalizationToken($doc)
|
|
210
|
+
const previousCount = this.subCount.get(hash) || 0
|
|
195
211
|
this.cancelDestroy(hash)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
this.subCount.set(hash,
|
|
212
|
+
this.incrementOwnerIntent(ownerKey, intent)
|
|
213
|
+
this.addOwnerMeta(ownerKey, hash, segments, rootId)
|
|
214
|
+
this.subCount.set(hash, previousCount + 1)
|
|
199
215
|
if (rootId) {
|
|
200
|
-
registerRootOwnedDirectDocSubscription(rootId, hash, segments,
|
|
201
|
-
}
|
|
202
|
-
if (count > 1) {
|
|
203
|
-
const existingDoc = this.docs.get(hash)
|
|
204
|
-
if (existingDoc) return existingDoc._subscribing
|
|
205
|
-
// Recover from stale ref-count state when doc entry was already cleaned up.
|
|
206
|
-
count = 1
|
|
207
|
-
this.subCount.set(hash, count)
|
|
216
|
+
registerRootOwnedDirectDocSubscription(rootId, hash, segments, token)
|
|
208
217
|
}
|
|
218
|
+
this.fr.register($doc, { hash, ownerKey }, token)
|
|
209
219
|
|
|
210
220
|
this.init($doc)
|
|
211
221
|
const doc = this.docs.get(hash)
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
if (
|
|
223
|
+
previousCount > 0 &&
|
|
224
|
+
doc &&
|
|
225
|
+
!doc._subscribing &&
|
|
226
|
+
!this.transportTasks.get(hash) &&
|
|
227
|
+
this.getDesiredTransportMode(hash) === doc.activeTransportMode
|
|
228
|
+
) return
|
|
229
|
+
return this.reconcileTransport(hash)
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
retain ($doc) {
|
|
@@ -222,26 +238,33 @@ export class DocSubscriptions {
|
|
|
222
238
|
this.init($doc)
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
async unsubscribe ($doc) {
|
|
241
|
+
async unsubscribe ($doc, { intent = 'subscribe' } = {}) {
|
|
226
242
|
const segments = [...$doc[SEGMENTS]]
|
|
227
243
|
const hash = hashDoc(segments)
|
|
228
244
|
const rootId = getOwningRootId($doc)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
245
|
+
const ownerKey = getDocOwnerKey(rootId, hash)
|
|
246
|
+
const token = getDocFinalizationToken($doc)
|
|
247
|
+
const currentIntentCount = this.getOwnerIntentCount(ownerKey, intent)
|
|
248
|
+
if (currentIntentCount <= 0) {
|
|
232
249
|
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES) throw ERRORS.notSubscribed($doc)
|
|
233
250
|
return
|
|
234
251
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.subCount.set(hash, 0)
|
|
240
|
-
this.fr.unregister(getDocFinalizationToken($doc))
|
|
252
|
+
this.setOwnerIntentCount(ownerKey, intent, currentIntentCount - 1)
|
|
253
|
+
const nextOwnerCount = this.getOwnerTotalCount(ownerKey)
|
|
254
|
+
const count = Math.max((this.subCount.get(hash) || 0) - 1, 0)
|
|
255
|
+
if (count > 0) this.subCount.set(hash, count)
|
|
256
|
+
else this.subCount.set(hash, 0)
|
|
241
257
|
if (rootId) {
|
|
242
|
-
unregisterRootOwnedDirectDocSubscription(rootId, hash,
|
|
258
|
+
unregisterRootOwnedDirectDocSubscription(rootId, hash, token)
|
|
243
259
|
}
|
|
244
|
-
|
|
260
|
+
if (nextOwnerCount === 0) {
|
|
261
|
+
this.fr.unregister(token)
|
|
262
|
+
this.removeOwnerMeta(ownerKey, hash)
|
|
263
|
+
}
|
|
264
|
+
const destroyPromise = count === 0 ? this.scheduleDestroy(segments) : undefined
|
|
265
|
+
await this.reconcileTransport(hash)
|
|
266
|
+
if (count > 0) return
|
|
267
|
+
await destroyPromise
|
|
245
268
|
}
|
|
246
269
|
|
|
247
270
|
async release ($doc) {
|
|
@@ -275,6 +298,10 @@ export class DocSubscriptions {
|
|
|
275
298
|
await this.destroyByHash(hash, { force: true })
|
|
276
299
|
}
|
|
277
300
|
this.subCount.clear()
|
|
301
|
+
this.ownerFetchCount.clear()
|
|
302
|
+
this.ownerSubscribeCount.clear()
|
|
303
|
+
this.ownerMeta.clear()
|
|
304
|
+
this.ownerKeysByHash.clear()
|
|
278
305
|
}
|
|
279
306
|
|
|
280
307
|
async releaseRootOwnedSubscriptions (rootId) {
|
|
@@ -284,14 +311,7 @@ export class DocSubscriptions {
|
|
|
284
311
|
for (const token of entry.tokenCounts.keys()) {
|
|
285
312
|
this.fr.unregister(token)
|
|
286
313
|
}
|
|
287
|
-
|
|
288
|
-
const nextCount = Math.max(currentCount - entry.count, 0)
|
|
289
|
-
if (nextCount > 0) {
|
|
290
|
-
this.subCount.set(hash, nextCount)
|
|
291
|
-
continue
|
|
292
|
-
}
|
|
293
|
-
this.subCount.set(hash, 0)
|
|
294
|
-
await this.destroyByHash(hash, { force: true })
|
|
314
|
+
await this.destroyByOwnerKey(getDocOwnerKey(rootId, hash), { hash, force: true })
|
|
295
315
|
}
|
|
296
316
|
clearRootOwnedDirectDocSubscriptions(rootId)
|
|
297
317
|
}
|
|
@@ -330,6 +350,40 @@ export class DocSubscriptions {
|
|
|
330
350
|
entry.resolve()
|
|
331
351
|
}
|
|
332
352
|
|
|
353
|
+
async reconcileTransport (hash) {
|
|
354
|
+
const previous = this.transportTasks.get(hash) || Promise.resolve()
|
|
355
|
+
const next = previous
|
|
356
|
+
.catch(ignoreDestroyError)
|
|
357
|
+
.then(() => this.reconcileTransportNow(hash))
|
|
358
|
+
this.transportTasks.set(hash, next)
|
|
359
|
+
try {
|
|
360
|
+
await next
|
|
361
|
+
} finally {
|
|
362
|
+
if (this.transportTasks.get(hash) === next) this.transportTasks.delete(hash)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async reconcileTransportNow (hash) {
|
|
367
|
+
const doc = this.docs.get(hash)
|
|
368
|
+
if (!doc) return
|
|
369
|
+
while (true) {
|
|
370
|
+
const desiredMode = this.getDesiredTransportMode(hash)
|
|
371
|
+
const currentMode = doc.activeTransportMode
|
|
372
|
+
if (desiredMode === currentMode) return
|
|
373
|
+
if (desiredMode === 'idle') {
|
|
374
|
+
if (currentMode === 'idle') return
|
|
375
|
+
await doc.unsubscribe()
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
if (currentMode !== 'idle') {
|
|
379
|
+
await doc.unsubscribe()
|
|
380
|
+
continue
|
|
381
|
+
}
|
|
382
|
+
doc._subscribing = doc.subscribe({ mode: desiredMode }).then(() => { doc._subscribing = undefined })
|
|
383
|
+
await doc._subscribing
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
333
387
|
async destroyByHash (hash, options = {}) {
|
|
334
388
|
let pendingDestroy = options._pendingDestroy
|
|
335
389
|
if (pendingDestroy) this.takePendingDestroy(hash, pendingDestroy)
|
|
@@ -354,12 +408,13 @@ export class DocSubscriptions {
|
|
|
354
408
|
settlePending()
|
|
355
409
|
return
|
|
356
410
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
await doc.unsubscribe()
|
|
360
|
-
if (doc.subscribed) {
|
|
411
|
+
await this.reconcileTransport(hash)
|
|
412
|
+
if (!options.force && (this.subCount.get(hash) || 0) > 0) {
|
|
361
413
|
settlePending()
|
|
362
|
-
return
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
if (doc.activeTransportMode !== 'idle') {
|
|
417
|
+
await doc.unsubscribe()
|
|
363
418
|
}
|
|
364
419
|
if (!options.force && (this.subCount.get(hash) || 0) > 0) {
|
|
365
420
|
settlePending()
|
|
@@ -381,6 +436,7 @@ export class DocSubscriptions {
|
|
|
381
436
|
if (typeof doc.dispose === 'function') doc.dispose()
|
|
382
437
|
this.docs.delete(hash)
|
|
383
438
|
this.subCount.delete(hash)
|
|
439
|
+
this.ownerKeysByHash.delete(hash)
|
|
384
440
|
settlePending()
|
|
385
441
|
} catch (err) {
|
|
386
442
|
settlePending(err)
|
|
@@ -396,6 +452,83 @@ export class DocSubscriptions {
|
|
|
396
452
|
this.pendingDestroyTimers.delete(hash)
|
|
397
453
|
return entry
|
|
398
454
|
}
|
|
455
|
+
|
|
456
|
+
getOwnerIntentCount (ownerKey, intent) {
|
|
457
|
+
const store = intent === 'fetch' ? this.ownerFetchCount : this.ownerSubscribeCount
|
|
458
|
+
return store.get(ownerKey) || 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
setOwnerIntentCount (ownerKey, intent, count) {
|
|
462
|
+
const store = intent === 'fetch' ? this.ownerFetchCount : this.ownerSubscribeCount
|
|
463
|
+
if (count > 0) store.set(ownerKey, count)
|
|
464
|
+
else store.delete(ownerKey)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
incrementOwnerIntent (ownerKey, intent) {
|
|
468
|
+
this.setOwnerIntentCount(ownerKey, intent, this.getOwnerIntentCount(ownerKey, intent) + 1)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
getOwnerTotalCount (ownerKey) {
|
|
472
|
+
return (this.ownerFetchCount.get(ownerKey) || 0) + (this.ownerSubscribeCount.get(ownerKey) || 0)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
addOwnerMeta (ownerKey, hash, segments, rootId) {
|
|
476
|
+
if (this.ownerMeta.has(ownerKey)) return
|
|
477
|
+
this.ownerMeta.set(ownerKey, { hash, segments: [...segments], rootId })
|
|
478
|
+
let ownerKeys = this.ownerKeysByHash.get(hash)
|
|
479
|
+
if (!ownerKeys) {
|
|
480
|
+
ownerKeys = new Set()
|
|
481
|
+
this.ownerKeysByHash.set(hash, ownerKeys)
|
|
482
|
+
}
|
|
483
|
+
ownerKeys.add(ownerKey)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
removeOwnerMeta (ownerKey, hash) {
|
|
487
|
+
const meta = this.ownerMeta.get(ownerKey)
|
|
488
|
+
const knownHash = hash ?? meta?.hash
|
|
489
|
+
this.ownerMeta.delete(ownerKey)
|
|
490
|
+
this.ownerFetchCount.delete(ownerKey)
|
|
491
|
+
this.ownerSubscribeCount.delete(ownerKey)
|
|
492
|
+
if (!knownHash) return
|
|
493
|
+
const ownerKeys = this.ownerKeysByHash.get(knownHash)
|
|
494
|
+
if (!ownerKeys) return
|
|
495
|
+
ownerKeys.delete(ownerKey)
|
|
496
|
+
if (ownerKeys.size === 0) this.ownerKeysByHash.delete(knownHash)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
getDesiredTransportMode (hash) {
|
|
500
|
+
const ownerKeys = this.ownerKeysByHash.get(hash)
|
|
501
|
+
if (!ownerKeys || ownerKeys.size === 0) return 'idle'
|
|
502
|
+
let hasFetchBackedOwner = false
|
|
503
|
+
for (const ownerKey of ownerKeys) {
|
|
504
|
+
const subscribeCount = this.ownerSubscribeCount.get(ownerKey) || 0
|
|
505
|
+
const fetchCount = this.ownerFetchCount.get(ownerKey) || 0
|
|
506
|
+
const rootId = this.ownerMeta.get(ownerKey)?.rootId
|
|
507
|
+
const subscribeMode = getRootTransportMode(rootId, 'subscribe')
|
|
508
|
+
if (subscribeCount > 0 && subscribeMode === 'subscribe') return 'subscribe'
|
|
509
|
+
if (fetchCount > 0 || (subscribeCount > 0 && subscribeMode === 'fetch')) {
|
|
510
|
+
hasFetchBackedOwner = true
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return hasFetchBackedOwner ? 'fetch' : 'idle'
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async destroyByOwnerKey (ownerKey, options = {}) {
|
|
517
|
+
const meta = this.ownerMeta.get(ownerKey)
|
|
518
|
+
if (!meta) return
|
|
519
|
+
const { hash, segments } = meta
|
|
520
|
+
const ownerCount = this.getOwnerTotalCount(ownerKey)
|
|
521
|
+
if (!options.force && ownerCount > 0) return
|
|
522
|
+
|
|
523
|
+
const currentCount = this.subCount.get(hash) || 0
|
|
524
|
+
const nextCount = Math.max(currentCount - ownerCount, 0)
|
|
525
|
+
if (nextCount > 0) this.subCount.set(hash, nextCount)
|
|
526
|
+
else this.subCount.set(hash, 0)
|
|
527
|
+
this.removeOwnerMeta(ownerKey, hash)
|
|
528
|
+
await this.reconcileTransport(hash)
|
|
529
|
+
if (nextCount > 0) return
|
|
530
|
+
await this.scheduleDestroy(segments, { force: !!options.force })
|
|
531
|
+
}
|
|
399
532
|
}
|
|
400
533
|
|
|
401
534
|
export const docSubscriptions = new DocSubscriptions()
|
|
@@ -404,6 +537,10 @@ function hashDoc (segments) {
|
|
|
404
537
|
return JSON.stringify(segments)
|
|
405
538
|
}
|
|
406
539
|
|
|
540
|
+
function getDocOwnerKey (rootId, hash) {
|
|
541
|
+
return JSON.stringify({ owner: [rootId, hash] })
|
|
542
|
+
}
|
|
543
|
+
|
|
407
544
|
function ignoreDestroyError () {}
|
|
408
545
|
|
|
409
546
|
function createPendingDestroyEntry () {
|
package/orm/Query.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { raw } from '@nx-js/observer-util'
|
|
2
2
|
import { set as _set, getRaw } from './dataTree.js'
|
|
3
3
|
import getSignal from './getSignal.js'
|
|
4
|
-
import { getConnection
|
|
4
|
+
import { getConnection } from './connection.js'
|
|
5
5
|
import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js'
|
|
6
6
|
import { isCompatEnv } from './compatEnv.js'
|
|
7
7
|
import { docSubscriptions } from './Doc.js'
|
|
@@ -10,7 +10,7 @@ import SubscriptionState from './SubscriptionState.js'
|
|
|
10
10
|
import { getIdFieldsForSegments, injectIdFields, isPlainObject } from './idFields.js'
|
|
11
11
|
import { getSubscriptionGcDelay } from './subscriptionGcDelay.js'
|
|
12
12
|
import { getScopedSignalHash } from './rootScope.js'
|
|
13
|
-
import { getRoot, ROOT_ID } from './Root.js'
|
|
13
|
+
import { getRoot, ROOT_ID, getRootTransportMode } from './Root.js'
|
|
14
14
|
import { registerRootOwnedRuntime, unregisterRootOwnedRuntime } from './rootContext.js'
|
|
15
15
|
import {
|
|
16
16
|
delPrivateData,
|
|
@@ -39,10 +39,12 @@ export class Query {
|
|
|
39
39
|
onSubscribe: () => this._subscribe(),
|
|
40
40
|
onUnsubscribe: () => this._unsubscribe()
|
|
41
41
|
})
|
|
42
|
+
this.requestedTransportMode = 'subscribe'
|
|
43
|
+
this.activeTransportMode = 'idle'
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
get subscribed () {
|
|
45
|
-
return this.lifecycle.subscribed
|
|
47
|
+
return this.activeTransportMode !== 'idle' || this.lifecycle.subscribed
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
init () {
|
|
@@ -51,7 +53,8 @@ export class Query {
|
|
|
51
53
|
this._initData()
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
async subscribe () {
|
|
56
|
+
async subscribe ({ mode } = {}) {
|
|
57
|
+
if (mode) this.requestedTransportMode = mode
|
|
55
58
|
await this.lifecycle.subscribe()
|
|
56
59
|
this.init()
|
|
57
60
|
}
|
|
@@ -60,7 +63,7 @@ export class Query {
|
|
|
60
63
|
await this.lifecycle.unsubscribe()
|
|
61
64
|
if (!this.subscribed) {
|
|
62
65
|
this.initialized = undefined
|
|
63
|
-
this.
|
|
66
|
+
this._detachTransportData({ keepRoots: false })
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -78,10 +81,12 @@ export class Query {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
async _subscribe () {
|
|
84
|
+
const mode = this.requestedTransportMode
|
|
81
85
|
await new Promise((resolve, reject) => {
|
|
82
|
-
const method =
|
|
86
|
+
const method = mode === 'fetch' ? 'createFetchQuery' : 'createSubscribeQuery'
|
|
83
87
|
this.shareQuery = getConnection()[method](this.collectionName, this.params, {}, err => {
|
|
84
88
|
if (err) return reject(err)
|
|
89
|
+
this.activeTransportMode = mode
|
|
85
90
|
resolve()
|
|
86
91
|
})
|
|
87
92
|
})
|
|
@@ -92,6 +97,7 @@ export class Query {
|
|
|
92
97
|
await new Promise((resolve, reject) => {
|
|
93
98
|
this.shareQuery.destroy(err => {
|
|
94
99
|
if (err) return reject(err)
|
|
100
|
+
this.activeTransportMode = 'idle'
|
|
95
101
|
resolve()
|
|
96
102
|
})
|
|
97
103
|
this.shareQuery = undefined
|
|
@@ -250,13 +256,17 @@ export class Query {
|
|
|
250
256
|
})
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
|
|
259
|
+
_detachTransportData ({ keepRoots = true } = {}) {
|
|
254
260
|
for (const $doc of this.docSignals) {
|
|
255
261
|
docSubscriptions.release($doc).catch(ignoreDestroyError)
|
|
256
262
|
}
|
|
257
263
|
this.docSignals.clear()
|
|
258
264
|
this._forEachRoot(rootId => this._removeRootData(rootId))
|
|
259
|
-
this.rootIds.clear()
|
|
265
|
+
if (!keepRoots) this.rootIds.clear()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_removeData () {
|
|
269
|
+
this._detachTransportData({ keepRoots: false })
|
|
260
270
|
}
|
|
261
271
|
}
|
|
262
272
|
|
|
@@ -264,39 +274,44 @@ export class QuerySubscriptions {
|
|
|
264
274
|
constructor (QueryClass = Query) {
|
|
265
275
|
this.QueryClass = QueryClass
|
|
266
276
|
this.runtimeKind = 'query'
|
|
267
|
-
this.subCount = new Map() // ownerKey -> count
|
|
268
|
-
this.transportSubCount = new Map() // transportHash -> attached
|
|
277
|
+
this.subCount = new Map() // ownerKey -> total ref count
|
|
278
|
+
this.transportSubCount = new Map() // transportHash -> attached owner count
|
|
279
|
+
this.ownerFetchCount = new Map() // ownerKey -> fetch intent count
|
|
280
|
+
this.ownerSubscribeCount = new Map() // ownerKey -> subscribe intent count
|
|
269
281
|
this.queries = new Map()
|
|
270
282
|
this.ownerToTransport = new Map() // ownerKey -> transportHash
|
|
271
283
|
this.ownerMeta = new Map() // ownerKey -> { collectionName, params, transportHash, rootId }
|
|
272
284
|
this.ownerKeysByTransport = new Map() // transportHash -> Set(ownerKey)
|
|
273
285
|
this.pendingDestroyTimers = new Map()
|
|
286
|
+
this.transportTasks = new Map()
|
|
274
287
|
this.fr = new FinalizationRegistry(({ collectionName, params, ownerKey }) => {
|
|
275
288
|
this.scheduleDestroy(collectionName, params, ownerKey, { force: true })
|
|
276
289
|
})
|
|
277
290
|
}
|
|
278
291
|
|
|
279
|
-
subscribe ($query) {
|
|
292
|
+
subscribe ($query, { intent = 'subscribe' } = {}) {
|
|
280
293
|
const collectionName = $query[COLLECTION_NAME]
|
|
281
294
|
const params = cloneQueryParams($query[PARAMS])
|
|
282
295
|
const transportHash = $query[HASH]
|
|
283
296
|
const rootId = getOwningRootId($query)
|
|
284
297
|
const ownerKey = getQueryOwnerKey(rootId, transportHash)
|
|
285
298
|
this.cancelDestroy(ownerKey)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.subCount.
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.
|
|
299
|
+
|
|
300
|
+
let query = this.queries.get(transportHash)
|
|
301
|
+
let previousCount = this.subCount.get(ownerKey) || 0
|
|
302
|
+
if (previousCount > 0 && !query) {
|
|
303
|
+
this.subCount.delete(ownerKey)
|
|
304
|
+
this.ownerFetchCount.delete(ownerKey)
|
|
305
|
+
this.ownerSubscribeCount.delete(ownerKey)
|
|
306
|
+
const staleTransportHash = this.ownerToTransport.get(ownerKey)
|
|
307
|
+
if (staleTransportHash) this.removeOwnerMeta(ownerKey, staleTransportHash)
|
|
308
|
+
previousCount = 0
|
|
295
309
|
}
|
|
296
310
|
|
|
311
|
+
this.incrementOwnerIntent(ownerKey, intent)
|
|
312
|
+
this.subCount.set(ownerKey, previousCount + 1)
|
|
297
313
|
this.fr.register($query, { collectionName, params, ownerKey }, $query)
|
|
298
314
|
|
|
299
|
-
let query = this.queries.get(transportHash)
|
|
300
315
|
if (!query) {
|
|
301
316
|
query = new this.QueryClass(collectionName, params, { hash: transportHash })
|
|
302
317
|
this.queries.set(transportHash, query)
|
|
@@ -320,29 +335,66 @@ export class QuerySubscriptions {
|
|
|
320
335
|
|
|
321
336
|
const transportCount = (this.transportSubCount.get(transportHash) || 0) + 1
|
|
322
337
|
this.transportSubCount.set(transportHash, transportCount)
|
|
323
|
-
if (transportCount === 1) {
|
|
324
|
-
query._subscribing = query.subscribe().then(() => { query._subscribing = undefined })
|
|
325
|
-
}
|
|
326
338
|
}
|
|
327
339
|
|
|
328
|
-
|
|
340
|
+
if (
|
|
341
|
+
previousCount > 0 &&
|
|
342
|
+
query &&
|
|
343
|
+
!query._subscribing &&
|
|
344
|
+
!this.transportTasks.get(transportHash) &&
|
|
345
|
+
this.getDesiredTransportMode(transportHash) === query.activeTransportMode
|
|
346
|
+
) return
|
|
347
|
+
|
|
348
|
+
return this.reconcileTransport(transportHash)
|
|
329
349
|
}
|
|
330
350
|
|
|
331
|
-
async unsubscribe ($query) {
|
|
351
|
+
async unsubscribe ($query, { intent = 'subscribe' } = {}) {
|
|
332
352
|
const ownerKey = getQueryOwnerKey(getOwningRootId($query), $query[HASH])
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
353
|
+
const currentIntentCount = this.getOwnerIntentCount(ownerKey, intent)
|
|
354
|
+
if (currentIntentCount <= 0) {
|
|
355
|
+
if ((this.subCount.get(ownerKey) || 0) > 0 && !this.queries.get($query[HASH])) {
|
|
356
|
+
this.subCount.delete(ownerKey)
|
|
357
|
+
this.ownerFetchCount.delete(ownerKey)
|
|
358
|
+
this.ownerSubscribeCount.delete(ownerKey)
|
|
359
|
+
this.removeOwnerMeta(ownerKey)
|
|
360
|
+
}
|
|
336
361
|
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES) throw Error(ERRORS.notSubscribed($query))
|
|
337
362
|
return
|
|
338
363
|
}
|
|
364
|
+
|
|
365
|
+
const meta = this.ownerMeta.get(ownerKey)
|
|
366
|
+
const transportHash = meta?.transportHash ?? $query[HASH]
|
|
367
|
+
|
|
368
|
+
this.setOwnerIntentCount(ownerKey, intent, currentIntentCount - 1)
|
|
369
|
+
|
|
370
|
+
const count = Math.max((this.subCount.get(ownerKey) || 0) - 1, 0)
|
|
339
371
|
if (count > 0) {
|
|
340
372
|
this.subCount.set(ownerKey, count)
|
|
341
|
-
|
|
373
|
+
} else {
|
|
374
|
+
this.subCount.set(ownerKey, 0)
|
|
342
375
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
376
|
+
|
|
377
|
+
if (count === 0) {
|
|
378
|
+
this.fr.unregister($query)
|
|
379
|
+
if (meta) {
|
|
380
|
+
const query = this.queries.get(transportHash)
|
|
381
|
+
this.removeOwnerMeta(ownerKey, transportHash)
|
|
382
|
+
detachQueryRoot(query, meta.rootId)
|
|
383
|
+
unregisterRootOwnedRuntime(meta.rootId, this.runtimeKind, transportHash)
|
|
384
|
+
|
|
385
|
+
const nextTransportCount = Math.max((this.transportSubCount.get(transportHash) || 0) - 1, 0)
|
|
386
|
+
if (nextTransportCount > 0) this.transportSubCount.set(transportHash, nextTransportCount)
|
|
387
|
+
else this.transportSubCount.set(transportHash, 0)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const destroyPromise = count === 0
|
|
392
|
+
? this.scheduleDestroy($query[COLLECTION_NAME], $query[PARAMS], ownerKey)
|
|
393
|
+
: undefined
|
|
394
|
+
|
|
395
|
+
await this.reconcileTransport(transportHash)
|
|
396
|
+
if (count > 0) return
|
|
397
|
+
await destroyPromise
|
|
346
398
|
}
|
|
347
399
|
|
|
348
400
|
async destroy (collectionName, params, options = {}) {
|
|
@@ -367,9 +419,12 @@ export class QuerySubscriptions {
|
|
|
367
419
|
}
|
|
368
420
|
this.subCount.clear()
|
|
369
421
|
this.transportSubCount.clear()
|
|
422
|
+
this.ownerFetchCount.clear()
|
|
423
|
+
this.ownerSubscribeCount.clear()
|
|
370
424
|
this.ownerToTransport.clear()
|
|
371
425
|
this.ownerMeta.clear()
|
|
372
426
|
this.ownerKeysByTransport.clear()
|
|
427
|
+
this.transportTasks.clear()
|
|
373
428
|
}
|
|
374
429
|
|
|
375
430
|
async flushPendingDestroys () {
|
|
@@ -393,6 +448,8 @@ export class QuerySubscriptions {
|
|
|
393
448
|
}
|
|
394
449
|
const entry = createPendingDestroyEntry()
|
|
395
450
|
if (options.force) entry.force = true
|
|
451
|
+
entry.collectionName = collectionName
|
|
452
|
+
entry.params = params
|
|
396
453
|
entry.timer = setTimeout(() => {
|
|
397
454
|
this.destroyByOwnerKey(fallbackOwnerKey, { collectionName, params, force: entry.force })
|
|
398
455
|
.catch(ignoreDestroyError)
|
|
@@ -407,9 +464,80 @@ export class QuerySubscriptions {
|
|
|
407
464
|
entry.resolve()
|
|
408
465
|
}
|
|
409
466
|
|
|
467
|
+
async reconcileTransport (transportHash) {
|
|
468
|
+
const previous = this.transportTasks.get(transportHash) || Promise.resolve()
|
|
469
|
+
const next = previous
|
|
470
|
+
.catch(ignoreDestroyError)
|
|
471
|
+
.then(() => this.reconcileTransportNow(transportHash))
|
|
472
|
+
this.transportTasks.set(transportHash, next)
|
|
473
|
+
try {
|
|
474
|
+
await next
|
|
475
|
+
} finally {
|
|
476
|
+
if (this.transportTasks.get(transportHash) === next) this.transportTasks.delete(transportHash)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async reconcileTransportNow (transportHash) {
|
|
481
|
+
const query = this.queries.get(transportHash)
|
|
482
|
+
if (!query) return
|
|
483
|
+
while (true) {
|
|
484
|
+
const desiredMode = this.getDesiredTransportMode(transportHash)
|
|
485
|
+
const currentMode = query.activeTransportMode
|
|
486
|
+
if (desiredMode === currentMode) return
|
|
487
|
+
if (desiredMode === 'idle') {
|
|
488
|
+
if (currentMode === 'idle') return
|
|
489
|
+
await unsubscribeQueryTransport(query, { keepRoots: true })
|
|
490
|
+
continue
|
|
491
|
+
}
|
|
492
|
+
if (currentMode !== 'idle') {
|
|
493
|
+
await unsubscribeQueryTransport(query, { keepRoots: true })
|
|
494
|
+
continue
|
|
495
|
+
}
|
|
496
|
+
await subscribeQueryTransport(query, desiredMode)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getOwnerIntentCount (ownerKey, intent) {
|
|
501
|
+
const store = intent === 'fetch' ? this.ownerFetchCount : this.ownerSubscribeCount
|
|
502
|
+
return store.get(ownerKey) || 0
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
setOwnerIntentCount (ownerKey, intent, count) {
|
|
506
|
+
const store = intent === 'fetch' ? this.ownerFetchCount : this.ownerSubscribeCount
|
|
507
|
+
if (count > 0) store.set(ownerKey, count)
|
|
508
|
+
else store.delete(ownerKey)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
incrementOwnerIntent (ownerKey, intent) {
|
|
512
|
+
this.setOwnerIntentCount(ownerKey, intent, this.getOwnerIntentCount(ownerKey, intent) + 1)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
getDesiredTransportMode (transportHash) {
|
|
516
|
+
const ownerKeys = this.ownerKeysByTransport.get(transportHash)
|
|
517
|
+
if (!ownerKeys || ownerKeys.size === 0) return 'idle'
|
|
518
|
+
let hasFetchBackedOwner = false
|
|
519
|
+
for (const ownerKey of ownerKeys) {
|
|
520
|
+
const subscribeCount = this.ownerSubscribeCount.get(ownerKey) || 0
|
|
521
|
+
const fetchCount = this.ownerFetchCount.get(ownerKey) || 0
|
|
522
|
+
const rootId = this.ownerMeta.get(ownerKey)?.rootId
|
|
523
|
+
const subscribeMode = getRootTransportMode(rootId, 'subscribe')
|
|
524
|
+
if (subscribeCount > 0 && subscribeMode === 'subscribe') return 'subscribe'
|
|
525
|
+
if (fetchCount > 0 || (subscribeCount > 0 && subscribeMode === 'fetch')) {
|
|
526
|
+
hasFetchBackedOwner = true
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return hasFetchBackedOwner ? 'fetch' : 'idle'
|
|
530
|
+
}
|
|
531
|
+
|
|
410
532
|
async destroyByOwnerKey (ownerKey, options = {}) {
|
|
411
533
|
const pendingDestroy = this.takePendingDestroy(ownerKey)
|
|
412
534
|
if (pendingDestroy?.force) options.force = true
|
|
535
|
+
if (options.collectionName == null && pendingDestroy?.collectionName != null) {
|
|
536
|
+
options.collectionName = pendingDestroy.collectionName
|
|
537
|
+
}
|
|
538
|
+
if (options.params == null && pendingDestroy?.params != null) {
|
|
539
|
+
options.params = pendingDestroy.params
|
|
540
|
+
}
|
|
413
541
|
|
|
414
542
|
const settlePending = err => {
|
|
415
543
|
if (!pendingDestroy) return
|
|
@@ -425,43 +553,59 @@ export class QuerySubscriptions {
|
|
|
425
553
|
}
|
|
426
554
|
const meta = this.ownerMeta.get(ownerKey)
|
|
427
555
|
if (!meta) {
|
|
556
|
+
const transportHash = options.collectionName && options.params
|
|
557
|
+
? hashQuery(options.collectionName, options.params)
|
|
558
|
+
: this.ownerToTransport.get(ownerKey)
|
|
428
559
|
this.subCount.delete(ownerKey)
|
|
560
|
+
this.ownerFetchCount.delete(ownerKey)
|
|
561
|
+
this.ownerSubscribeCount.delete(ownerKey)
|
|
562
|
+
this.ownerToTransport.delete(ownerKey)
|
|
563
|
+
if (!transportHash) {
|
|
564
|
+
settlePending()
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
const query = this.queries.get(transportHash)
|
|
568
|
+
await this.reconcileTransport(transportHash)
|
|
569
|
+
if ((this.transportSubCount.get(transportHash) || 0) <= 0) {
|
|
570
|
+
if (query?.activeTransportMode !== 'idle') await unsubscribeQueryTransport(query, { keepRoots: true })
|
|
571
|
+
query?._detachTransportData?.({ keepRoots: false })
|
|
572
|
+
this.transportSubCount.delete(transportHash)
|
|
573
|
+
this.ownerKeysByTransport.delete(transportHash)
|
|
574
|
+
this.queries.delete(transportHash)
|
|
575
|
+
}
|
|
429
576
|
settlePending()
|
|
430
577
|
return
|
|
431
578
|
}
|
|
432
579
|
const { transportHash, rootId } = meta
|
|
433
580
|
const query = this.queries.get(transportHash)
|
|
434
|
-
|
|
435
|
-
this.subCount.delete(ownerKey)
|
|
436
|
-
this.removeOwnerMeta(ownerKey, transportHash)
|
|
437
|
-
unregisterRootOwnedRuntime(rootId, this.runtimeKind, transportHash)
|
|
438
|
-
const nextTransportCount = Math.max((this.transportSubCount.get(transportHash) || 0) - 1, 0)
|
|
439
|
-
if (nextTransportCount > 0) this.transportSubCount.set(transportHash, nextTransportCount)
|
|
440
|
-
else this.transportSubCount.delete(transportHash)
|
|
441
|
-
settlePending()
|
|
442
|
-
return
|
|
443
|
-
}
|
|
581
|
+
|
|
444
582
|
this.subCount.delete(ownerKey)
|
|
445
583
|
this.removeOwnerMeta(ownerKey, transportHash)
|
|
446
584
|
detachQueryRoot(query, rootId)
|
|
447
585
|
unregisterRootOwnedRuntime(rootId, this.runtimeKind, transportHash)
|
|
448
586
|
|
|
449
587
|
const nextTransportCount = Math.max((this.transportSubCount.get(transportHash) || 0) - 1, 0)
|
|
450
|
-
this.transportSubCount.set(transportHash, nextTransportCount)
|
|
451
|
-
|
|
588
|
+
if (nextTransportCount > 0) this.transportSubCount.set(transportHash, nextTransportCount)
|
|
589
|
+
else this.transportSubCount.set(transportHash, 0)
|
|
590
|
+
|
|
591
|
+
await this.reconcileTransport(transportHash)
|
|
592
|
+
if ((this.transportSubCount.get(transportHash) || 0) > 0) {
|
|
452
593
|
settlePending()
|
|
453
594
|
return
|
|
454
595
|
}
|
|
455
|
-
|
|
456
|
-
|
|
596
|
+
if (!query) {
|
|
597
|
+
this.transportSubCount.delete(transportHash)
|
|
457
598
|
settlePending()
|
|
458
|
-
return
|
|
599
|
+
return
|
|
459
600
|
}
|
|
601
|
+
if (query.activeTransportMode !== 'idle') await unsubscribeQueryTransport(query, { keepRoots: true })
|
|
602
|
+
query._detachTransportData({ keepRoots: false })
|
|
460
603
|
if ((this.transportSubCount.get(transportHash) || 0) > 0) {
|
|
461
604
|
settlePending()
|
|
462
605
|
return
|
|
463
606
|
}
|
|
464
607
|
this.transportSubCount.delete(transportHash)
|
|
608
|
+
this.ownerKeysByTransport.delete(transportHash)
|
|
465
609
|
this.queries.delete(transportHash)
|
|
466
610
|
settlePending()
|
|
467
611
|
} catch (err) {
|
|
@@ -625,8 +769,45 @@ function createPendingDestroyEntry () {
|
|
|
625
769
|
return {
|
|
626
770
|
timer: undefined,
|
|
627
771
|
force: false,
|
|
772
|
+
collectionName: undefined,
|
|
773
|
+
params: undefined,
|
|
628
774
|
promise,
|
|
629
775
|
resolve: resolvePending,
|
|
630
776
|
reject: rejectPending
|
|
631
777
|
}
|
|
632
778
|
}
|
|
779
|
+
|
|
780
|
+
async function subscribeQueryTransport (query, mode) {
|
|
781
|
+
query.requestedTransportMode = mode
|
|
782
|
+
if (typeof query._subscribe === 'function') {
|
|
783
|
+
query._subscribing = query._subscribe()
|
|
784
|
+
.then(() => {
|
|
785
|
+
query._subscribing = undefined
|
|
786
|
+
query.initialized = undefined
|
|
787
|
+
query.init?.()
|
|
788
|
+
}, err => {
|
|
789
|
+
query._subscribing = undefined
|
|
790
|
+
throw err
|
|
791
|
+
})
|
|
792
|
+
await query._subscribing
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
await query.subscribe({ mode })
|
|
796
|
+
if (query.activeTransportMode == null || query.activeTransportMode === 'idle') {
|
|
797
|
+
query.activeTransportMode = mode
|
|
798
|
+
}
|
|
799
|
+
if (query.initialized !== true) query.init?.()
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async function unsubscribeQueryTransport (query, { keepRoots = true } = {}) {
|
|
803
|
+
if (query.initialized) {
|
|
804
|
+
query.initialized = undefined
|
|
805
|
+
query._detachTransportData?.({ keepRoots })
|
|
806
|
+
}
|
|
807
|
+
if (typeof query._unsubscribe === 'function') {
|
|
808
|
+
await query._unsubscribe()
|
|
809
|
+
return
|
|
810
|
+
}
|
|
811
|
+
await query.unsubscribe?.()
|
|
812
|
+
query.activeTransportMode = 'idle'
|
|
813
|
+
}
|
package/orm/Root.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import getSignal from './getSignal.js'
|
|
2
2
|
import disposeRootContext from './disposeRootContext.js'
|
|
3
|
-
import { reviveRootContext } from './rootContext.js'
|
|
3
|
+
import { getRootContext, reviveRootContext } from './rootContext.js'
|
|
4
4
|
import { isGlobalRootId, normalizeRootId } from './rootScope.js'
|
|
5
5
|
import FinalizationRegistry from '../utils/MockFinalizationRegistry.js'
|
|
6
6
|
|
|
@@ -22,11 +22,13 @@ const REGISTERED_ROOT_SIGNALS = new WeakSet()
|
|
|
22
22
|
// TODO: create a separate local root for private collections
|
|
23
23
|
export function getRootSignal ({
|
|
24
24
|
rootFunction,
|
|
25
|
+
fetchOnly,
|
|
25
26
|
// connection,
|
|
26
27
|
rootId = '_' + createRandomString(8),
|
|
27
28
|
...options
|
|
28
29
|
}) {
|
|
29
30
|
reviveRootContext(rootId)
|
|
31
|
+
getRootContext(rootId, true, { fetchOnly })
|
|
30
32
|
const $root = getSignal(undefined, [], {
|
|
31
33
|
rootId,
|
|
32
34
|
...options
|
|
@@ -39,11 +41,28 @@ export function getRootSignal ({
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export function getRoot (signal) {
|
|
44
|
+
if (!signal) return undefined
|
|
42
45
|
if (signal[ROOT]) return signal[ROOT]
|
|
43
46
|
else if (signal[ROOT_ID]) return signal
|
|
44
47
|
else return undefined
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
export function getRootFetchOnly (rootOrRootId) {
|
|
51
|
+
const $root = typeof rootOrRootId === 'string'
|
|
52
|
+
? undefined
|
|
53
|
+
: (getRoot(rootOrRootId) || rootOrRootId)
|
|
54
|
+
const rootId = typeof rootOrRootId === 'string'
|
|
55
|
+
? rootOrRootId
|
|
56
|
+
: $root?.[ROOT_ID]
|
|
57
|
+
const context = getRootContext(rootId, false)
|
|
58
|
+
return context?.getFetchOnly() ?? false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getRootTransportMode (rootOrRootId, intent = 'subscribe') {
|
|
62
|
+
if (intent === 'fetch') return 'fetch'
|
|
63
|
+
return getRootFetchOnly(rootOrRootId) ? 'fetch' : 'subscribe'
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
export function registerRootFinalizer ($root) {
|
|
48
67
|
if (!$root?.[ROOT_ID]) return
|
|
49
68
|
if (REGISTERED_ROOT_SIGNALS.has($root)) return
|
package/orm/SignalBase.js
CHANGED
|
@@ -34,7 +34,7 @@ import { docSubscriptions } from './Doc.js'
|
|
|
34
34
|
import { IS_QUERY, HASH, QUERIES } from './Query.js'
|
|
35
35
|
import { AGGREGATIONS, IS_AGGREGATION, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js'
|
|
36
36
|
import { ROOT_FUNCTION, ROOT_ID, getRoot } from './Root.js'
|
|
37
|
-
import {
|
|
37
|
+
import { isPrivateMutationForbidden } from './connection.js'
|
|
38
38
|
import {
|
|
39
39
|
DEFAULT_ID_FIELDS,
|
|
40
40
|
getIdFieldsForSegments,
|
|
@@ -300,7 +300,7 @@ export class Signal extends Function {
|
|
|
300
300
|
if (isPublicCollection(this[SEGMENTS][0])) {
|
|
301
301
|
await _setPublicDoc(this[SEGMENTS], value)
|
|
302
302
|
} else {
|
|
303
|
-
if (
|
|
303
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
304
304
|
setPrivateData(getOwningRootId(this), this[SEGMENTS], value)
|
|
305
305
|
}
|
|
306
306
|
}
|
|
@@ -330,7 +330,7 @@ export class Signal extends Function {
|
|
|
330
330
|
const idFields = getIdFieldsForSegments(segments)
|
|
331
331
|
if (isIdFieldPath(segments, idFields)) return
|
|
332
332
|
if (isPublicCollection(segments[0])) return _arrayPushPublic(segments, value)
|
|
333
|
-
if (
|
|
333
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
334
334
|
return arrayPushPrivateData(getOwningRootId(this), segments, value)
|
|
335
335
|
}
|
|
336
336
|
|
|
@@ -340,7 +340,7 @@ export class Signal extends Function {
|
|
|
340
340
|
const idFields = getIdFieldsForSegments(segments)
|
|
341
341
|
if (isIdFieldPath(segments, idFields)) return
|
|
342
342
|
if (isPublicCollection(segments[0])) return _arrayPopPublic(segments)
|
|
343
|
-
if (
|
|
343
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
344
344
|
return arrayPopPrivateData(getOwningRootId(this), segments)
|
|
345
345
|
}
|
|
346
346
|
|
|
@@ -350,7 +350,7 @@ export class Signal extends Function {
|
|
|
350
350
|
const idFields = getIdFieldsForSegments(segments)
|
|
351
351
|
if (isIdFieldPath(segments, idFields)) return
|
|
352
352
|
if (isPublicCollection(segments[0])) return _arrayUnshiftPublic(segments, value)
|
|
353
|
-
if (
|
|
353
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
354
354
|
return arrayUnshiftPrivateData(getOwningRootId(this), segments, value)
|
|
355
355
|
}
|
|
356
356
|
|
|
@@ -360,7 +360,7 @@ export class Signal extends Function {
|
|
|
360
360
|
const idFields = getIdFieldsForSegments(segments)
|
|
361
361
|
if (isIdFieldPath(segments, idFields)) return
|
|
362
362
|
if (isPublicCollection(segments[0])) return _arrayShiftPublic(segments)
|
|
363
|
-
if (
|
|
363
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
364
364
|
return arrayShiftPrivateData(getOwningRootId(this), segments)
|
|
365
365
|
}
|
|
366
366
|
|
|
@@ -374,7 +374,7 @@ export class Signal extends Function {
|
|
|
374
374
|
const idFields = getIdFieldsForSegments(segments)
|
|
375
375
|
if (isIdFieldPath(segments, idFields)) return
|
|
376
376
|
if (isPublicCollection(segments[0])) return _arrayInsertPublic(segments, index, values)
|
|
377
|
-
if (
|
|
377
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
378
378
|
return arrayInsertPrivateData(getOwningRootId(this), segments, index, values)
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -388,7 +388,7 @@ export class Signal extends Function {
|
|
|
388
388
|
const idFields = getIdFieldsForSegments(segments)
|
|
389
389
|
if (isIdFieldPath(segments, idFields)) return
|
|
390
390
|
if (isPublicCollection(segments[0])) return _arrayRemovePublic(segments, index, howMany)
|
|
391
|
-
if (
|
|
391
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
392
392
|
return arrayRemovePrivateData(getOwningRootId(this), segments, index, howMany)
|
|
393
393
|
}
|
|
394
394
|
|
|
@@ -402,7 +402,7 @@ export class Signal extends Function {
|
|
|
402
402
|
const idFields = getIdFieldsForSegments(segments)
|
|
403
403
|
if (isIdFieldPath(segments, idFields)) return
|
|
404
404
|
if (isPublicCollection(segments[0])) return _arrayMovePublic(segments, from, to, howMany)
|
|
405
|
-
if (
|
|
405
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
406
406
|
return arrayMovePrivateData(getOwningRootId(this), segments, from, to, howMany)
|
|
407
407
|
}
|
|
408
408
|
|
|
@@ -416,7 +416,7 @@ export class Signal extends Function {
|
|
|
416
416
|
const idFields = getIdFieldsForSegments(segments)
|
|
417
417
|
if (isIdFieldPath(segments, idFields)) return
|
|
418
418
|
if (isPublicCollection(segments[0])) return _stringInsertPublic(segments, index, text)
|
|
419
|
-
if (
|
|
419
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
420
420
|
return stringInsertPrivateData(getOwningRootId(this), segments, index, text)
|
|
421
421
|
}
|
|
422
422
|
|
|
@@ -430,7 +430,7 @@ export class Signal extends Function {
|
|
|
430
430
|
const idFields = getIdFieldsForSegments(segments)
|
|
431
431
|
if (isIdFieldPath(segments, idFields)) return
|
|
432
432
|
if (isPublicCollection(segments[0])) return _stringRemovePublic(segments, index, howMany)
|
|
433
|
-
if (
|
|
433
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
434
434
|
return stringRemovePrivateData(getOwningRootId(this), segments, index, howMany)
|
|
435
435
|
}
|
|
436
436
|
|
|
@@ -449,7 +449,7 @@ export class Signal extends Function {
|
|
|
449
449
|
await _incrementPublic(segments, value)
|
|
450
450
|
return currentValue + value
|
|
451
451
|
}
|
|
452
|
-
if (
|
|
452
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
453
453
|
setReplacePrivateData(getOwningRootId(this), segments, currentValue + value)
|
|
454
454
|
return currentValue + value
|
|
455
455
|
}
|
|
@@ -471,7 +471,7 @@ export class Signal extends Function {
|
|
|
471
471
|
if (this[SEGMENTS].length === 1) throw Error('Can\'t delete the whole collection')
|
|
472
472
|
await _setPublicDoc(this[SEGMENTS], undefined, true)
|
|
473
473
|
} else {
|
|
474
|
-
if (
|
|
474
|
+
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
475
475
|
delPrivateData(getOwningRootId(this), this[SEGMENTS])
|
|
476
476
|
}
|
|
477
477
|
}
|
package/orm/connection.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isCompatEnv } from './compatEnv.js'
|
|
2
|
+
|
|
1
3
|
export let connection
|
|
2
|
-
|
|
4
|
+
let defaultFetchOnly
|
|
3
5
|
export let publicOnly
|
|
4
6
|
|
|
5
7
|
export function setConnection (_connection) {
|
|
@@ -11,14 +13,27 @@ export function getConnection () {
|
|
|
11
13
|
return connection
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
export function setDefaultFetchOnly (_fetchOnly) {
|
|
17
|
+
defaultFetchOnly = !!_fetchOnly
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDefaultFetchOnly () {
|
|
21
|
+
return !!defaultFetchOnly
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Deprecated alias kept for internal transition.
|
|
14
25
|
export function setFetchOnly (_fetchOnly) {
|
|
15
|
-
|
|
26
|
+
setDefaultFetchOnly(_fetchOnly)
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export function setPublicOnly (_publicOnly) {
|
|
19
30
|
publicOnly = _publicOnly
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
export function isPrivateMutationForbidden () {
|
|
34
|
+
return !!publicOnly && !isCompatEnv()
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
const ERRORS = {
|
|
23
38
|
notSet: `
|
|
24
39
|
Connection is not set.
|
package/orm/rootContext.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { observable } from '@nx-js/observer-util'
|
|
2
2
|
import { normalizeRootId } from './rootScope.js'
|
|
3
|
+
import { getDefaultFetchOnly } from './connection.js'
|
|
3
4
|
|
|
4
5
|
const ROOT_CONTEXTS = new Map()
|
|
5
6
|
const CLOSED_ROOT_CONTEXTS = new Set()
|
|
@@ -9,8 +10,9 @@ const RUNTIME_KIND_QUERY = 'query'
|
|
|
9
10
|
const RUNTIME_KIND_AGGREGATION = 'aggregation'
|
|
10
11
|
|
|
11
12
|
export default class RootContext {
|
|
12
|
-
constructor (rootId) {
|
|
13
|
+
constructor (rootId, { fetchOnly } = {}) {
|
|
13
14
|
this.rootId = normalizeRootId(rootId)
|
|
15
|
+
this.fetchOnly = fetchOnly == null ? getDefaultFetchOnly() : !!fetchOnly
|
|
14
16
|
this.privateDataRaw = {}
|
|
15
17
|
this.privateData = observable(this.privateDataRaw)
|
|
16
18
|
this.refLinks = new Map()
|
|
@@ -34,6 +36,14 @@ export default class RootContext {
|
|
|
34
36
|
return store
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
getFetchOnly () {
|
|
40
|
+
return !!this.fetchOnly
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setFetchOnly (value) {
|
|
44
|
+
this.fetchOnly = !!value
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
getPrivateDataRoot () {
|
|
38
48
|
return this.privateData
|
|
39
49
|
}
|
|
@@ -164,12 +174,12 @@ export default class RootContext {
|
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
export function getRootContext (rootId, create = true) {
|
|
177
|
+
export function getRootContext (rootId, create = true, options = {}) {
|
|
168
178
|
const normalizedRootId = normalizeRootId(rootId)
|
|
169
179
|
if (create && CLOSED_ROOT_CONTEXTS.has(normalizedRootId)) return undefined
|
|
170
180
|
let context = ROOT_CONTEXTS.get(normalizedRootId)
|
|
171
181
|
if (!context && create) {
|
|
172
|
-
context = new RootContext(normalizedRootId)
|
|
182
|
+
context = new RootContext(normalizedRootId, options)
|
|
173
183
|
ROOT_CONTEXTS.set(normalizedRootId, context)
|
|
174
184
|
}
|
|
175
185
|
return context
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.83",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
]
|
|
84
84
|
},
|
|
85
85
|
"license": "MIT",
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "cf9710fd110129aafbfc8f0e7260ec3186795cb2"
|
|
87
87
|
}
|
package/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createChannel from '@teamplay/channel/server'
|
|
2
|
-
import { connection, setConnection,
|
|
2
|
+
import { connection, setConnection, setDefaultFetchOnly, setPublicOnly } from './orm/connection.js'
|
|
3
3
|
|
|
4
4
|
export { default as ShareDB } from 'sharedb'
|
|
5
5
|
export {
|
|
@@ -25,7 +25,7 @@ export function initConnection (backend, {
|
|
|
25
25
|
if (!backend) throw Error('backend is required')
|
|
26
26
|
if (connection) throw Error('Connection already exists')
|
|
27
27
|
setConnection(backend.connect())
|
|
28
|
-
|
|
28
|
+
setDefaultFetchOnly(fetchOnly)
|
|
29
29
|
setPublicOnly(publicOnly)
|
|
30
30
|
return createChannel(backend, options)
|
|
31
31
|
}
|