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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { getRefLinks, getRefRootIds } from './refRegistry.js';
|
|
2
|
+
import { isCompatEnv } from '../compatEnv.js';
|
|
3
|
+
import { isSilentContextActive, isModelEventsSilentContextActive, runInModelEventsSilentContext } from './silentContext.js';
|
|
4
|
+
import { normalizeRootId } from "../rootScope.js";
|
|
5
|
+
import { getRootContext, getRootContexts } from "../rootContext.js";
|
|
6
|
+
import { setReplace as setReplaceInDataTree, del as delFromDataTree } from '../dataTree.js';
|
|
7
|
+
import { setReplacePrivateData, delPrivateData } from '../privateData.js';
|
|
8
|
+
const MODEL_EVENT_NAMES = ['change', 'all'];
|
|
9
|
+
export function isModelEventsEnabled() {
|
|
10
|
+
return isCompatEnv();
|
|
11
|
+
}
|
|
12
|
+
export function normalizePattern(pattern, methodName) {
|
|
13
|
+
if (pattern && typeof pattern.path === 'function')
|
|
14
|
+
pattern = pattern.path();
|
|
15
|
+
if (pattern == null || typeof pattern !== 'string') {
|
|
16
|
+
if (methodName)
|
|
17
|
+
throw Error(`${methodName} expects a string path or a signal`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return pattern.split('.').filter(Boolean).join('.');
|
|
21
|
+
}
|
|
22
|
+
export function onModelEvent(rootId, eventName, pattern, handler) {
|
|
23
|
+
if (typeof handler !== 'function')
|
|
24
|
+
throw Error('Model event handler must be a function');
|
|
25
|
+
if (!MODEL_EVENT_NAMES.includes(eventName))
|
|
26
|
+
throw Error(`Unsupported model event: ${eventName}`);
|
|
27
|
+
const store = getModelEventRootStore(eventName, rootId, true);
|
|
28
|
+
const normalized = normalizePattern(pattern);
|
|
29
|
+
let entry = store.get(normalized);
|
|
30
|
+
if (!entry) {
|
|
31
|
+
entry = {
|
|
32
|
+
pattern: normalized,
|
|
33
|
+
segments: splitPattern(normalized),
|
|
34
|
+
handlers: new Set()
|
|
35
|
+
};
|
|
36
|
+
store.set(normalized, entry);
|
|
37
|
+
}
|
|
38
|
+
entry.handlers.add(handler);
|
|
39
|
+
return handler;
|
|
40
|
+
}
|
|
41
|
+
export function removeModelListener(rootId, eventName, handler) {
|
|
42
|
+
const store = getModelEventRootStore(eventName, rootId);
|
|
43
|
+
if (!store)
|
|
44
|
+
return;
|
|
45
|
+
for (const [pattern, entry] of store) {
|
|
46
|
+
entry.handlers.delete(handler);
|
|
47
|
+
if (!entry.handlers.size)
|
|
48
|
+
store.delete(pattern);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function emitModelChange(path, value, prevValue, meta) {
|
|
52
|
+
if (!isModelEventsEnabled())
|
|
53
|
+
return;
|
|
54
|
+
if (isSilentContextActive() || isModelEventsSilentContextActive())
|
|
55
|
+
return;
|
|
56
|
+
const initialSegments = splitPath(path);
|
|
57
|
+
const eventName = meta?.eventName || 'change';
|
|
58
|
+
const rootIds = getTargetRootIds(meta?.rootId);
|
|
59
|
+
for (const rootId of rootIds) {
|
|
60
|
+
const visited = new Set();
|
|
61
|
+
const queue = [initialSegments];
|
|
62
|
+
while (queue.length) {
|
|
63
|
+
const segments = queue.shift();
|
|
64
|
+
const key = segments.join('.');
|
|
65
|
+
if (visited.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
visited.add(key);
|
|
68
|
+
emitForEvent(rootId, 'change', segments, value, prevValue, meta);
|
|
69
|
+
emitForEvent(rootId, 'all', segments, value, prevValue, meta, eventName);
|
|
70
|
+
for (const link of getRefLinks(rootId).values()) {
|
|
71
|
+
if (!isPathPrefix(link.toSegments, segments))
|
|
72
|
+
continue;
|
|
73
|
+
if (link.mirrorOnly && typeof link.onChange === 'function') {
|
|
74
|
+
link.onChange();
|
|
75
|
+
}
|
|
76
|
+
else if (!link.mirrorOnly) {
|
|
77
|
+
mirrorRefAliasFromTargetSegments(rootId, link, segments, value, meta);
|
|
78
|
+
}
|
|
79
|
+
const suffix = segments.slice(link.toSegments.length);
|
|
80
|
+
const nextSegments = link.fromSegments.concat(suffix);
|
|
81
|
+
const nextKey = nextSegments.join('.');
|
|
82
|
+
if (!visited.has(nextKey))
|
|
83
|
+
queue.push(nextSegments);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function __resetModelEventsForTests() {
|
|
89
|
+
for (const context of getRootContexts()) {
|
|
90
|
+
context.resetModelListeners();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function emitForEvent(rootId, eventName, pathSegments, value, prevValue, meta, resolvedEventName = eventName) {
|
|
94
|
+
const store = getModelEventRootStore(eventName, rootId);
|
|
95
|
+
if (!store || store.size === 0)
|
|
96
|
+
return;
|
|
97
|
+
for (const entry of store.values()) {
|
|
98
|
+
const captures = matchPattern(entry.segments, pathSegments);
|
|
99
|
+
if (!captures)
|
|
100
|
+
continue;
|
|
101
|
+
for (const handler of entry.handlers) {
|
|
102
|
+
if (eventName === 'all') {
|
|
103
|
+
handler(...captures, resolvedEventName, value, prevValue, meta);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
handler(...captures, value, prevValue, meta);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function splitPattern(pattern) {
|
|
112
|
+
if (!pattern)
|
|
113
|
+
return [];
|
|
114
|
+
return pattern.split('.').filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
function mirrorRefAliasFromTargetSegments(rootId, link, targetSegments, value, meta) {
|
|
117
|
+
const suffix = targetSegments.slice(link.toSegments.length);
|
|
118
|
+
const fromSegments = link.fromSegments.concat(suffix);
|
|
119
|
+
const fromRootId = normalizeRootId(link.fromRootId ?? rootId);
|
|
120
|
+
const shouldDelete = shouldDeleteMirroredPath(value, meta);
|
|
121
|
+
runInModelEventsSilentContext(() => {
|
|
122
|
+
if (isPrivateSegments(fromSegments)) {
|
|
123
|
+
if (shouldDelete) {
|
|
124
|
+
delPrivateData(fromRootId, fromSegments);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
setReplacePrivateData(fromRootId, fromSegments, cloneValue(value));
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (shouldDelete) {
|
|
132
|
+
delFromDataTree(fromSegments);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
setReplaceInDataTree(fromSegments, cloneValue(value));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function isPrivateSegments(segments) {
|
|
139
|
+
if (!Array.isArray(segments) || !segments.length)
|
|
140
|
+
return false;
|
|
141
|
+
return /^[_$]/.test(String(segments[0]));
|
|
142
|
+
}
|
|
143
|
+
function shouldDeleteMirroredPath(value, meta) {
|
|
144
|
+
if (meta?.op === 'setReplace')
|
|
145
|
+
return false;
|
|
146
|
+
if (meta?.op === 'del')
|
|
147
|
+
return true;
|
|
148
|
+
return value === undefined;
|
|
149
|
+
}
|
|
150
|
+
function cloneValue(value) {
|
|
151
|
+
if (Array.isArray(value))
|
|
152
|
+
return value.map(cloneValue);
|
|
153
|
+
if (value && typeof value === 'object') {
|
|
154
|
+
const cloned = {};
|
|
155
|
+
for (const key of Object.keys(value))
|
|
156
|
+
cloned[key] = cloneValue(value[key]);
|
|
157
|
+
return cloned;
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
function getModelEventRootStore(eventName, rootId, create = false) {
|
|
162
|
+
return getRootContext(normalizeRootId(rootId), create)?.getModelEventStore(eventName, create);
|
|
163
|
+
}
|
|
164
|
+
function getModelEventRootIds() {
|
|
165
|
+
const rootIds = new Set();
|
|
166
|
+
for (const context of getRootContexts()) {
|
|
167
|
+
for (const store of Object.values(context.modelListeners)) {
|
|
168
|
+
if (store.size)
|
|
169
|
+
rootIds.add(context.rootId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return rootIds;
|
|
173
|
+
}
|
|
174
|
+
function getTargetRootIds(rootId) {
|
|
175
|
+
if (rootId != null)
|
|
176
|
+
return [normalizeRootId(rootId)];
|
|
177
|
+
const rootIds = new Set([
|
|
178
|
+
...getModelEventRootIds(),
|
|
179
|
+
...getRefRootIds()
|
|
180
|
+
]);
|
|
181
|
+
return rootIds;
|
|
182
|
+
}
|
|
183
|
+
function splitPath(path) {
|
|
184
|
+
if (Array.isArray(path))
|
|
185
|
+
return path.map(segment => String(segment));
|
|
186
|
+
if (!path)
|
|
187
|
+
return [];
|
|
188
|
+
return String(path).split('.').filter(Boolean);
|
|
189
|
+
}
|
|
190
|
+
function isPathPrefix(prefixSegments, fullSegments) {
|
|
191
|
+
if (prefixSegments.length > fullSegments.length)
|
|
192
|
+
return false;
|
|
193
|
+
for (let i = 0; i < prefixSegments.length; i++) {
|
|
194
|
+
if (prefixSegments[i] !== fullSegments[i])
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
function matchPattern(patternSegments, pathSegments) {
|
|
200
|
+
function walk(patternIndex, pathIndex) {
|
|
201
|
+
if (patternIndex === patternSegments.length) {
|
|
202
|
+
return pathIndex === pathSegments.length ? [] : null;
|
|
203
|
+
}
|
|
204
|
+
const segment = patternSegments[patternIndex];
|
|
205
|
+
if (segment === '**') {
|
|
206
|
+
for (let i = pathIndex; i <= pathSegments.length; i++) {
|
|
207
|
+
const rest = walk(patternIndex + 1, i);
|
|
208
|
+
if (rest !== null) {
|
|
209
|
+
const capture = pathSegments.slice(pathIndex, i).join('.');
|
|
210
|
+
return [capture, ...rest];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (pathIndex >= pathSegments.length)
|
|
216
|
+
return null;
|
|
217
|
+
if (segment === '*') {
|
|
218
|
+
const rest = walk(patternIndex + 1, pathIndex + 1);
|
|
219
|
+
if (rest === null)
|
|
220
|
+
return null;
|
|
221
|
+
return [pathSegments[pathIndex], ...rest];
|
|
222
|
+
}
|
|
223
|
+
if (segment !== pathSegments[pathIndex])
|
|
224
|
+
return null;
|
|
225
|
+
return walk(patternIndex + 1, pathIndex + 1);
|
|
226
|
+
}
|
|
227
|
+
return walk(0, 0);
|
|
228
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function isQueryReady(collection: any, idsSegments: any, docsSegments: any, extraSegments: any, aggregationSegments: any, isAggregate: any, hasExtraResult: any): boolean;
|
|
2
|
+
export function isDocReady(segments: any): boolean;
|
|
3
|
+
export function waitForImperativeQueryReady($query: any): Promise<void>;
|
|
4
|
+
export function __setImperativeQueryReadyTimeoutForTests(timeoutMs: any): void;
|
|
5
|
+
export function __resetImperativeQueryReadyTimeoutForTests(): void;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { getRaw } from '../dataTree.js';
|
|
2
|
+
import { getConnection } from "../connection.js";
|
|
3
|
+
import { isMissingShareDoc } from '../missingDoc.js';
|
|
4
|
+
import { QUERIES, HASH, PARAMS, COLLECTION_NAME, querySubscriptions } from '../Query.js';
|
|
5
|
+
import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../Aggregation.js';
|
|
6
|
+
import { getPrivateData, setPrivateData } from '../privateData.js';
|
|
7
|
+
import { getRoot, ROOT_ID } from "../Root.js";
|
|
8
|
+
import { isRootContextClosed } from "../rootContext.js";
|
|
9
|
+
import { getScopedSignalHash, normalizeRootId } from "../rootScope.js";
|
|
10
|
+
let imperativeQueryReadyTimeoutMs = 1000;
|
|
11
|
+
export function isQueryReady(collection, idsSegments, docsSegments, extraSegments, aggregationSegments, isAggregate, hasExtraResult) {
|
|
12
|
+
if (hasExtraResult) {
|
|
13
|
+
return getRaw(extraSegments) !== undefined;
|
|
14
|
+
}
|
|
15
|
+
if (isAggregate) {
|
|
16
|
+
const docs = getRaw(docsSegments);
|
|
17
|
+
if (Array.isArray(docs))
|
|
18
|
+
return true;
|
|
19
|
+
if (getRaw(extraSegments) !== undefined)
|
|
20
|
+
return true;
|
|
21
|
+
return getRaw(aggregationSegments) !== undefined;
|
|
22
|
+
}
|
|
23
|
+
const ids = getRaw(idsSegments);
|
|
24
|
+
if (!Array.isArray(ids))
|
|
25
|
+
return false;
|
|
26
|
+
for (const id of ids) {
|
|
27
|
+
if (id == null)
|
|
28
|
+
continue;
|
|
29
|
+
if (!isDocReady([collection, id]))
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
export function isDocReady(segments) {
|
|
35
|
+
const rawDoc = getRaw(segments);
|
|
36
|
+
if (rawDoc !== undefined)
|
|
37
|
+
return true;
|
|
38
|
+
const [collection, id] = segments;
|
|
39
|
+
const shareDoc = getShareDoc(collection, id);
|
|
40
|
+
// Missing docs should not block the batch barrier forever.
|
|
41
|
+
return isMissingShareDoc(shareDoc);
|
|
42
|
+
}
|
|
43
|
+
export async function waitForImperativeQueryReady($query) {
|
|
44
|
+
const timeoutMs = imperativeQueryReadyTimeoutMs;
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
const ownerState = createImperativeOwnerState($query);
|
|
47
|
+
while (true) {
|
|
48
|
+
if (isImperativeQueryCancelled($query, ownerState))
|
|
49
|
+
return;
|
|
50
|
+
if (isImperativeQueryReady($query)) {
|
|
51
|
+
syncQueryDocsFromCollection($query);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
55
|
+
throw createImperativeQueryReadinessError($query, timeoutMs);
|
|
56
|
+
}
|
|
57
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function __setImperativeQueryReadyTimeoutForTests(timeoutMs) {
|
|
61
|
+
imperativeQueryReadyTimeoutMs = timeoutMs;
|
|
62
|
+
}
|
|
63
|
+
export function __resetImperativeQueryReadyTimeoutForTests() {
|
|
64
|
+
imperativeQueryReadyTimeoutMs = 1000;
|
|
65
|
+
}
|
|
66
|
+
function isImperativeQueryReady($query) {
|
|
67
|
+
const collection = $query[COLLECTION_NAME];
|
|
68
|
+
const hash = $query[HASH];
|
|
69
|
+
const rootId = getRoot($query)?.[ROOT_ID];
|
|
70
|
+
const params = $query[PARAMS];
|
|
71
|
+
const hasExtraResult = isExtraQuery(params);
|
|
72
|
+
if (hasExtraResult)
|
|
73
|
+
return getPrivateData(rootId, [QUERIES, hash, 'extra'], true) !== undefined;
|
|
74
|
+
const isAggregate = !!$query[IS_AGGREGATION] || isAggregationQuery(params);
|
|
75
|
+
if (isAggregate) {
|
|
76
|
+
const docs = getPrivateData(rootId, [QUERIES, hash, 'docs'], true);
|
|
77
|
+
if (Array.isArray(docs))
|
|
78
|
+
return true;
|
|
79
|
+
if (getPrivateData(rootId, [QUERIES, hash, 'extra'], true) !== undefined)
|
|
80
|
+
return true;
|
|
81
|
+
return getPrivateData(rootId, [AGGREGATIONS, hash], true) !== undefined;
|
|
82
|
+
}
|
|
83
|
+
const ids = getPrivateData(rootId, [QUERIES, hash, 'ids'], true);
|
|
84
|
+
if (!Array.isArray(ids))
|
|
85
|
+
return false;
|
|
86
|
+
for (const id of ids) {
|
|
87
|
+
if (id == null)
|
|
88
|
+
continue;
|
|
89
|
+
if (getRaw([collection, id]) === undefined)
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
function isImperativeQueryCancelled($query, ownerState) {
|
|
95
|
+
const rootId = getRoot($query)?.[ROOT_ID];
|
|
96
|
+
if (isRootContextClosed(rootId))
|
|
97
|
+
return true;
|
|
98
|
+
if (!ownerState?.wasTracked)
|
|
99
|
+
return false;
|
|
100
|
+
const trackedOwnerCount = ownerState.subscriptions.getTrackedOwnerCount(ownerState.ownerKey);
|
|
101
|
+
return trackedOwnerCount == null || trackedOwnerCount <= 0;
|
|
102
|
+
}
|
|
103
|
+
function createImperativeOwnerState($query) {
|
|
104
|
+
const hash = $query[HASH];
|
|
105
|
+
const rootId = normalizeRootId(getRoot($query)?.[ROOT_ID]);
|
|
106
|
+
const subscriptions = ($query[IS_AGGREGATION] || isAggregationQuery($query[PARAMS]))
|
|
107
|
+
? aggregationSubscriptions
|
|
108
|
+
: querySubscriptions;
|
|
109
|
+
const ownerKey = getScopedSignalHash(rootId, hash, 'queryOwner');
|
|
110
|
+
return {
|
|
111
|
+
subscriptions,
|
|
112
|
+
ownerKey,
|
|
113
|
+
wasTracked: subscriptions.getTrackedOwnerCount(ownerKey) != null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function syncQueryDocsFromCollection($query) {
|
|
117
|
+
const params = $query[PARAMS];
|
|
118
|
+
if ($query[IS_AGGREGATION] || isAggregationQuery(params) || isExtraQuery(params))
|
|
119
|
+
return;
|
|
120
|
+
const collection = $query[COLLECTION_NAME];
|
|
121
|
+
const hash = $query[HASH];
|
|
122
|
+
const rootId = getRoot($query)?.[ROOT_ID];
|
|
123
|
+
const ids = getPrivateData(rootId, [QUERIES, hash, 'ids'], true);
|
|
124
|
+
if (!Array.isArray(ids))
|
|
125
|
+
return;
|
|
126
|
+
const docs = [];
|
|
127
|
+
for (const id of ids) {
|
|
128
|
+
if (id == null)
|
|
129
|
+
continue;
|
|
130
|
+
const doc = getRaw([collection, id]);
|
|
131
|
+
if (doc === undefined) {
|
|
132
|
+
throw createImperativeQueryReadinessError($query, imperativeQueryReadyTimeoutMs);
|
|
133
|
+
}
|
|
134
|
+
docs.push(doc);
|
|
135
|
+
}
|
|
136
|
+
setPrivateData(rootId, [QUERIES, hash, 'docs'], docs);
|
|
137
|
+
}
|
|
138
|
+
function createImperativeQueryReadinessError($query, timeoutMs) {
|
|
139
|
+
const collection = $query[COLLECTION_NAME];
|
|
140
|
+
const hash = $query[HASH];
|
|
141
|
+
const rootId = getRoot($query)?.[ROOT_ID];
|
|
142
|
+
const params = $query[PARAMS];
|
|
143
|
+
const ids = getPrivateData(rootId, [QUERIES, hash, 'ids'], true);
|
|
144
|
+
const missingDocs = [];
|
|
145
|
+
if (Array.isArray(ids)) {
|
|
146
|
+
for (const id of ids) {
|
|
147
|
+
if (id == null)
|
|
148
|
+
continue;
|
|
149
|
+
const doc = getRaw([collection, id]);
|
|
150
|
+
if (doc !== undefined)
|
|
151
|
+
continue;
|
|
152
|
+
const shareDoc = getShareDoc(collection, id);
|
|
153
|
+
missingDocs.push({
|
|
154
|
+
id,
|
|
155
|
+
missingShareDoc: isMissingShareDoc(shareDoc)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return Error(`
|
|
160
|
+
Compat query did not fully materialize within ${timeoutMs}ms.
|
|
161
|
+
Collection: ${collection}
|
|
162
|
+
Params: ${JSON.stringify(params)}
|
|
163
|
+
Hash: ${hash}
|
|
164
|
+
Ids: ${JSON.stringify(ids)}
|
|
165
|
+
Missing docs: ${JSON.stringify(missingDocs)}
|
|
166
|
+
`);
|
|
167
|
+
}
|
|
168
|
+
function getShareDoc(collection, id) {
|
|
169
|
+
try {
|
|
170
|
+
return getConnection().get(collection, id);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isExtraQuery(query) {
|
|
177
|
+
if (!query || typeof query !== 'object')
|
|
178
|
+
return false;
|
|
179
|
+
return !!(query.$count || query.$queryName);
|
|
180
|
+
}
|
|
181
|
+
function isAggregationQuery(query) {
|
|
182
|
+
if (!query || typeof query !== 'object')
|
|
183
|
+
return false;
|
|
184
|
+
return !!(query.$aggregate || query.$aggregationName);
|
|
185
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PathSegment } from '../types/path.js'
|
|
2
|
+
import type { SignalBaseInstance } from '../Signal.js'
|
|
3
|
+
|
|
4
|
+
export const REF_TARGET: unique symbol
|
|
5
|
+
export function resolveRefSignalSafe<TSignal extends SignalBaseInstance> (
|
|
6
|
+
$signal: TSignal | undefined,
|
|
7
|
+
maxDepth?: number
|
|
8
|
+
): TSignal | undefined
|
|
9
|
+
export function resolveRefSegmentsSafe (
|
|
10
|
+
segments: readonly PathSegment[],
|
|
11
|
+
rootId?: string,
|
|
12
|
+
maxDepth?: number
|
|
13
|
+
): PathSegment[] | undefined
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getRefLinks } from './refRegistry.js';
|
|
2
|
+
import { GLOBAL_ROOT_ID } from "../Root.js";
|
|
3
|
+
export const REF_TARGET = Symbol.for('teamplay.compat.refTarget');
|
|
4
|
+
export function resolveRefSignalSafe($signal, maxDepth = 32) {
|
|
5
|
+
let current = $signal;
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
8
|
+
if (!current)
|
|
9
|
+
return undefined;
|
|
10
|
+
const next = current[REF_TARGET];
|
|
11
|
+
if (!next)
|
|
12
|
+
return current;
|
|
13
|
+
if (seen.has(current))
|
|
14
|
+
return undefined;
|
|
15
|
+
seen.add(current);
|
|
16
|
+
current = next;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
export function resolveRefSegmentsSafe(segments, rootId = GLOBAL_ROOT_ID, maxDepth = 32) {
|
|
21
|
+
if (!Array.isArray(segments) || segments.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
let current = [...segments];
|
|
24
|
+
const visited = new Set([toPathKey(current)]);
|
|
25
|
+
let changed = false;
|
|
26
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
27
|
+
const link = findBestMatchingLink(current, rootId);
|
|
28
|
+
if (!link)
|
|
29
|
+
return changed ? current : undefined;
|
|
30
|
+
const suffix = current.slice(link.fromSegments.length);
|
|
31
|
+
const next = link.toSegments.concat(suffix);
|
|
32
|
+
const key = toPathKey(next);
|
|
33
|
+
if (visited.has(key))
|
|
34
|
+
return undefined;
|
|
35
|
+
visited.add(key);
|
|
36
|
+
current = next;
|
|
37
|
+
changed = true;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
function findBestMatchingLink(segments, rootId) {
|
|
42
|
+
let best;
|
|
43
|
+
for (const link of getRefLinks(rootId).values()) {
|
|
44
|
+
if (link.mirrorOnly)
|
|
45
|
+
continue;
|
|
46
|
+
if (!isPathPrefix(link.fromSegments, segments))
|
|
47
|
+
continue;
|
|
48
|
+
if (!best || link.fromSegments.length > best.fromSegments.length) {
|
|
49
|
+
best = link;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return best;
|
|
53
|
+
}
|
|
54
|
+
function isPathPrefix(prefixSegments, fullSegments) {
|
|
55
|
+
if (prefixSegments.length > fullSegments.length)
|
|
56
|
+
return false;
|
|
57
|
+
for (let i = 0; i < prefixSegments.length; i++) {
|
|
58
|
+
if (prefixSegments[i] !== String(fullSegments[i]))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
function toPathKey(segments) {
|
|
64
|
+
return segments.map(segment => String(segment)).join('.');
|
|
65
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function setRefLink(rootId: any, fromPath: any, toPath: any, fromSegments: any, toSegments: any, options?: {}): void;
|
|
2
|
+
export function removeRefLink(rootId: any, fromPath: any): void;
|
|
3
|
+
export function getRefLinks(rootId?: string): Map<string, unknown>;
|
|
4
|
+
export function getAllRefLinks(): Generator<unknown, void, unknown>;
|
|
5
|
+
export function getRefRootIds(): string[];
|
|
6
|
+
export function __resetRefLinksForTests(): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { GLOBAL_ROOT_ID } from "../Root.js";
|
|
2
|
+
import { normalizeRootId } from "../rootScope.js";
|
|
3
|
+
import { getRootContext, getRootContexts } from "../rootContext.js";
|
|
4
|
+
const EMPTY_MAP = new Map();
|
|
5
|
+
export function setRefLink(rootId, fromPath, toPath, fromSegments, toSegments, options = {}) {
|
|
6
|
+
if (typeof fromPath !== 'string' || typeof toPath !== 'string')
|
|
7
|
+
return;
|
|
8
|
+
const normalizedFromSegments = Array.isArray(fromSegments)
|
|
9
|
+
? fromSegments.map(segment => String(segment))
|
|
10
|
+
: splitPath(fromPath);
|
|
11
|
+
const normalizedToSegments = Array.isArray(toSegments)
|
|
12
|
+
? toSegments.map(segment => String(segment))
|
|
13
|
+
: splitPath(toPath);
|
|
14
|
+
getRefStore(rootId, true).set(fromPath, {
|
|
15
|
+
fromPath,
|
|
16
|
+
toPath,
|
|
17
|
+
fromSegments: normalizedFromSegments,
|
|
18
|
+
toSegments: normalizedToSegments,
|
|
19
|
+
fromRootId: normalizeRootId(rootId),
|
|
20
|
+
toRootId: options.toRootId,
|
|
21
|
+
mirrorOnly: !!options.mirrorOnly,
|
|
22
|
+
onChange: typeof options.onChange === 'function' ? options.onChange : undefined
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function removeRefLink(rootId, fromPath) {
|
|
26
|
+
const store = getRefStore(rootId);
|
|
27
|
+
if (!store)
|
|
28
|
+
return;
|
|
29
|
+
store.delete(fromPath);
|
|
30
|
+
}
|
|
31
|
+
export function getRefLinks(rootId = GLOBAL_ROOT_ID) {
|
|
32
|
+
return getRefStore(rootId) || EMPTY_MAP;
|
|
33
|
+
}
|
|
34
|
+
export function* getAllRefLinks() {
|
|
35
|
+
for (const context of getRootContexts()) {
|
|
36
|
+
yield* context.refLinks.values();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function getRefRootIds() {
|
|
40
|
+
return Array.from(getRootContexts())
|
|
41
|
+
.filter(context => context.refLinks.size > 0)
|
|
42
|
+
.map(context => context.rootId);
|
|
43
|
+
}
|
|
44
|
+
export function __resetRefLinksForTests() {
|
|
45
|
+
for (const context of getRootContexts()) {
|
|
46
|
+
context.resetRefs();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function splitPath(path) {
|
|
50
|
+
return path.split('.').filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
function getRefStore(rootId, create = false) {
|
|
53
|
+
return getRootContext(normalizeRootId(rootId), create)?.refLinks;
|
|
54
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function isSilentContextActive(): boolean;
|
|
2
|
+
export function isModelEventsSilentContextActive(): boolean;
|
|
3
|
+
export function runInSilentContext(fn: any): any;
|
|
4
|
+
export function runInModelEventsSilentContext(fn: any): any;
|
|
5
|
+
export function __resetSilentContextForTests(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
let silentDepth = 0;
|
|
2
|
+
let modelEventsSilentDepth = 0;
|
|
3
|
+
export function isSilentContextActive() {
|
|
4
|
+
return silentDepth > 0;
|
|
5
|
+
}
|
|
6
|
+
export function isModelEventsSilentContextActive() {
|
|
7
|
+
return modelEventsSilentDepth > 0;
|
|
8
|
+
}
|
|
9
|
+
export function runInSilentContext(fn) {
|
|
10
|
+
silentDepth += 1;
|
|
11
|
+
let result;
|
|
12
|
+
try {
|
|
13
|
+
result = fn();
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
silentDepth -= 1;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
if (result?.then) {
|
|
20
|
+
return Promise.resolve(result).finally(() => {
|
|
21
|
+
silentDepth -= 1;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
silentDepth -= 1;
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
export function runInModelEventsSilentContext(fn) {
|
|
28
|
+
modelEventsSilentDepth += 1;
|
|
29
|
+
let result;
|
|
30
|
+
try {
|
|
31
|
+
result = fn();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
modelEventsSilentDepth -= 1;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
if (result?.then) {
|
|
38
|
+
return Promise.resolve(result).finally(() => {
|
|
39
|
+
modelEventsSilentDepth -= 1;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
modelEventsSilentDepth -= 1;
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
export function __resetSilentContextForTests() {
|
|
46
|
+
silentDepth = 0;
|
|
47
|
+
modelEventsSilentDepth = 0;
|
|
48
|
+
}
|