querysub 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- package/yarnSpec.txt +56 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
|
|
2
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
3
|
+
import { batchFunction, delay, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
4
|
+
import { cache } from "socket-function/src/caching";
|
|
5
|
+
import { isNode, nextId } from "socket-function/src/misc";
|
|
6
|
+
import { measureFnc, measureBlock } from "socket-function/src/profiling/measure";
|
|
7
|
+
import { isOwnNodeId, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
8
|
+
import { nodePathAuthority, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
9
|
+
import { PathValueController, pathValueCommitter } from "../0-path-value-core/PathValueController";
|
|
10
|
+
import { WatchConfig, authorityStorage, decodeParentFilter, matchesParentRangeFilter, pathWatcher } from "../0-path-value-core/pathValueCore";
|
|
11
|
+
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
12
|
+
import { registerResource } from "../diagnostics/trackResources";
|
|
13
|
+
import { logErrors } from "../errors";
|
|
14
|
+
import { ClientWatcher } from "./pathValueClientWatcher";
|
|
15
|
+
import { blue, green, yellow } from "socket-function/src/formatting/logColors";
|
|
16
|
+
import { appendToPathStr, getParentPathStr, getPathDepth, getPathIndexAssert, getPathSuffix, hack_getPackedPathSuffix, hack_setPackedPathSuffix } from "../path";
|
|
17
|
+
import { isEmpty } from "../misc";
|
|
18
|
+
import debugbreak from "debugbreak";
|
|
19
|
+
import { isDevDebugbreak } from "../config";
|
|
20
|
+
import { debugLog } from "../0-path-value-core/debugLogs";
|
|
21
|
+
|
|
22
|
+
export class RemoteWatcher {
|
|
23
|
+
public static DEBUG = false;
|
|
24
|
+
|
|
25
|
+
public static REMOTE_WATCH_FUNCTION = (config: WatchConfig, authorityId: string) => PathValueController.nodes[authorityId].watchLatest(config);
|
|
26
|
+
public static REMOTE_UNWATCH_FUNCTION = (config: WatchConfig, authorityId: string) => PathValueController.nodes[authorityId].unwatchLatest(config);
|
|
27
|
+
|
|
28
|
+
// path => nodeId
|
|
29
|
+
// isOwnNodeId(nodeId) means we are watching it locally
|
|
30
|
+
private remoteWatchPaths = registerResource("paths|remoteWatchPaths", new Map<string, string>());
|
|
31
|
+
private remoteWatchParents = registerResource("paths|remoteWatchParents", new Map<string, {
|
|
32
|
+
nodesId: Map<string, {
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
}>;
|
|
36
|
+
fullNodeCount: number;
|
|
37
|
+
}>());
|
|
38
|
+
|
|
39
|
+
private disconnectedPaths = registerResource("paths|disconnectedPaths", new Set<string>());
|
|
40
|
+
private disconnectedParents = registerResource("paths|disconnectedParents", new Set<string>());
|
|
41
|
+
|
|
42
|
+
private reconnectCache = cache((authorityId: string) => {
|
|
43
|
+
SocketFunction.onNextDisconnect(authorityId, () => {
|
|
44
|
+
logErrors((async () => {
|
|
45
|
+
// NOTE: While we have no remote unwatcher we leave the values in our cache. They might be stale
|
|
46
|
+
// for a bit (which might result in our writes being rejected), but... hopefully we find a new
|
|
47
|
+
// server to watch really fast! (Otherwise... if there is no authority... stale data is better
|
|
48
|
+
// than no data).
|
|
49
|
+
this.reconnectCache.clear(authorityId);
|
|
50
|
+
let paths: string[] = [];
|
|
51
|
+
for (let [path, remoteAuthorityId] of this.remoteWatchPaths.entries()) {
|
|
52
|
+
if (remoteAuthorityId === authorityId) {
|
|
53
|
+
paths.push(path);
|
|
54
|
+
this.remoteWatchPaths.delete(path);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let parentPaths: string[] = [];
|
|
58
|
+
for (let [path, watchObj] of this.remoteWatchParents.entries()) {
|
|
59
|
+
if (typeof watchObj.nodesId === "string") {
|
|
60
|
+
if (watchObj.nodesId === authorityId) {
|
|
61
|
+
this.remoteWatchParents.delete(path);
|
|
62
|
+
parentPaths.push(path);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
if (watchObj.nodesId.has(authorityId)) {
|
|
66
|
+
watchObj.nodesId.delete(authorityId);
|
|
67
|
+
if (isEmpty(watchObj.nodesId)) {
|
|
68
|
+
this.remoteWatchParents.delete(path);
|
|
69
|
+
}
|
|
70
|
+
parentPaths.push(path);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (paths.length === 0 && parentPaths.length === 0) return;
|
|
75
|
+
|
|
76
|
+
for (let path of paths) {
|
|
77
|
+
this.disconnectedPaths.add(path);
|
|
78
|
+
}
|
|
79
|
+
for (let path of parentPaths) {
|
|
80
|
+
this.disconnectedParents.add(path);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// NOTE: We keep around the old authorities in routing (for a few minutes), so if a new node doesn't start,
|
|
84
|
+
// we will match the disconnected node again. This means we have a few minutes to start another server.
|
|
85
|
+
// TODO: Retry instead of throwing
|
|
86
|
+
console.log(yellow(`Trying to find new authority for disconnected watches ${paths.length} paths and ${parentPaths.length} parent paths`));
|
|
87
|
+
logErrors(this.tryToReconnectNow());
|
|
88
|
+
})());
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// NOTE: Lower numbers mean more lag on disconnected values, but... faster reconnection. AND... if a node is disconnected,
|
|
93
|
+
// the chances the server will be useless is high, so... lag is probably fine in exchange for faster
|
|
94
|
+
// reconnection.
|
|
95
|
+
private reconnectLoop = runInfinitePoll(10000, () => this.tryToReconnectNow());
|
|
96
|
+
private async tryToReconnectNow() {
|
|
97
|
+
if (!this.disconnectedPaths.size && !this.disconnectedParents.size) return;
|
|
98
|
+
|
|
99
|
+
// We HAVE to watch synchronously, so there is no gap where a path can be removed from disconnectedPaths,
|
|
100
|
+
// and we can trigger it to be re-added
|
|
101
|
+
this.internalWatchLatest({
|
|
102
|
+
paths: Array.from(this.disconnectedPaths),
|
|
103
|
+
parentPaths: Array.from(this.disconnectedParents),
|
|
104
|
+
tryReconnect: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
public debugIsWatchingPath(path: string) {
|
|
110
|
+
// HACK: If there is no read node... pretend we are watching it. Because... for now, this is a known
|
|
111
|
+
// and the code calling this function isn't looking for that specific issue.
|
|
112
|
+
return this.remoteWatchPaths.has(path) || !pathValueAuthority2.getSingleReadNodeSync(path);
|
|
113
|
+
}
|
|
114
|
+
public debugIsWatchingParentPath(path: string) {
|
|
115
|
+
return this.remoteWatchParents.has(path) || !pathValueAuthority2.getSingleReadNodeSync(path);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public watchLatest(config: WatchConfig & { debugName?: string }) {
|
|
119
|
+
logErrors(this.watchLatestPromise(config));
|
|
120
|
+
}
|
|
121
|
+
// NOTE: This is private, as who wants to wait for watch to finish?
|
|
122
|
+
// 1) The value won't be ready when it finishes.
|
|
123
|
+
// 2) If it errors out, they can't do anything to fix the error
|
|
124
|
+
// 3) We retry internally anyways
|
|
125
|
+
private watchLatestPromise(config: WatchConfig & { debugName?: string }) {
|
|
126
|
+
// NOTE: If none of the values are remote... early out. This has been profile to save a bit of time,
|
|
127
|
+
// mostly due to avoiding the async call.
|
|
128
|
+
if (config.parentPaths.length === 0 && config.paths.every(x => pathValueAuthority2.isSelfAuthority(x))) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
return this.watchLatestBase(config);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Returns [] if we aren't remotely syncing the path. */
|
|
135
|
+
public getMultiNodesForParent(path: string): Map<string, unknown> | undefined {
|
|
136
|
+
let watchObj = this.remoteWatchParents.get(path);
|
|
137
|
+
return watchObj?.nodesId;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private watchUnwatchSerial = runInSerial((name: string, fnc: () => Promise<unknown>) =>
|
|
141
|
+
measureBlock(fnc, name)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
/** NOTE: We dedupe duplicate watches in watchLatest. */
|
|
145
|
+
private async watchLatestBase(config: WatchConfig & { debugName?: string }) {
|
|
146
|
+
await this.watchUnwatchSerial("watchLatestBase", async () => {
|
|
147
|
+
await pathValueAuthority2.waitUntilRoutingIsReady();
|
|
148
|
+
|
|
149
|
+
this.internalWatchLatest(config);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// IMPORTANT! This function is expected to be synchronous, due to how tryToReconnectNow works.
|
|
153
|
+
@measureFnc
|
|
154
|
+
private internalWatchLatest(config: WatchConfig & {
|
|
155
|
+
debugName?: string;
|
|
156
|
+
tryReconnect?: boolean;
|
|
157
|
+
}) {
|
|
158
|
+
let watchesPerAuthority = new Map<string, { paths: string[]; parentPaths: string[] }>();
|
|
159
|
+
|
|
160
|
+
let unwatchParentsPerAuthority = new Map<string, Set<string>>();
|
|
161
|
+
|
|
162
|
+
let newDisconnectPaths = 0;
|
|
163
|
+
let newDisconnectParents = 0;
|
|
164
|
+
let foundPaths = 0;
|
|
165
|
+
let foundParentPaths = 0;
|
|
166
|
+
measureBlock(() => {
|
|
167
|
+
for (let path of config.paths) {
|
|
168
|
+
// IMPORTANT! We have to await, otherwise we might see the paths are watched, skip them
|
|
169
|
+
// find another path which has an authority, then go to watch that... which causes
|
|
170
|
+
// us to circumvent earlier watchers, causing us to watch out of order, causing reads
|
|
171
|
+
// to come back out of order, causing watching functions to trigger out of order!
|
|
172
|
+
let remoteAuthorityId = this.remoteWatchPaths.get(path);
|
|
173
|
+
if (remoteAuthorityId !== undefined) continue;
|
|
174
|
+
|
|
175
|
+
let authorityId = pathValueAuthority2.getSingleReadNodeSync(path);
|
|
176
|
+
if (!authorityId) {
|
|
177
|
+
if (!this.disconnectedPaths.has(path)) {
|
|
178
|
+
newDisconnectPaths++;
|
|
179
|
+
this.disconnectedPaths.add(path);
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
this.disconnectedPaths.delete(path);
|
|
184
|
+
foundPaths++;
|
|
185
|
+
|
|
186
|
+
this.remoteWatchPaths.set(path, authorityId);
|
|
187
|
+
let paths = watchesPerAuthority.get(authorityId);
|
|
188
|
+
if (!paths) {
|
|
189
|
+
paths = { paths: [], parentPaths: [] };
|
|
190
|
+
watchesPerAuthority.set(authorityId, paths);
|
|
191
|
+
}
|
|
192
|
+
paths.paths.push(path);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (let path of config.parentPaths) {
|
|
196
|
+
let watchObj = this.remoteWatchParents.get(path);
|
|
197
|
+
if (
|
|
198
|
+
watchObj
|
|
199
|
+
&& watchObj.nodesId.size === watchObj.fullNodeCount
|
|
200
|
+
&& !config.tryReconnect
|
|
201
|
+
) continue;
|
|
202
|
+
|
|
203
|
+
let { nodes } = pathValueAuthority2.getChildReadNodes(path, {
|
|
204
|
+
// Pass existing connected node ids as preferred
|
|
205
|
+
preferredNodeIds: watchObj?.nodesId
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
let rangeStart = 0;
|
|
209
|
+
let rangeEnd = 1;
|
|
210
|
+
|
|
211
|
+
let nodesToAdd = new Map<string, { start: number; end: number }>();
|
|
212
|
+
let range = decodeParentFilter(path);
|
|
213
|
+
if (range) {
|
|
214
|
+
rangeStart = range.start;
|
|
215
|
+
rangeEnd = range.end;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
let curStart = 0;
|
|
220
|
+
for (let node of nodes) {
|
|
221
|
+
let r = node.range;
|
|
222
|
+
if (r.start > curStart) {
|
|
223
|
+
console.warn(`There is a gap in the range of nodes for path ${path} values from from ${curStart} to ${r.start} cannot be found in any nodes.`);
|
|
224
|
+
curStart = r.start;
|
|
225
|
+
}
|
|
226
|
+
let nextEnd = Math.min(r.end, rangeEnd);
|
|
227
|
+
// Ignore values with no overlap (mostly trailing values)
|
|
228
|
+
if (nextEnd <= curStart) continue;
|
|
229
|
+
|
|
230
|
+
nodesToAdd.set(node.nodeId, { start: curStart, end: nextEnd });
|
|
231
|
+
curStart = nextEnd;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let fullRangeCovered = true;
|
|
236
|
+
{
|
|
237
|
+
let lastValue = rangeStart;
|
|
238
|
+
for (let node of nodes) {
|
|
239
|
+
if (node.range.start > lastValue) {
|
|
240
|
+
fullRangeCovered = false;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
lastValue = node.range.end;
|
|
244
|
+
}
|
|
245
|
+
if (lastValue < rangeEnd) {
|
|
246
|
+
fullRangeCovered = false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!fullRangeCovered) {
|
|
250
|
+
if (!this.disconnectedParents.has(path)) {
|
|
251
|
+
newDisconnectParents++;
|
|
252
|
+
this.disconnectedParents.add(path);
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
this.disconnectedParents.delete(path);
|
|
256
|
+
foundParentPaths++;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (watchObj) {
|
|
260
|
+
for (let nodeId of watchObj.nodesId.keys()) {
|
|
261
|
+
if (!nodesToAdd.has(nodeId)) {
|
|
262
|
+
let parentPaths = unwatchParentsPerAuthority.get(nodeId);
|
|
263
|
+
if (!parentPaths) {
|
|
264
|
+
parentPaths = new Set();
|
|
265
|
+
unwatchParentsPerAuthority.set(nodeId, parentPaths);
|
|
266
|
+
}
|
|
267
|
+
parentPaths.add(path);
|
|
268
|
+
watchObj.nodesId.delete(nodeId);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!watchObj) {
|
|
274
|
+
watchObj = { nodesId: new Map(), fullNodeCount: 0 };
|
|
275
|
+
this.remoteWatchParents.set(path, watchObj);
|
|
276
|
+
}
|
|
277
|
+
watchObj.fullNodeCount = nodes.length;
|
|
278
|
+
for (let [nodeId, range] of nodesToAdd) {
|
|
279
|
+
watchObj.nodesId.set(nodeId, range);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (let authorityId of nodesToAdd.keys()) {
|
|
283
|
+
let paths = watchesPerAuthority.get(authorityId);
|
|
284
|
+
if (!paths) {
|
|
285
|
+
paths = { paths: [], parentPaths: [] };
|
|
286
|
+
watchesPerAuthority.set(authorityId, paths);
|
|
287
|
+
}
|
|
288
|
+
paths.parentPaths.push(path);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}, `watchLatest|getSingleReadNode`);
|
|
292
|
+
|
|
293
|
+
if (newDisconnectPaths > 0 || newDisconnectParents > 0) {
|
|
294
|
+
console.log(yellow(`Some paths have no authority. We will search for an authority periodically until we find an authority. New missing ${newDisconnectPaths} paths and ${newDisconnectParents} parent paths, total missing ${this.disconnectedPaths.size} paths and ${this.disconnectedParents.size} parent paths.`));
|
|
295
|
+
let first10 = Array.from(this.disconnectedPaths).slice(0, 10);
|
|
296
|
+
for (let path of first10) {
|
|
297
|
+
console.log(`\t${path}`);
|
|
298
|
+
}
|
|
299
|
+
if (this.disconnectedPaths.size > 10) {
|
|
300
|
+
console.log(`\t... and ${this.disconnectedPaths.size - 10} more paths`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (config.tryReconnect) {
|
|
304
|
+
if (foundPaths || foundParentPaths) {
|
|
305
|
+
console.log(green(`Reconnected ${foundPaths} paths and ${foundParentPaths} parent paths.`));
|
|
306
|
+
}
|
|
307
|
+
if (this.disconnectedPaths.size > 0 || this.disconnectedParents.size > 0) {
|
|
308
|
+
console.log(yellow(`We still have ${this.disconnectedPaths.size} paths and ${this.disconnectedParents.size} parent paths disconnected.`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (let [authorityId, { paths, parentPaths }] of watchesPerAuthority.entries()) {
|
|
313
|
+
// Only remote watch remotes!
|
|
314
|
+
if (isOwnNodeId(authorityId)) continue;
|
|
315
|
+
|
|
316
|
+
if (RemoteWatcher.DEBUG) {
|
|
317
|
+
console.log(`${blue("Watching")} ${paths.length} paths and ${parentPaths.length} parent paths on ${authorityId}\n\t authority: ${blue(nodePathAuthority.getAuthorityPaths(authorityId)?.map(x => nodePathAuthority.getArchiveDirectory(x)).join(" | ") || "")}`);
|
|
318
|
+
if (parentPaths.length > 0) {
|
|
319
|
+
console.log(`\t parentPaths:`);
|
|
320
|
+
for (let path of parentPaths) {
|
|
321
|
+
console.log(`\t ${path}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (isDevDebugbreak()) {
|
|
327
|
+
for (let path of paths) {
|
|
328
|
+
debugLog("remoteWatcher outer WATCH", { path, remoteNodeId: authorityId });
|
|
329
|
+
}
|
|
330
|
+
for (let path of parentPaths) {
|
|
331
|
+
debugLog("remoteWatcher outer WATCH PARENT", { path, remoteNodeId: authorityId });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.reconnectCache(authorityId);
|
|
336
|
+
// Don't throw, as the reconnectCache will eventually resolve errors and watch successfully
|
|
337
|
+
logErrors(this.watchLatestRemote({
|
|
338
|
+
authorityId,
|
|
339
|
+
paths,
|
|
340
|
+
parentPaths,
|
|
341
|
+
debugName: config.debugName
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// No throttling for unwatches, so we don't have race conditions?
|
|
346
|
+
for (let [authorityId, parentPaths] of unwatchParentsPerAuthority.entries()) {
|
|
347
|
+
if (isOwnNodeId(authorityId)) continue;
|
|
348
|
+
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({ paths: [], parentPaths: Array.from(parentPaths) }, authorityId));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public getChildPatchWatchChecker(config: {
|
|
353
|
+
parentPath: string;
|
|
354
|
+
nodeId: string;
|
|
355
|
+
}): {
|
|
356
|
+
isWatchedByNodeId(childPath: string): boolean;
|
|
357
|
+
} {
|
|
358
|
+
let remoteNodes = this.remoteWatchParents.get(config.parentPath);
|
|
359
|
+
if (!remoteNodes) return { isWatchedByNodeId: () => false };
|
|
360
|
+
const range = remoteNodes.nodesId.get(config.nodeId);
|
|
361
|
+
if (!range) return { isWatchedByNodeId: () => false };
|
|
362
|
+
return {
|
|
363
|
+
isWatchedByNodeId(childPath) {
|
|
364
|
+
return matchesParentRangeFilter({
|
|
365
|
+
parentPath: config.parentPath,
|
|
366
|
+
fullPath: childPath,
|
|
367
|
+
start: range.start,
|
|
368
|
+
end: range.end,
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private watchLatestRemote = batchFunction(
|
|
375
|
+
{ delay: 10, throttleWindow: isNode() ? 500 : undefined },
|
|
376
|
+
async (batched: (WatchConfig & { authorityId: string; debugName?: string })[]) => {
|
|
377
|
+
await this.watchUnwatchSerial("watchLatestRemote", async () => {
|
|
378
|
+
let byAuthority = new Map<string, { paths: Set<string>; parentPaths: Set<string>; }>();
|
|
379
|
+
for (let { authorityId, paths, parentPaths } of batched) {
|
|
380
|
+
let pathsPerAuthority = byAuthority.get(authorityId);
|
|
381
|
+
if (!pathsPerAuthority) {
|
|
382
|
+
pathsPerAuthority = { paths: new Set(), parentPaths: new Set() };
|
|
383
|
+
byAuthority.set(authorityId, pathsPerAuthority);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (let path of paths) {
|
|
387
|
+
if (this.remoteWatchPaths.get(path) !== authorityId) continue;
|
|
388
|
+
pathsPerAuthority.paths.add(path);
|
|
389
|
+
}
|
|
390
|
+
for (let path of parentPaths) {
|
|
391
|
+
let watchObj = this.remoteWatchParents.get(path);
|
|
392
|
+
if (!watchObj) continue;
|
|
393
|
+
if (!watchObj.nodesId.has(authorityId)) continue;
|
|
394
|
+
pathsPerAuthority.parentPaths.add(path);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (let [authorityId, { paths, parentPaths }] of byAuthority) {
|
|
398
|
+
if (isDevDebugbreak()) {
|
|
399
|
+
for (let path of paths) {
|
|
400
|
+
debugLog("remoteWatcher inner WATCH", { path, remoteNodeId: authorityId });
|
|
401
|
+
}
|
|
402
|
+
for (let path of parentPaths) {
|
|
403
|
+
debugLog("remoteWatcher inner WATCH PARENT", { path, remoteNodeId: authorityId });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// NOTE: We log watches here because we want to log batched watches
|
|
407
|
+
ActionsHistory.OnNewWatches({ newPathsWatched: paths, newParentsWatched: parentPaths, debugName: batched[0].debugName, authorityId });
|
|
408
|
+
let config: WatchConfig = {
|
|
409
|
+
paths: Array.from(paths),
|
|
410
|
+
parentPaths: Array.from(parentPaths),
|
|
411
|
+
};
|
|
412
|
+
logErrors(RemoteWatcher.REMOTE_WATCH_FUNCTION(config, authorityId));
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
/** NOTE: We do not count the number of watches, so... only unwatch if everything is done watching a path! */
|
|
419
|
+
|
|
420
|
+
public unwatchLatest(config: WatchConfig) {
|
|
421
|
+
// IMPORTANT! This serial is important, otherwise there is a race condition between starting to unwatch,
|
|
422
|
+
// and unwatching, which can result in marking a path as unwatched when it is watched.
|
|
423
|
+
// 1) We want to unwatch, and call unwatchLatest
|
|
424
|
+
// 2) There is a delay in unwatchLatest
|
|
425
|
+
// 3) We want to watch again, and do, and receive a new value
|
|
426
|
+
// 4) unwatchLatest runs, causing us to mark the path as unsynced
|
|
427
|
+
// 5) The user of the watch runs, see's the value is unsynced, and so waits,
|
|
428
|
+
// but it never receives the value.
|
|
429
|
+
// We ALSO could just check if the path is watched here, but... that is extra work, and... the path
|
|
430
|
+
// from here to where we unwatch is pretty short (PathWatcher.unwatchPath => hook => RemoteWatcher.unwatchLatest),
|
|
431
|
+
// so I don't imagine any more bugs... hopefully.
|
|
432
|
+
logErrors(this.watchUnwatchSerial("unwatchLatest", async () => {
|
|
433
|
+
this.unwatchLatestBase(config);
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
@measureFnc
|
|
437
|
+
private unwatchLatestBase(config: WatchConfig) {
|
|
438
|
+
let unwatchesPerAuthority = new Map<string, WatchConfig>();
|
|
439
|
+
|
|
440
|
+
const innerPathRemove = (path: string, remoteAuthorityId: string | undefined) => {
|
|
441
|
+
if (remoteAuthorityId === undefined) return;
|
|
442
|
+
this.remoteWatchPaths.delete(path);
|
|
443
|
+
this.disconnectedPaths.delete(path);
|
|
444
|
+
let paths = unwatchesPerAuthority.get(remoteAuthorityId);
|
|
445
|
+
if (!paths) {
|
|
446
|
+
paths = { paths: [], parentPaths: [] };
|
|
447
|
+
unwatchesPerAuthority.set(remoteAuthorityId, paths);
|
|
448
|
+
}
|
|
449
|
+
paths.paths.push(path);
|
|
450
|
+
};
|
|
451
|
+
const innerParentPathRemove = (path: string, remoteAuthorityId: string | undefined) => {
|
|
452
|
+
if (remoteAuthorityId === undefined) return;
|
|
453
|
+
this.remoteWatchParents.delete(path);
|
|
454
|
+
this.disconnectedParents.delete(path);
|
|
455
|
+
let paths = unwatchesPerAuthority.get(remoteAuthorityId);
|
|
456
|
+
if (!paths) {
|
|
457
|
+
paths = { paths: [], parentPaths: [] };
|
|
458
|
+
unwatchesPerAuthority.set(remoteAuthorityId, paths);
|
|
459
|
+
}
|
|
460
|
+
paths.parentPaths.push(path);
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
for (let path of config.paths) {
|
|
464
|
+
let remoteAuthorityId = this.remoteWatchPaths.get(path);
|
|
465
|
+
innerPathRemove(path, remoteAuthorityId);
|
|
466
|
+
}
|
|
467
|
+
for (let path of config.parentPaths) {
|
|
468
|
+
let watchObj = this.remoteWatchParents.get(path);
|
|
469
|
+
if (!watchObj) continue;
|
|
470
|
+
for (let authorityId of watchObj.nodesId.keys()) {
|
|
471
|
+
innerParentPathRemove(path, authorityId);
|
|
472
|
+
}
|
|
473
|
+
this.remoteWatchParents.delete(path);
|
|
474
|
+
}
|
|
475
|
+
for (let [authorityIdBase, { paths, parentPaths }] of unwatchesPerAuthority.entries()) {
|
|
476
|
+
if (isDevDebugbreak()) {
|
|
477
|
+
for (let path of paths) {
|
|
478
|
+
debugLog("remoteWatcher inner UNWATCH", { path, remoteNodeId: authorityIdBase });
|
|
479
|
+
}
|
|
480
|
+
for (let path of parentPaths) {
|
|
481
|
+
debugLog("remoteWatcher inner UNWATCH PARENT", { path, remoteNodeId: authorityIdBase });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!isOwnNodeId(authorityIdBase)) {
|
|
485
|
+
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({ paths, parentPaths }, authorityIdBase));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@measureFnc
|
|
491
|
+
public unwatchEventPaths(config: WatchConfig) {
|
|
492
|
+
// NOTE: We HAVE to tell the remote to unwatch... because, it know not have the full PathValue,
|
|
493
|
+
// and so not know it is an event path (we could have receive it from another node), so...
|
|
494
|
+
// it might NOT know it should dispose it!
|
|
495
|
+
this.unwatchLatest(config);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
public getExistingWatchRemoteNodeId(path: string) {
|
|
499
|
+
return this.remoteWatchPaths.get(path);
|
|
500
|
+
}
|
|
501
|
+
public getAllRemoteWatchedNodeIds() {
|
|
502
|
+
return Array.from(new Set(this.remoteWatchPaths.values()));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public async refreshAllWatches(authorityNodeId: string) {
|
|
506
|
+
await pathValueAuthority2.waitUntilRoutingIsReady();
|
|
507
|
+
await this.watchUnwatchSerial("refreshAllWatches", async () => {
|
|
508
|
+
// Unwatch all paths, reset all remoteWatchPaths, and then watch all paths again SYNCHRONOUSLY,
|
|
509
|
+
// so there is no time we aren't watching the paths.
|
|
510
|
+
// - AND parent paths as well (at the same time)
|
|
511
|
+
// NOTE: This might result in the same authority being used, which is fine. The unwatch + watch
|
|
512
|
+
// should run in serial (due to how connections work), and result in being sent a new value.
|
|
513
|
+
// It might also result in a different authority being used, which is fine, and can resolve
|
|
514
|
+
// cases where an authority is in a broken state.
|
|
515
|
+
|
|
516
|
+
let pathsToWatch = new Set<string>();
|
|
517
|
+
let parentPathsToWatch = new Set<string>();
|
|
518
|
+
for (let [path, nodeId] of this.remoteWatchPaths) {
|
|
519
|
+
if (nodeId === authorityNodeId) {
|
|
520
|
+
pathsToWatch.add(path);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Delete after, so we don't break the iteration
|
|
524
|
+
for (let path of pathsToWatch) {
|
|
525
|
+
this.remoteWatchPaths.delete(path);
|
|
526
|
+
}
|
|
527
|
+
for (let [path, watchObj] of this.remoteWatchParents) {
|
|
528
|
+
if (watchObj.nodesId.has(authorityNodeId)) {
|
|
529
|
+
watchObj.nodesId.delete(authorityNodeId);
|
|
530
|
+
parentPathsToWatch.add(path);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
let paths = Array.from(pathsToWatch);
|
|
534
|
+
let parentPaths = Array.from(parentPathsToWatch);
|
|
535
|
+
|
|
536
|
+
logErrors(RemoteWatcher.REMOTE_UNWATCH_FUNCTION({ paths, parentPaths }, authorityNodeId));
|
|
537
|
+
this.internalWatchLatest({
|
|
538
|
+
paths: Array.from(pathsToWatch),
|
|
539
|
+
parentPaths: Array.from(parentPathsToWatch),
|
|
540
|
+
debugName: "refreshAllWatches",
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
public getWatchReason(path: string): "direct" | "parent" | undefined {
|
|
546
|
+
if (this.remoteWatchPaths.has(path)) return "direct";
|
|
547
|
+
if (this.remoteWatchParents.has(getParentPathStr(path))) return "parent";
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export const remoteWatcher = new RemoteWatcher();
|
|
553
|
+
(globalThis as any).remoteWatcher = remoteWatcher;
|
|
554
|
+
setImmediate(() => {
|
|
555
|
+
pathWatcher.watchUnwatched(config => remoteWatcher.unwatchLatest(config));
|
|
556
|
+
authorityStorage.setGetMultiNodesForParent(path => remoteWatcher.getMultiNodesForParent(path));
|
|
557
|
+
pathValueCommitter.setGetRemoteWatchNodeId(path => remoteWatcher.getExistingWatchRemoteNodeId(path));
|
|
558
|
+
});
|