teamplay 0.4.0 → 0.5.0-alpha.1
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/babel-loader.cjs +1 -0
- package/babel.cjs +1 -0
- package/dist/connect/index.d.ts +1 -0
- package/dist/connect/index.js +9 -0
- package/dist/connect/lib/sharedb-crosstab-pubsub.d.cts +10 -0
- package/dist/connect/offline/index.d.ts +1 -0
- package/dist/connect/offline/index.js +126 -0
- package/dist/connect/offline/react-native.d.ts +10 -0
- package/dist/connect/offline/react-native.js +25 -0
- package/dist/connect/offline/web.d.ts +9 -0
- package/dist/connect/offline/web.js +12 -0
- package/dist/connect/sharedbConnection.d.cts +2 -0
- package/dist/connect/test.d.ts +1 -0
- package/dist/connect/test.js +12 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +74 -0
- package/dist/orm/$.d.ts +9 -0
- package/dist/orm/$.js +34 -0
- package/dist/orm/Aggregation.d.ts +17 -0
- package/dist/orm/Aggregation.js +115 -0
- package/dist/orm/Cache.d.ts +9 -0
- package/dist/orm/Cache.js +32 -0
- package/dist/orm/Compat/SignalCompat.d.ts +3 -0
- package/dist/orm/Compat/SignalCompat.js +1542 -0
- package/dist/orm/Compat/eventsCompat.d.ts +3 -0
- package/dist/orm/Compat/eventsCompat.js +73 -0
- package/dist/orm/Compat/hooksCompat.d.ts +33 -0
- package/dist/orm/Compat/hooksCompat.js +360 -0
- package/dist/orm/Compat/modelEvents.d.ts +6 -0
- package/dist/orm/Compat/modelEvents.js +228 -0
- package/dist/orm/Compat/queryReadiness.d.ts +5 -0
- package/dist/orm/Compat/queryReadiness.js +185 -0
- package/dist/orm/Compat/refFallback.d.ts +13 -0
- package/dist/orm/Compat/refFallback.js +65 -0
- package/dist/orm/Compat/refRegistry.d.ts +6 -0
- package/dist/orm/Compat/refRegistry.js +54 -0
- package/dist/orm/Compat/silentContext.d.ts +5 -0
- package/dist/orm/Compat/silentContext.js +48 -0
- package/dist/orm/Compat/startStopCompat.d.ts +3 -0
- package/dist/orm/Compat/startStopCompat.js +217 -0
- package/dist/orm/Doc.d.ts +96 -0
- package/dist/orm/Doc.js +966 -0
- package/dist/orm/Query.d.ts +117 -0
- package/dist/orm/Query.js +1111 -0
- package/dist/orm/Reaction.d.ts +10 -0
- package/dist/orm/Reaction.js +41 -0
- package/dist/orm/Root.d.ts +22 -0
- package/dist/orm/Root.js +85 -0
- package/dist/orm/Signal.d.ts +12 -0
- package/dist/orm/Signal.js +7 -0
- package/dist/orm/SignalBase.d.ts +168 -0
- package/dist/orm/SignalBase.js +625 -0
- package/dist/orm/SubscriptionState.d.ts +17 -0
- package/dist/orm/SubscriptionState.js +123 -0
- package/dist/orm/Value.d.ts +9 -0
- package/dist/orm/Value.js +25 -0
- package/dist/orm/addModel.d.ts +6 -0
- package/dist/orm/addModel.js +39 -0
- package/dist/orm/associations.d.ts +18 -0
- package/dist/orm/associations.js +70 -0
- package/dist/orm/batchScheduler.d.ts +7 -0
- package/dist/orm/batchScheduler.js +62 -0
- package/dist/orm/compatEnv.d.ts +1 -0
- package/dist/orm/compatEnv.js +4 -0
- package/dist/orm/connection.d.ts +26 -0
- package/dist/orm/connection.js +38 -0
- package/dist/orm/dataTree.d.ts +34 -0
- package/dist/orm/dataTree.js +880 -0
- package/dist/orm/disposeRootContext.d.ts +4 -0
- package/dist/orm/disposeRootContext.js +59 -0
- package/dist/orm/getSignal.d.ts +16 -0
- package/dist/orm/getSignal.js +133 -0
- package/dist/orm/idFields.d.ts +14 -0
- package/dist/orm/idFields.js +95 -0
- package/dist/orm/index.d.ts +7 -0
- package/dist/orm/index.js +6 -0
- package/dist/orm/initModels.d.ts +5 -0
- package/dist/orm/initModels.js +74 -0
- package/dist/orm/missingDoc.d.ts +1 -0
- package/dist/orm/missingDoc.js +3 -0
- package/dist/orm/pluralize.d.ts +12 -0
- package/dist/orm/privateData.d.ts +22 -0
- package/dist/orm/privateData.js +170 -0
- package/dist/orm/rootContext.d.ts +78 -0
- package/dist/orm/rootContext.js +297 -0
- package/dist/orm/rootScope.d.ts +12 -0
- package/dist/orm/rootScope.js +46 -0
- package/dist/orm/signalArrayReaders.d.ts +22 -0
- package/dist/orm/signalArrayReaders.js +42 -0
- package/dist/orm/signalMetadata.d.ts +17 -0
- package/dist/orm/signalMetadata.js +56 -0
- package/dist/orm/signalMutationGuards.d.ts +4 -0
- package/dist/orm/signalMutationGuards.js +14 -0
- package/dist/orm/signalPathKind.d.ts +2 -0
- package/dist/orm/signalPathKind.js +10 -0
- package/dist/orm/signalPathRules.d.ts +6 -0
- package/dist/orm/signalPathRules.js +28 -0
- package/dist/orm/signalReads.d.ts +26 -0
- package/dist/orm/signalReads.js +64 -0
- package/dist/orm/signalRuntimeAccess.d.ts +16 -0
- package/dist/orm/signalRuntimeAccess.js +24 -0
- package/dist/orm/signalRuntimeDescriptor.d.ts +19 -0
- package/dist/orm/signalRuntimeDescriptor.js +97 -0
- package/dist/orm/signalStorageMutations.d.ts +18 -0
- package/dist/orm/signalStorageMutations.js +26 -0
- package/dist/orm/signalSymbols.d.ts +5 -0
- package/dist/orm/signalSymbols.js +5 -0
- package/dist/orm/signalValueMutations.d.ts +14 -0
- package/dist/orm/signalValueMutations.js +36 -0
- package/dist/orm/sub.d.ts +37 -0
- package/dist/orm/sub.js +187 -0
- package/dist/orm/subscriptionGcDelay.d.ts +4 -0
- package/dist/orm/subscriptionGcDelay.js +26 -0
- package/dist/orm/types/baseMethods.d.ts +43 -0
- package/dist/orm/types/baseMethods.js +1 -0
- package/dist/orm/types/jsonSchema.d.ts +75 -0
- package/dist/orm/types/jsonSchema.js +1 -0
- package/dist/orm/types/modelManifest.d.ts +37 -0
- package/dist/orm/types/modelManifest.js +1 -0
- package/dist/orm/types/path.d.ts +8 -0
- package/dist/orm/types/path.js +1 -0
- package/dist/orm/types/query.d.ts +29 -0
- package/dist/orm/types/query.js +1 -0
- package/dist/orm/types/signal.d.ts +132 -0
- package/dist/orm/types/signal.js +1 -0
- package/dist/react/compatComponentRegistry.d.ts +4 -0
- package/dist/react/compatComponentRegistry.js +19 -0
- package/dist/react/convertToObserver.d.ts +26 -0
- package/dist/react/convertToObserver.js +114 -0
- package/dist/react/executionContextTracker.d.ts +11 -0
- package/dist/react/executionContextTracker.js +28 -0
- package/dist/react/helpers.d.ts +34 -0
- package/dist/react/helpers.js +134 -0
- package/dist/react/observer.d.ts +22 -0
- package/dist/react/observer.js +8 -0
- package/dist/react/promiseBatcher.d.ts +13 -0
- package/dist/react/promiseBatcher.js +118 -0
- package/dist/react/renderAttemptDestroyer.d.ts +19 -0
- package/dist/react/renderAttemptDestroyer.js +46 -0
- package/dist/react/trapRender.d.ts +6 -0
- package/dist/react/trapRender.js +48 -0
- package/dist/react/universal$.d.ts +1 -0
- package/dist/react/universal$.js +18 -0
- package/dist/react/universalSub.d.ts +1 -0
- package/dist/react/universalSub.js +22 -0
- package/dist/react/useApi.d.ts +13 -0
- package/dist/react/useApi.js +67 -0
- package/dist/react/useSub.d.ts +102 -0
- package/dist/react/useSub.js +189 -0
- package/dist/react/useSuspendMemo.d.ts +3 -0
- package/dist/react/useSuspendMemo.js +102 -0
- package/dist/react/wrapIntoSuspense.d.ts +9 -0
- package/dist/react/wrapIntoSuspense.js +102 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +28 -0
- package/dist/utils/MockFinalizationRegistry.d.ts +37 -0
- package/dist/utils/MockFinalizationRegistry.js +85 -0
- package/dist/utils/MockWeakRef.d.ts +12 -0
- package/dist/utils/MockWeakRef.js +23 -0
- package/dist/utils/isServer.d.ts +3 -0
- package/dist/utils/isServer.js +18 -0
- package/dist/utils/setDiffDeep.d.ts +1 -0
- package/dist/utils/setDiffDeep.js +61 -0
- package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/utils/useIsomorphicLayoutEffect.js +3 -0
- package/file-based-models.js +3 -0
- package/file-based-models.node.js +6 -0
- package/package.json +77 -32
- package/teamplay.models.auto-init.virtual.js +1 -0
- package/teamplay.models.virtual.js +1 -0
- package/connect/index.js +0 -9
- package/connect/offline/index.js +0 -124
- package/connect/offline/react-native.js +0 -28
- package/connect/offline/web.js +0 -15
- package/connect/test.js +0 -12
- package/index.d.ts +0 -106
- package/index.js +0 -125
- package/orm/$.js +0 -38
- package/orm/Aggregation.js +0 -117
- package/orm/Cache.js +0 -46
- package/orm/Compat/README.md +0 -1064
- package/orm/Compat/SignalCompat.js +0 -1479
- package/orm/Compat/eventsCompat.js +0 -79
- package/orm/Compat/hooksCompat.js +0 -374
- package/orm/Compat/modelEvents.js +0 -225
- package/orm/Compat/queryReadiness.js +0 -191
- package/orm/Compat/refFallback.js +0 -62
- package/orm/Compat/refRegistry.js +0 -61
- package/orm/Compat/silentContext.js +0 -51
- package/orm/Compat/startStopCompat.js +0 -207
- package/orm/Doc.js +0 -969
- package/orm/Query.js +0 -1127
- package/orm/Reaction.js +0 -48
- package/orm/Root.js +0 -88
- package/orm/Signal.js +0 -22
- package/orm/SignalBase.js +0 -696
- package/orm/SubscriptionState.js +0 -138
- package/orm/Value.js +0 -30
- package/orm/addModel.js +0 -31
- package/orm/associations.js +0 -97
- package/orm/batchScheduler.js +0 -62
- package/orm/compatEnv.js +0 -4
- package/orm/connection.js +0 -46
- package/orm/dataTree.js +0 -869
- package/orm/disposeRootContext.js +0 -68
- package/orm/getSignal.js +0 -130
- package/orm/idFields.js +0 -88
- package/orm/index.d.ts +0 -6
- package/orm/index.js +0 -5
- package/orm/missingDoc.js +0 -3
- package/orm/privateData.js +0 -181
- package/orm/rootContext.js +0 -313
- package/orm/rootScope.js +0 -51
- package/orm/sub.js +0 -151
- package/orm/subscriptionGcDelay.js +0 -32
- package/react/compatComponentRegistry.js +0 -20
- package/react/convertToObserver.js +0 -117
- package/react/executionContextTracker.js +0 -32
- package/react/helpers.js +0 -141
- package/react/observer.js +0 -9
- package/react/promiseBatcher.js +0 -115
- package/react/renderAttemptDestroyer.js +0 -47
- package/react/trapRender.js +0 -50
- package/react/universal$.js +0 -18
- package/react/universalSub.js +0 -21
- package/react/useApi.js +0 -63
- package/react/useSub.js +0 -169
- package/react/useSuspendMemo.js +0 -96
- package/react/wrapIntoSuspense.js +0 -119
- package/server.js +0 -31
- package/utils/MockFinalizationRegistry.js +0 -94
- package/utils/MockWeakRef.js +0 -25
- package/utils/isServer.js +0 -17
- package/utils/setDiffDeep.js +0 -58
- package/utils/useIsomorphicLayoutEffect.js +0 -4
- /package/{connect → dist/connect}/lib/sharedb-crosstab-pubsub.cjs +0 -0
- /package/{connect → dist/connect}/sharedbConnection.cjs +0 -0
package/dist/orm/Doc.js
ADDED
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
import { isObservable, observable, raw } from '@nx-js/observer-util';
|
|
2
|
+
import { set as _set, del as _del, getRaw as _getRaw } from './dataTree.js';
|
|
3
|
+
import { SEGMENTS } from "./Signal.js";
|
|
4
|
+
import { getConnection } from "./connection.js";
|
|
5
|
+
import FinalizationRegistry from "../utils/MockFinalizationRegistry.js";
|
|
6
|
+
import SubscriptionState from './SubscriptionState.js';
|
|
7
|
+
import { getIdFieldsForSegments, injectIdFields, isPlainObject } from "./idFields.js";
|
|
8
|
+
import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js';
|
|
9
|
+
import { getSubscriptionGcDelay } from "./subscriptionGcDelay.js";
|
|
10
|
+
import { isMissingShareDoc } from './missingDoc.js';
|
|
11
|
+
import { getRoot, ROOT_ID, GLOBAL_ROOT_ID, getRootTransportMode } from "./Root.js";
|
|
12
|
+
import { registerRootOwnedDirectDocSubscription, unregisterRootOwnedDirectDocSubscription, getRootOwnedDirectDocSubscriptions, clearRootOwnedDirectDocSubscriptions } from "./rootContext.js";
|
|
13
|
+
const ERROR_ON_EXCESSIVE_UNSUBSCRIBES = false;
|
|
14
|
+
const DOC_FINALIZATION_TOKENS = new WeakMap();
|
|
15
|
+
function getDocFinalizationToken($doc) {
|
|
16
|
+
let token = DOC_FINALIZATION_TOKENS.get($doc);
|
|
17
|
+
if (!token) {
|
|
18
|
+
token = {};
|
|
19
|
+
DOC_FINALIZATION_TOKENS.set($doc, token);
|
|
20
|
+
}
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
function getOwningRootId($doc) {
|
|
24
|
+
const $root = getRoot($doc);
|
|
25
|
+
const rootId = $root?.[ROOT_ID];
|
|
26
|
+
if (rootId == null || rootId === GLOBAL_ROOT_ID)
|
|
27
|
+
return undefined;
|
|
28
|
+
return rootId;
|
|
29
|
+
}
|
|
30
|
+
function deepEqualDocData(left, right) {
|
|
31
|
+
if (left === right)
|
|
32
|
+
return true;
|
|
33
|
+
if (left == null || right == null)
|
|
34
|
+
return left === right;
|
|
35
|
+
const leftIsArray = Array.isArray(left);
|
|
36
|
+
if (leftIsArray || Array.isArray(right)) {
|
|
37
|
+
if (!leftIsArray || !Array.isArray(right))
|
|
38
|
+
return false;
|
|
39
|
+
if (left.length !== right.length)
|
|
40
|
+
return false;
|
|
41
|
+
for (let i = 0; i < left.length; i++) {
|
|
42
|
+
if (!deepEqualDocData(left[i], right[i]))
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (typeof left !== 'object' || typeof right !== 'object')
|
|
48
|
+
return false;
|
|
49
|
+
const leftKeys = Object.keys(left);
|
|
50
|
+
const rightKeys = Object.keys(right);
|
|
51
|
+
if (leftKeys.length !== rightKeys.length)
|
|
52
|
+
return false;
|
|
53
|
+
for (const key of leftKeys) {
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(right, key))
|
|
55
|
+
return false;
|
|
56
|
+
if (!deepEqualDocData(left[key], right[key]))
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
class Doc {
|
|
62
|
+
initialized;
|
|
63
|
+
constructor(collection, docId) {
|
|
64
|
+
this.collection = collection;
|
|
65
|
+
this.docId = docId;
|
|
66
|
+
this.lifecycle = new SubscriptionState({
|
|
67
|
+
onSubscribe: () => this._subscribe(),
|
|
68
|
+
onUnsubscribe: () => this._unsubscribe()
|
|
69
|
+
});
|
|
70
|
+
this.requestedTransportMode = 'subscribe';
|
|
71
|
+
this.activeTransportMode = 'idle';
|
|
72
|
+
this.init();
|
|
73
|
+
}
|
|
74
|
+
get subscribed() {
|
|
75
|
+
return this.lifecycle.subscribed;
|
|
76
|
+
}
|
|
77
|
+
init() {
|
|
78
|
+
if (this.initialized)
|
|
79
|
+
return;
|
|
80
|
+
this.initialized = true;
|
|
81
|
+
this._initData();
|
|
82
|
+
}
|
|
83
|
+
async subscribe({ mode } = {}) {
|
|
84
|
+
if (mode)
|
|
85
|
+
this.requestedTransportMode = mode;
|
|
86
|
+
await this.lifecycle.subscribe();
|
|
87
|
+
this.init();
|
|
88
|
+
}
|
|
89
|
+
async unsubscribe() {
|
|
90
|
+
await this.lifecycle.unsubscribe();
|
|
91
|
+
}
|
|
92
|
+
async _subscribe() {
|
|
93
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
94
|
+
const mode = this.requestedTransportMode;
|
|
95
|
+
await new Promise((resolve, reject) => {
|
|
96
|
+
const method = mode === 'fetch' ? 'fetch' : 'subscribe';
|
|
97
|
+
doc[method](err => {
|
|
98
|
+
if (err)
|
|
99
|
+
return reject(err);
|
|
100
|
+
this.activeTransportMode = mode;
|
|
101
|
+
resolve();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async _unsubscribe() {
|
|
106
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
107
|
+
await new Promise((resolve, reject) => {
|
|
108
|
+
const method = this.activeTransportMode === 'fetch' && typeof doc.unfetch === 'function'
|
|
109
|
+
? 'unfetch'
|
|
110
|
+
: 'unsubscribe';
|
|
111
|
+
doc[method](err => {
|
|
112
|
+
if (err)
|
|
113
|
+
return reject(err);
|
|
114
|
+
this.activeTransportMode = 'idle';
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
hasPending() {
|
|
120
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
121
|
+
if (typeof doc.hasPending !== 'function')
|
|
122
|
+
return false;
|
|
123
|
+
return doc.hasPending();
|
|
124
|
+
}
|
|
125
|
+
whenNothingPending(fn) {
|
|
126
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
127
|
+
if (typeof doc.whenNothingPending !== 'function')
|
|
128
|
+
return fn();
|
|
129
|
+
doc.whenNothingPending(fn);
|
|
130
|
+
}
|
|
131
|
+
async destroy() {
|
|
132
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
doc.destroy(err => {
|
|
135
|
+
if (err)
|
|
136
|
+
return reject(err);
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
dispose() {
|
|
142
|
+
this.initialized = undefined;
|
|
143
|
+
this._removeData();
|
|
144
|
+
}
|
|
145
|
+
_initData() {
|
|
146
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
147
|
+
this._refData();
|
|
148
|
+
doc.on('load', () => this._refData());
|
|
149
|
+
doc.on('create', () => this._refData());
|
|
150
|
+
doc.on('del', () => this._refMissingData());
|
|
151
|
+
if (isModelEventsEnabled()) {
|
|
152
|
+
doc.on('op', op => emitDocOp(this.collection, this.docId, op));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_refMissingData() {
|
|
156
|
+
_del([this.collection, this.docId]);
|
|
157
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
158
|
+
doc.data = observable(undefined);
|
|
159
|
+
}
|
|
160
|
+
_refData() {
|
|
161
|
+
const doc = getConnection().get(this.collection, this.docId);
|
|
162
|
+
// Racer/react-sharedb-hooks normalizes a missing ShareDB doc into a truthy
|
|
163
|
+
// observable placeholder on the shareDoc itself (`observable(undefined) -> {}`),
|
|
164
|
+
// while still keeping the model tree path unresolved. Some legacy consumers
|
|
165
|
+
// (for example readonly RTEditor paths) rely on this exact contract by reading
|
|
166
|
+
// `connection.get(...).data` directly and only checking for truthiness.
|
|
167
|
+
//
|
|
168
|
+
// We intentionally mirror that behavior here:
|
|
169
|
+
// - missing doc => keep model path undefined
|
|
170
|
+
// - but make shareDoc.data truthy/observable so direct ShareDB consumers behave
|
|
171
|
+
// the same way they do under Racer.
|
|
172
|
+
if (isMissingShareDoc(doc) && doc.data === undefined) {
|
|
173
|
+
if (!isObservable(doc.data))
|
|
174
|
+
doc.data = observable(undefined);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (doc.data == null)
|
|
178
|
+
return;
|
|
179
|
+
const idFields = getIdFieldsForSegments([this.collection, this.docId]);
|
|
180
|
+
if (isPlainObject(doc.data))
|
|
181
|
+
injectIdFields(doc.data, idFields, this.docId);
|
|
182
|
+
const path = [this.collection, this.docId];
|
|
183
|
+
const data = isObservable(doc.data) ? raw(doc.data) : doc.data;
|
|
184
|
+
const current = _getRaw(path);
|
|
185
|
+
if (deepEqualDocData(current, data)) {
|
|
186
|
+
if (current != null && current !== raw(doc.data))
|
|
187
|
+
doc.data = current;
|
|
188
|
+
if (!isObservable(doc.data))
|
|
189
|
+
doc.data = observable(doc.data);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
_set(path, data);
|
|
193
|
+
const synced = _getRaw(path);
|
|
194
|
+
if (synced != null && synced !== raw(doc.data))
|
|
195
|
+
doc.data = synced;
|
|
196
|
+
if (!isObservable(doc.data))
|
|
197
|
+
doc.data = observable(doc.data);
|
|
198
|
+
}
|
|
199
|
+
_removeData() {
|
|
200
|
+
_del([this.collection, this.docId]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export class DocSubscriptions {
|
|
204
|
+
constructor(DocClass = Doc) {
|
|
205
|
+
this.DocClass = DocClass;
|
|
206
|
+
this.ownerRecords = new Map(); // ownerKey -> owner record
|
|
207
|
+
this.entries = new Map(); // transportHash -> transport entry
|
|
208
|
+
this.fr = new FinalizationRegistry(({ hash, ownerKey }) => this.destroyByOwnerKey(ownerKey, { hash, force: true }));
|
|
209
|
+
this.subCount = createReadonlyMapView({
|
|
210
|
+
get: hash => this.getTrackedCount(hash),
|
|
211
|
+
has: hash => this.getTrackedCount(hash) !== undefined,
|
|
212
|
+
size: () => this.getTrackedHashCountSize(),
|
|
213
|
+
keys: () => getTrackedHashes(this.entries)
|
|
214
|
+
});
|
|
215
|
+
this.ownerFetchCount = createReadonlyMapView({
|
|
216
|
+
get: ownerKey => {
|
|
217
|
+
const count = this.ownerRecords.get(ownerKey)?.fetchCount;
|
|
218
|
+
return count > 0 ? count : undefined;
|
|
219
|
+
},
|
|
220
|
+
has: ownerKey => !!this.ownerRecords.get(ownerKey)?.fetchCount,
|
|
221
|
+
size: () => countMapLike(this.ownerRecords, record => record.fetchCount > 0),
|
|
222
|
+
keys: () => filterMapKeys(this.ownerRecords, record => record.fetchCount > 0)
|
|
223
|
+
});
|
|
224
|
+
this.ownerSubscribeCount = createReadonlyMapView({
|
|
225
|
+
get: ownerKey => {
|
|
226
|
+
const count = this.ownerRecords.get(ownerKey)?.subscribeCount;
|
|
227
|
+
return count > 0 ? count : undefined;
|
|
228
|
+
},
|
|
229
|
+
has: ownerKey => !!this.ownerRecords.get(ownerKey)?.subscribeCount,
|
|
230
|
+
size: () => countMapLike(this.ownerRecords, record => record.subscribeCount > 0),
|
|
231
|
+
keys: () => filterMapKeys(this.ownerRecords, record => record.subscribeCount > 0)
|
|
232
|
+
});
|
|
233
|
+
this.ownerMeta = createReadonlyMapView({
|
|
234
|
+
get: ownerKey => this.getOwnerMeta(ownerKey),
|
|
235
|
+
has: ownerKey => this.ownerRecords.has(ownerKey),
|
|
236
|
+
size: () => this.ownerRecords.size,
|
|
237
|
+
keys: () => this.ownerRecords.keys()
|
|
238
|
+
});
|
|
239
|
+
this.ownerKeysByHash = createReadonlyMapView({
|
|
240
|
+
get: hash => this.getOwnerKeys(hash),
|
|
241
|
+
has: hash => !!this.getOwnerKeys(hash),
|
|
242
|
+
size: () => countMapLike(this.entries, entry => entry.owners.size > 0),
|
|
243
|
+
keys: () => filterMapKeys(this.entries, entry => entry.owners.size > 0)
|
|
244
|
+
});
|
|
245
|
+
this.docs = createReadonlyMapView({
|
|
246
|
+
get: hash => this.getRuntime(hash),
|
|
247
|
+
has: hash => this.hasRuntime(hash),
|
|
248
|
+
size: () => this.getRuntimeCount(),
|
|
249
|
+
keys: () => filterMapKeys(this.entries, entry => !!entry.runtime)
|
|
250
|
+
});
|
|
251
|
+
this.pendingDestroyTimers = createReadonlyMapView({
|
|
252
|
+
get: hash => this.entries.get(hash)?.pendingDestroy,
|
|
253
|
+
has: hash => !!this.entries.get(hash)?.pendingDestroy,
|
|
254
|
+
size: () => countMapLike(this.entries, entry => !!entry.pendingDestroy),
|
|
255
|
+
keys: () => filterMapKeys(this.entries, entry => !!entry.pendingDestroy)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
getOrCreateOwnerRecord(ownerKey, meta) {
|
|
259
|
+
let record = this.ownerRecords.get(ownerKey);
|
|
260
|
+
if (!record) {
|
|
261
|
+
record = {
|
|
262
|
+
ownerKey,
|
|
263
|
+
rootId: meta.rootId,
|
|
264
|
+
hash: meta.hash,
|
|
265
|
+
segments: meta.segments ? [...meta.segments] : parseDocHash(meta.hash),
|
|
266
|
+
fetchCount: 0,
|
|
267
|
+
subscribeCount: 0
|
|
268
|
+
};
|
|
269
|
+
this.ownerRecords.set(ownerKey, record);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
if (meta.rootId != null)
|
|
273
|
+
record.rootId = meta.rootId;
|
|
274
|
+
if (meta.hash != null)
|
|
275
|
+
record.hash = meta.hash;
|
|
276
|
+
if (meta.segments != null)
|
|
277
|
+
record.segments = [...meta.segments];
|
|
278
|
+
}
|
|
279
|
+
return record;
|
|
280
|
+
}
|
|
281
|
+
getOrCreateEntry(hash, segments) {
|
|
282
|
+
let entry = this.entries.get(hash);
|
|
283
|
+
if (!entry) {
|
|
284
|
+
entry = {
|
|
285
|
+
hash,
|
|
286
|
+
segments: segments ? [...segments] : parseDocHash(hash),
|
|
287
|
+
mode: 'idle',
|
|
288
|
+
targetMode: 'idle',
|
|
289
|
+
phase: 'stable',
|
|
290
|
+
runtime: null,
|
|
291
|
+
owners: new Set(),
|
|
292
|
+
retainCount: 0,
|
|
293
|
+
pendingDestroy: null,
|
|
294
|
+
reconcilePromise: null
|
|
295
|
+
};
|
|
296
|
+
this.entries.set(hash, entry);
|
|
297
|
+
}
|
|
298
|
+
else if (segments && !entry.segments?.length) {
|
|
299
|
+
entry.segments = [...segments];
|
|
300
|
+
}
|
|
301
|
+
return entry;
|
|
302
|
+
}
|
|
303
|
+
getEntry(hash) {
|
|
304
|
+
return this.entries.get(hash);
|
|
305
|
+
}
|
|
306
|
+
getEntryTotalCount(entry) {
|
|
307
|
+
if (!entry)
|
|
308
|
+
return 0;
|
|
309
|
+
let count = entry.retainCount;
|
|
310
|
+
for (const ownerKey of entry.owners) {
|
|
311
|
+
count += this.getOwnerTotalCount(ownerKey);
|
|
312
|
+
}
|
|
313
|
+
return count;
|
|
314
|
+
}
|
|
315
|
+
getEntryTrackedTotal(entry) {
|
|
316
|
+
if (!entry)
|
|
317
|
+
return undefined;
|
|
318
|
+
const total = this.getEntryTotalCount(entry);
|
|
319
|
+
if (total > 0 || entry.pendingDestroy)
|
|
320
|
+
return total;
|
|
321
|
+
}
|
|
322
|
+
syncOwnerMirror() { }
|
|
323
|
+
clearOwnerMirror() { }
|
|
324
|
+
syncEntryMirror() { }
|
|
325
|
+
deleteEntryIfEmpty(hash) {
|
|
326
|
+
const entry = this.entries.get(hash);
|
|
327
|
+
if (!entry)
|
|
328
|
+
return;
|
|
329
|
+
if (!this.canDeleteEntry(entry))
|
|
330
|
+
return;
|
|
331
|
+
this.entries.delete(hash);
|
|
332
|
+
}
|
|
333
|
+
canDeleteEntry(entry) {
|
|
334
|
+
if (!entry)
|
|
335
|
+
return false;
|
|
336
|
+
if (this.getEntryTrackedTotal(entry) !== undefined)
|
|
337
|
+
return false;
|
|
338
|
+
if (entry.runtime)
|
|
339
|
+
return false;
|
|
340
|
+
if (entry.phase === 'transition')
|
|
341
|
+
return false;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
ensureRuntime(hash, segments) {
|
|
345
|
+
const entry = this.getOrCreateEntry(hash, segments);
|
|
346
|
+
if (!entry.runtime) {
|
|
347
|
+
const runtimeSegments = entry.segments?.length ? entry.segments : parseDocHash(hash);
|
|
348
|
+
entry.runtime = new this.DocClass(...runtimeSegments);
|
|
349
|
+
}
|
|
350
|
+
entry.runtime.init();
|
|
351
|
+
entry.mode = entry.runtime.activeTransportMode || entry.mode;
|
|
352
|
+
this.syncEntryMirror(entry);
|
|
353
|
+
return entry.runtime;
|
|
354
|
+
}
|
|
355
|
+
addOwnerToEntry(record) {
|
|
356
|
+
const entry = this.getOrCreateEntry(record.hash, record.segments);
|
|
357
|
+
entry.owners.add(record.ownerKey);
|
|
358
|
+
this.syncEntryMirror(entry);
|
|
359
|
+
return entry;
|
|
360
|
+
}
|
|
361
|
+
removeOwnerFromEntry(record) {
|
|
362
|
+
const entry = this.entries.get(record.hash);
|
|
363
|
+
if (!entry)
|
|
364
|
+
return;
|
|
365
|
+
entry.owners.delete(record.ownerKey);
|
|
366
|
+
this.syncEntryMirror(entry);
|
|
367
|
+
}
|
|
368
|
+
init($doc) {
|
|
369
|
+
const segments = [...$doc[SEGMENTS]];
|
|
370
|
+
const hash = hashDoc(segments);
|
|
371
|
+
this.getOrCreateEntry(hash, segments);
|
|
372
|
+
this.ensureRuntime(hash, segments);
|
|
373
|
+
}
|
|
374
|
+
subscribe($doc, { intent = 'subscribe' } = {}) {
|
|
375
|
+
const segments = [...$doc[SEGMENTS]];
|
|
376
|
+
const hash = hashDoc(segments);
|
|
377
|
+
const rootId = getOwningRootId($doc);
|
|
378
|
+
const ownerKey = getDocOwnerKey(rootId, hash);
|
|
379
|
+
const token = getDocFinalizationToken($doc);
|
|
380
|
+
const entry = this.getOrCreateEntry(hash, segments);
|
|
381
|
+
const previousCount = this.getEntryTotalCount(entry);
|
|
382
|
+
this.cancelDestroy(hash);
|
|
383
|
+
const record = this.getOrCreateOwnerRecord(ownerKey, { hash, segments, rootId });
|
|
384
|
+
this.incrementOwnerIntent(record, intent);
|
|
385
|
+
this.addOwnerToEntry(record);
|
|
386
|
+
if (rootId) {
|
|
387
|
+
registerRootOwnedDirectDocSubscription(rootId, hash, segments, token);
|
|
388
|
+
}
|
|
389
|
+
this.fr.register($doc, { hash, ownerKey }, token);
|
|
390
|
+
this.ensureRuntime(hash, segments);
|
|
391
|
+
const doc = entry.runtime;
|
|
392
|
+
if (previousCount > 0 &&
|
|
393
|
+
doc &&
|
|
394
|
+
entry.phase === 'stable' &&
|
|
395
|
+
this.getDesiredTransportMode(hash) === doc.activeTransportMode)
|
|
396
|
+
return;
|
|
397
|
+
return this.reconcileTransport(hash);
|
|
398
|
+
}
|
|
399
|
+
retain($doc) {
|
|
400
|
+
const segments = [...$doc[SEGMENTS]];
|
|
401
|
+
const hash = hashDoc(segments);
|
|
402
|
+
const entry = this.getOrCreateEntry(hash, segments);
|
|
403
|
+
this.cancelDestroy(hash);
|
|
404
|
+
entry.retainCount += 1;
|
|
405
|
+
this.ensureRuntime(hash, segments);
|
|
406
|
+
this.syncEntryMirror(entry);
|
|
407
|
+
}
|
|
408
|
+
async unsubscribe($doc, { intent = 'subscribe' } = {}) {
|
|
409
|
+
const segments = [...$doc[SEGMENTS]];
|
|
410
|
+
const hash = hashDoc(segments);
|
|
411
|
+
const rootId = getOwningRootId($doc);
|
|
412
|
+
const ownerKey = getDocOwnerKey(rootId, hash);
|
|
413
|
+
const token = getDocFinalizationToken($doc);
|
|
414
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
415
|
+
const currentIntentCount = this.getOwnerIntentCount(record, intent);
|
|
416
|
+
if (currentIntentCount <= 0) {
|
|
417
|
+
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES)
|
|
418
|
+
throw ERRORS.notSubscribed($doc);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
this.setOwnerIntentCount(record, intent, currentIntentCount - 1);
|
|
422
|
+
const nextOwnerCount = this.getOwnerTotalCount(record);
|
|
423
|
+
if (rootId) {
|
|
424
|
+
unregisterRootOwnedDirectDocSubscription(rootId, hash, token);
|
|
425
|
+
}
|
|
426
|
+
const entry = this.getOrCreateEntry(hash, segments);
|
|
427
|
+
if (nextOwnerCount === 0) {
|
|
428
|
+
this.fr.unregister(token);
|
|
429
|
+
if (record) {
|
|
430
|
+
this.removeOwnerFromEntry(record);
|
|
431
|
+
}
|
|
432
|
+
this.ownerRecords.delete(ownerKey);
|
|
433
|
+
}
|
|
434
|
+
const count = this.getEntryTotalCount(entry);
|
|
435
|
+
const destroyPromise = count === 0 ? this.scheduleDestroy(segments) : undefined;
|
|
436
|
+
await this.reconcileTransport(hash);
|
|
437
|
+
if (count > 0)
|
|
438
|
+
return;
|
|
439
|
+
await destroyPromise;
|
|
440
|
+
}
|
|
441
|
+
async release($doc) {
|
|
442
|
+
const segments = [...$doc[SEGMENTS]];
|
|
443
|
+
const hash = hashDoc(segments);
|
|
444
|
+
const entry = this.entries.get(hash);
|
|
445
|
+
if (!entry) {
|
|
446
|
+
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES)
|
|
447
|
+
throw ERRORS.notSubscribed($doc);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (entry.retainCount <= 0) {
|
|
451
|
+
if (ERROR_ON_EXCESSIVE_UNSUBSCRIBES)
|
|
452
|
+
throw ERRORS.notSubscribed($doc);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
entry.retainCount -= 1;
|
|
456
|
+
if ((this.getTrackedCount(hash) || 0) > 0)
|
|
457
|
+
return;
|
|
458
|
+
await this.scheduleDestroy(segments);
|
|
459
|
+
}
|
|
460
|
+
async destroy(segments) {
|
|
461
|
+
const hash = hashDoc(segments);
|
|
462
|
+
await this.destroyByHash(hash, { force: true });
|
|
463
|
+
}
|
|
464
|
+
async clear() {
|
|
465
|
+
const hashes = new Set(this.entries.keys());
|
|
466
|
+
for (const hash of hashes) {
|
|
467
|
+
await this.destroyByHash(hash, { force: true });
|
|
468
|
+
}
|
|
469
|
+
this.entries.clear();
|
|
470
|
+
this.ownerRecords.clear();
|
|
471
|
+
}
|
|
472
|
+
async releaseRootOwnedSubscriptions(rootId) {
|
|
473
|
+
const entries = Array.from(getRootOwnedDirectDocSubscriptions(rootId).entries());
|
|
474
|
+
if (entries.length === 0)
|
|
475
|
+
return;
|
|
476
|
+
for (const [hash, entry] of entries) {
|
|
477
|
+
for (const token of entry.tokenCounts.keys()) {
|
|
478
|
+
this.fr.unregister(token);
|
|
479
|
+
}
|
|
480
|
+
await this.destroyByOwnerKey(getDocOwnerKey(rootId, hash), { hash, force: true });
|
|
481
|
+
}
|
|
482
|
+
clearRootOwnedDirectDocSubscriptions(rootId);
|
|
483
|
+
}
|
|
484
|
+
async flushPendingDestroys() {
|
|
485
|
+
const hashes = Array.from(filterMapKeys(this.entries, entry => !!entry.pendingDestroy));
|
|
486
|
+
for (const hash of hashes) {
|
|
487
|
+
await this.destroyByHash(hash);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async scheduleDestroy(segments, options = {}) {
|
|
491
|
+
const hash = hashDoc(segments);
|
|
492
|
+
const delay = getSubscriptionGcDelay();
|
|
493
|
+
if (delay <= 0) {
|
|
494
|
+
await this.destroyByHash(hash, options);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const entry = this.getOrCreateEntry(hash, segments);
|
|
498
|
+
const existing = entry.pendingDestroy;
|
|
499
|
+
if (existing) {
|
|
500
|
+
if (options.force)
|
|
501
|
+
existing.force = true;
|
|
502
|
+
return existing.promise;
|
|
503
|
+
}
|
|
504
|
+
const pendingDestroy = createPendingDestroyEntry();
|
|
505
|
+
if (options.force)
|
|
506
|
+
pendingDestroy.force = true;
|
|
507
|
+
pendingDestroy.timer = setTimeout(() => {
|
|
508
|
+
this.destroyByHash(hash, { force: pendingDestroy.force }).catch(ignoreDestroyError);
|
|
509
|
+
}, delay);
|
|
510
|
+
entry.pendingDestroy = pendingDestroy;
|
|
511
|
+
return pendingDestroy.promise;
|
|
512
|
+
}
|
|
513
|
+
cancelDestroy(hash) {
|
|
514
|
+
const entry = this.takePendingDestroy(hash);
|
|
515
|
+
if (!entry)
|
|
516
|
+
return;
|
|
517
|
+
entry.resolve();
|
|
518
|
+
}
|
|
519
|
+
async reconcileTransport(hash) {
|
|
520
|
+
const entry = this.getOrCreateEntry(hash);
|
|
521
|
+
entry.targetMode = this.getDesiredTransportMode(hash);
|
|
522
|
+
if (entry.phase === 'transition' && entry.reconcilePromise)
|
|
523
|
+
return entry.reconcilePromise;
|
|
524
|
+
const next = Promise.resolve()
|
|
525
|
+
.catch(ignoreDestroyError)
|
|
526
|
+
.then(() => this.reconcileTransportNow(hash));
|
|
527
|
+
entry.phase = 'transition';
|
|
528
|
+
entry.reconcilePromise = next;
|
|
529
|
+
try {
|
|
530
|
+
await next;
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
const currentEntry = this.entries.get(hash);
|
|
534
|
+
if (currentEntry?.reconcilePromise === next) {
|
|
535
|
+
currentEntry.reconcilePromise = null;
|
|
536
|
+
currentEntry.phase = 'stable';
|
|
537
|
+
}
|
|
538
|
+
this.deleteEntryIfEmpty(hash);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async reconcileTransportNow(hash) {
|
|
542
|
+
const entry = this.getOrCreateEntry(hash);
|
|
543
|
+
while (true) {
|
|
544
|
+
let doc = entry.runtime;
|
|
545
|
+
const desiredMode = entry.targetMode = this.getDesiredTransportMode(hash);
|
|
546
|
+
const currentMode = doc?.activeTransportMode ?? entry.mode;
|
|
547
|
+
entry.mode = currentMode;
|
|
548
|
+
if (desiredMode === currentMode)
|
|
549
|
+
return;
|
|
550
|
+
if (desiredMode === 'idle') {
|
|
551
|
+
if (doc && currentMode !== 'idle') {
|
|
552
|
+
await doc.unsubscribe();
|
|
553
|
+
}
|
|
554
|
+
entry.mode = 'idle';
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (currentMode !== 'idle' && doc) {
|
|
558
|
+
await doc.unsubscribe();
|
|
559
|
+
entry.mode = 'idle';
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
doc = this.ensureRuntime(hash);
|
|
563
|
+
await doc.subscribe({ mode: desiredMode });
|
|
564
|
+
entry.runtime = doc;
|
|
565
|
+
entry.mode = doc.activeTransportMode || desiredMode;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async destroyByHash(hash, options = {}) {
|
|
569
|
+
let pendingDestroy = options._pendingDestroy;
|
|
570
|
+
if (pendingDestroy)
|
|
571
|
+
this.takePendingDestroy(hash, pendingDestroy);
|
|
572
|
+
else
|
|
573
|
+
pendingDestroy = this.takePendingDestroy(hash);
|
|
574
|
+
if (pendingDestroy?.force)
|
|
575
|
+
options.force = true;
|
|
576
|
+
const settlePending = err => {
|
|
577
|
+
if (!pendingDestroy)
|
|
578
|
+
return;
|
|
579
|
+
if (err)
|
|
580
|
+
pendingDestroy.reject(err);
|
|
581
|
+
else
|
|
582
|
+
pendingDestroy.resolve();
|
|
583
|
+
};
|
|
584
|
+
try {
|
|
585
|
+
const entry = this.entries.get(hash);
|
|
586
|
+
if (options.force && entry?.owners.size) {
|
|
587
|
+
this.removeAllOwnersFromEntry(hash);
|
|
588
|
+
}
|
|
589
|
+
const count = entry ? this.getEntryTotalCount(entry) : (this.getTrackedCount(hash) || 0);
|
|
590
|
+
if (!options.force && count > 0) {
|
|
591
|
+
settlePending();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const doc = entry?.runtime;
|
|
595
|
+
if (!doc) {
|
|
596
|
+
if (entry) {
|
|
597
|
+
entry.mode = 'idle';
|
|
598
|
+
entry.runtime = null;
|
|
599
|
+
this.deleteEntryIfEmpty(hash);
|
|
600
|
+
}
|
|
601
|
+
settlePending();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
await this.reconcileTransport(hash);
|
|
605
|
+
const nextEntry = this.entries.get(hash);
|
|
606
|
+
const nextCount = nextEntry ? this.getEntryTotalCount(nextEntry) : (this.getTrackedCount(hash) || 0);
|
|
607
|
+
if (!options.force && nextCount > 0) {
|
|
608
|
+
settlePending();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const activeDoc = nextEntry?.runtime || doc;
|
|
612
|
+
if (activeDoc.activeTransportMode !== 'idle') {
|
|
613
|
+
await activeDoc.unsubscribe();
|
|
614
|
+
}
|
|
615
|
+
const finalEntryBeforeDestroy = this.entries.get(hash);
|
|
616
|
+
const finalCountBeforeDestroy = finalEntryBeforeDestroy
|
|
617
|
+
? this.getEntryTotalCount(finalEntryBeforeDestroy)
|
|
618
|
+
: (this.getTrackedCount(hash) || 0);
|
|
619
|
+
if (!options.force && finalCountBeforeDestroy > 0) {
|
|
620
|
+
settlePending();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (typeof activeDoc.hasPending === 'function' && activeDoc.hasPending()) {
|
|
624
|
+
if (typeof activeDoc.whenNothingPending === 'function') {
|
|
625
|
+
if (pendingDestroy) {
|
|
626
|
+
const nextEntry = this.getOrCreateEntry(hash);
|
|
627
|
+
nextEntry.pendingDestroy = pendingDestroy;
|
|
628
|
+
}
|
|
629
|
+
activeDoc.whenNothingPending(() => {
|
|
630
|
+
const nextOptions = pendingDestroy ? { ...options, _pendingDestroy: pendingDestroy } : options;
|
|
631
|
+
this.destroyByHash(hash, nextOptions).catch(ignoreDestroyError);
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
settlePending();
|
|
636
|
+
}
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (typeof activeDoc.destroy === 'function')
|
|
640
|
+
await activeDoc.destroy();
|
|
641
|
+
if (typeof activeDoc.dispose === 'function')
|
|
642
|
+
activeDoc.dispose();
|
|
643
|
+
const finalEntry = this.entries.get(hash);
|
|
644
|
+
if (finalEntry) {
|
|
645
|
+
finalEntry.runtime = null;
|
|
646
|
+
finalEntry.mode = 'idle';
|
|
647
|
+
this.deleteEntryIfEmpty(hash);
|
|
648
|
+
}
|
|
649
|
+
settlePending();
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
settlePending(err);
|
|
653
|
+
throw err;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
takePendingDestroy(hash, expectedEntry) {
|
|
657
|
+
const transportEntry = this.entries.get(hash);
|
|
658
|
+
const pendingDestroy = transportEntry?.pendingDestroy;
|
|
659
|
+
if (!pendingDestroy)
|
|
660
|
+
return;
|
|
661
|
+
if (expectedEntry && pendingDestroy !== expectedEntry)
|
|
662
|
+
return;
|
|
663
|
+
clearTimeout(pendingDestroy.timer);
|
|
664
|
+
transportEntry.pendingDestroy = null;
|
|
665
|
+
this.deleteEntryIfEmpty(hash);
|
|
666
|
+
return pendingDestroy;
|
|
667
|
+
}
|
|
668
|
+
getOwnerIntentCount(recordOrOwnerKey, intent) {
|
|
669
|
+
const record = typeof recordOrOwnerKey === 'string'
|
|
670
|
+
? this.ownerRecords.get(recordOrOwnerKey)
|
|
671
|
+
: recordOrOwnerKey;
|
|
672
|
+
if (!record)
|
|
673
|
+
return 0;
|
|
674
|
+
return intent === 'fetch' ? record.fetchCount : record.subscribeCount;
|
|
675
|
+
}
|
|
676
|
+
setOwnerIntentCount(record, intent, count) {
|
|
677
|
+
if (!record)
|
|
678
|
+
return;
|
|
679
|
+
if (intent === 'fetch')
|
|
680
|
+
record.fetchCount = Math.max(count, 0);
|
|
681
|
+
else
|
|
682
|
+
record.subscribeCount = Math.max(count, 0);
|
|
683
|
+
this.syncOwnerMirror(record);
|
|
684
|
+
}
|
|
685
|
+
incrementOwnerIntent(record, intent) {
|
|
686
|
+
this.setOwnerIntentCount(record, intent, this.getOwnerIntentCount(record, intent) + 1);
|
|
687
|
+
}
|
|
688
|
+
getOwnerTotalCount(recordOrOwnerKey) {
|
|
689
|
+
const record = typeof recordOrOwnerKey === 'string'
|
|
690
|
+
? this.ownerRecords.get(recordOrOwnerKey)
|
|
691
|
+
: recordOrOwnerKey;
|
|
692
|
+
if (!record)
|
|
693
|
+
return 0;
|
|
694
|
+
return record.fetchCount + record.subscribeCount;
|
|
695
|
+
}
|
|
696
|
+
addOwnerMeta(ownerKey, hash, segments, rootId) {
|
|
697
|
+
const record = this.getOrCreateOwnerRecord(ownerKey, { hash, segments, rootId });
|
|
698
|
+
this.addOwnerToEntry(record);
|
|
699
|
+
}
|
|
700
|
+
removeOwnerMeta(ownerKey, hash) {
|
|
701
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
702
|
+
const knownHash = hash ?? record?.hash;
|
|
703
|
+
if (record) {
|
|
704
|
+
this.removeOwnerFromEntry(record);
|
|
705
|
+
this.ownerRecords.delete(ownerKey);
|
|
706
|
+
}
|
|
707
|
+
if (!knownHash)
|
|
708
|
+
return;
|
|
709
|
+
const ownerKeys = this.entries.get(knownHash)?.owners;
|
|
710
|
+
if (!ownerKeys)
|
|
711
|
+
return;
|
|
712
|
+
ownerKeys.delete(ownerKey);
|
|
713
|
+
this.deleteEntryIfEmpty(knownHash);
|
|
714
|
+
}
|
|
715
|
+
getDesiredTransportMode(hash) {
|
|
716
|
+
const entry = this.entries.get(hash);
|
|
717
|
+
const ownerKeys = entry?.owners;
|
|
718
|
+
if (!ownerKeys || ownerKeys.size === 0)
|
|
719
|
+
return 'idle';
|
|
720
|
+
let hasFetchBackedOwner = false;
|
|
721
|
+
for (const ownerKey of ownerKeys) {
|
|
722
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
723
|
+
const subscribeCount = record?.subscribeCount || 0;
|
|
724
|
+
const fetchCount = record?.fetchCount || 0;
|
|
725
|
+
const rootId = record?.rootId;
|
|
726
|
+
const subscribeMode = getRootTransportMode(rootId, 'subscribe');
|
|
727
|
+
if (subscribeCount > 0 && subscribeMode === 'subscribe')
|
|
728
|
+
return 'subscribe';
|
|
729
|
+
if (fetchCount > 0 || (subscribeCount > 0 && subscribeMode === 'fetch')) {
|
|
730
|
+
hasFetchBackedOwner = true;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return hasFetchBackedOwner ? 'fetch' : 'idle';
|
|
734
|
+
}
|
|
735
|
+
removeAllOwnersFromEntry(hash) {
|
|
736
|
+
const entry = this.entries.get(hash);
|
|
737
|
+
if (!entry)
|
|
738
|
+
return;
|
|
739
|
+
for (const ownerKey of Array.from(entry.owners)) {
|
|
740
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
741
|
+
if (record)
|
|
742
|
+
this.removeOwnerFromEntry(record);
|
|
743
|
+
else
|
|
744
|
+
entry.owners.delete(ownerKey);
|
|
745
|
+
this.ownerRecords.delete(ownerKey);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
async destroyTransportEntry(hash, runtime) {
|
|
749
|
+
const activeDoc = this.entries.get(hash)?.runtime || runtime;
|
|
750
|
+
if (!activeDoc) {
|
|
751
|
+
const entry = this.entries.get(hash);
|
|
752
|
+
if (entry) {
|
|
753
|
+
entry.runtime = null;
|
|
754
|
+
entry.mode = 'idle';
|
|
755
|
+
}
|
|
756
|
+
this.deleteEntryIfEmpty(hash);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (activeDoc.activeTransportMode !== 'idle') {
|
|
760
|
+
await activeDoc.unsubscribe();
|
|
761
|
+
}
|
|
762
|
+
if (typeof activeDoc.hasPending === 'function' && activeDoc.hasPending()) {
|
|
763
|
+
if (typeof activeDoc.whenNothingPending === 'function') {
|
|
764
|
+
await new Promise(resolve => activeDoc.whenNothingPending(resolve));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (typeof activeDoc.destroy === 'function')
|
|
768
|
+
await activeDoc.destroy();
|
|
769
|
+
if (typeof activeDoc.dispose === 'function')
|
|
770
|
+
activeDoc.dispose();
|
|
771
|
+
const finalEntry = this.entries.get(hash);
|
|
772
|
+
if (finalEntry && finalEntry.owners.size > 0)
|
|
773
|
+
return;
|
|
774
|
+
if (finalEntry) {
|
|
775
|
+
finalEntry.runtime = null;
|
|
776
|
+
finalEntry.mode = 'idle';
|
|
777
|
+
}
|
|
778
|
+
this.deleteEntryIfEmpty(hash);
|
|
779
|
+
}
|
|
780
|
+
async destroyByOwnerKey(ownerKey, options = {}) {
|
|
781
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
782
|
+
const hash = record?.hash ?? options.hash;
|
|
783
|
+
if (!hash)
|
|
784
|
+
return;
|
|
785
|
+
const segments = record?.segments ?? parseDocHash(hash);
|
|
786
|
+
const ownerCount = this.getOwnerTotalCount(record || ownerKey);
|
|
787
|
+
if (!options.force && ownerCount > 0)
|
|
788
|
+
return;
|
|
789
|
+
const entry = this.entries.get(hash);
|
|
790
|
+
if (record) {
|
|
791
|
+
this.removeOwnerFromEntry(record);
|
|
792
|
+
this.ownerRecords.delete(ownerKey);
|
|
793
|
+
}
|
|
794
|
+
else if (entry?.owners.has(ownerKey)) {
|
|
795
|
+
entry.owners.delete(ownerKey);
|
|
796
|
+
}
|
|
797
|
+
if (!entry && !this.getRuntime(hash)) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
await this.reconcileTransport(hash);
|
|
801
|
+
const nextEntry = this.entries.get(hash);
|
|
802
|
+
const nextCount = nextEntry ? this.getEntryTotalCount(nextEntry) : (this.getTrackedCount(hash) || 0);
|
|
803
|
+
if (nextCount > 0) {
|
|
804
|
+
this.deleteEntryIfEmpty(hash);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (options.force) {
|
|
808
|
+
await this.destroyTransportEntry(hash, nextEntry?.runtime || entry?.runtime);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await this.scheduleDestroy(segments, { force: false });
|
|
812
|
+
}
|
|
813
|
+
getRuntime(hash) {
|
|
814
|
+
return this.entries.get(hash)?.runtime;
|
|
815
|
+
}
|
|
816
|
+
hasRuntime(hash) {
|
|
817
|
+
return !!this.getRuntime(hash);
|
|
818
|
+
}
|
|
819
|
+
getRuntimeCount() {
|
|
820
|
+
return countMapLike(this.entries, entry => !!entry.runtime);
|
|
821
|
+
}
|
|
822
|
+
getTrackedCount(hash) {
|
|
823
|
+
const entry = this.entries.get(hash);
|
|
824
|
+
return this.getEntryTrackedTotal(entry);
|
|
825
|
+
}
|
|
826
|
+
getTrackedHashCountSize() {
|
|
827
|
+
return countMapLike(this.entries, entry => this.getEntryTrackedTotal(entry) !== undefined);
|
|
828
|
+
}
|
|
829
|
+
getOwnerMeta(ownerKey) {
|
|
830
|
+
const record = this.ownerRecords.get(ownerKey);
|
|
831
|
+
if (!record)
|
|
832
|
+
return undefined;
|
|
833
|
+
return {
|
|
834
|
+
hash: record.hash,
|
|
835
|
+
segments: [...record.segments],
|
|
836
|
+
rootId: record.rootId
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
getOwnerKeys(hash) {
|
|
840
|
+
const owners = this.entries.get(hash)?.owners;
|
|
841
|
+
if (!owners?.size)
|
|
842
|
+
return undefined;
|
|
843
|
+
return new Set(owners);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
export const docSubscriptions = new DocSubscriptions();
|
|
847
|
+
function hashDoc(segments) {
|
|
848
|
+
return JSON.stringify(segments);
|
|
849
|
+
}
|
|
850
|
+
function parseDocHash(hash) {
|
|
851
|
+
return JSON.parse(hash);
|
|
852
|
+
}
|
|
853
|
+
function getDocOwnerKey(rootId, hash) {
|
|
854
|
+
return JSON.stringify({ owner: [rootId, hash] });
|
|
855
|
+
}
|
|
856
|
+
function ignoreDestroyError() { }
|
|
857
|
+
function createPendingDestroyEntry() {
|
|
858
|
+
let resolvePending;
|
|
859
|
+
let rejectPending;
|
|
860
|
+
const promise = new Promise((resolve, reject) => {
|
|
861
|
+
resolvePending = resolve;
|
|
862
|
+
rejectPending = reject;
|
|
863
|
+
});
|
|
864
|
+
promise.catch(ignoreDestroyError);
|
|
865
|
+
return {
|
|
866
|
+
timer: undefined,
|
|
867
|
+
force: false,
|
|
868
|
+
promise,
|
|
869
|
+
resolve: resolvePending,
|
|
870
|
+
reject: rejectPending
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function emitDocOp(collection, docId, op) {
|
|
874
|
+
if (!isModelEventsEnabled())
|
|
875
|
+
return;
|
|
876
|
+
const ops = Array.isArray(op) ? op : [op];
|
|
877
|
+
for (const component of ops) {
|
|
878
|
+
if (!component || !component.p)
|
|
879
|
+
continue;
|
|
880
|
+
const baseSegments = [collection, docId];
|
|
881
|
+
let pathSegments = baseSegments.concat(component.p);
|
|
882
|
+
const meta = {};
|
|
883
|
+
let value;
|
|
884
|
+
let prevValue;
|
|
885
|
+
if (has(component, 'si') || has(component, 'sd')) {
|
|
886
|
+
const index = component.p[component.p.length - 1];
|
|
887
|
+
meta.op = has(component, 'si') ? 'stringInsert' : 'stringRemove';
|
|
888
|
+
meta.index = index;
|
|
889
|
+
pathSegments = baseSegments.concat(component.p.slice(0, -1));
|
|
890
|
+
value = _getRaw(pathSegments);
|
|
891
|
+
prevValue = component.sd;
|
|
892
|
+
}
|
|
893
|
+
else if (has(component, 'lm')) {
|
|
894
|
+
meta.op = 'arrayMove';
|
|
895
|
+
meta.from = component.p[component.p.length - 1];
|
|
896
|
+
meta.to = component.lm;
|
|
897
|
+
pathSegments = baseSegments.concat(component.p.slice(0, -1));
|
|
898
|
+
value = _getRaw(pathSegments);
|
|
899
|
+
}
|
|
900
|
+
else if (has(component, 'li') || has(component, 'ld')) {
|
|
901
|
+
meta.op = has(component, 'li') ? 'arrayInsert' : 'arrayRemove';
|
|
902
|
+
meta.index = component.p[component.p.length - 1];
|
|
903
|
+
value = _getRaw(pathSegments);
|
|
904
|
+
prevValue = component.ld;
|
|
905
|
+
}
|
|
906
|
+
else if (has(component, 'na')) {
|
|
907
|
+
meta.op = 'increment';
|
|
908
|
+
meta.by = component.na;
|
|
909
|
+
value = _getRaw(pathSegments);
|
|
910
|
+
if (typeof value === 'number')
|
|
911
|
+
prevValue = value - component.na;
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
meta.op = 'set';
|
|
915
|
+
value = has(component, 'oi') ? component.oi : _getRaw(pathSegments);
|
|
916
|
+
prevValue = component.od;
|
|
917
|
+
}
|
|
918
|
+
emitModelChange(pathSegments, value, prevValue, meta);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function has(obj, key) {
|
|
922
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
923
|
+
}
|
|
924
|
+
const ERRORS = {
|
|
925
|
+
notSubscribed: $doc => Error('trying to unsubscribe when not subscribed. Doc: ' + $doc.path())
|
|
926
|
+
};
|
|
927
|
+
function createReadonlyMapView({ get, has, size, keys }) {
|
|
928
|
+
return {
|
|
929
|
+
get,
|
|
930
|
+
has,
|
|
931
|
+
get size() {
|
|
932
|
+
return size();
|
|
933
|
+
},
|
|
934
|
+
*keys() {
|
|
935
|
+
yield* keys();
|
|
936
|
+
},
|
|
937
|
+
*values() {
|
|
938
|
+
for (const key of keys())
|
|
939
|
+
yield get(key);
|
|
940
|
+
},
|
|
941
|
+
*entries() {
|
|
942
|
+
for (const key of keys())
|
|
943
|
+
yield [key, get(key)];
|
|
944
|
+
},
|
|
945
|
+
[Symbol.iterator]() {
|
|
946
|
+
return this.entries();
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function countMapLike(iterableMap, predicate) {
|
|
951
|
+
let count = 0;
|
|
952
|
+
for (const value of iterableMap.values()) {
|
|
953
|
+
if (predicate(value))
|
|
954
|
+
count++;
|
|
955
|
+
}
|
|
956
|
+
return count;
|
|
957
|
+
}
|
|
958
|
+
function* filterMapKeys(iterableMap, predicate) {
|
|
959
|
+
for (const [key, value] of iterableMap.entries()) {
|
|
960
|
+
if (predicate(value))
|
|
961
|
+
yield key;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function* getTrackedHashes(entries) {
|
|
965
|
+
yield* entries.keys();
|
|
966
|
+
}
|