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.
Files changed (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1 @@
1
+ export { LOCAL_DOMAIN } from "./NodePathAuthorities";
@@ -0,0 +1,565 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { delay, batchFunction } from "socket-function/src/batching";
3
+ import { markArrayAsSplitable } from "socket-function/src/fixLargeNetworkCalls";
4
+ import { isNode, timeInSecond } from "socket-function/src/misc";
5
+ import { measureBlock, measureFnc, registerMeasureInfo } from "socket-function/src/profiling/measure";
6
+ import { isTrustedByNode } from "../-d-trust/NetworkTrust2";
7
+ import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
8
+ import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
9
+ import { ActionsHistory } from "../diagnostics/ActionsHistory";
10
+ import { errorToUndefined, logErrors, timeoutToUndefined } from "../errors";
11
+ import { getPathFromStr } from "../path";
12
+ import { nodePathAuthority, pathValueAuthority2 } from "./NodePathAuthorities";
13
+ import { PathValueController } from "./PathValueController";
14
+ import { PathValue, MAX_ACCEPTED_CHANGE_AGE, lockWatcher, authorityStorage, writeValidStorage, WriteState, lockToCallback, pathWatcher, MAX_CHANGE_AGE, debugPathValue, debugPathValuePath } from "./pathValueCore";
15
+ import debugbreak from "debugbreak";
16
+ import { red } from "socket-function/src/formatting/logColors";
17
+ import { registerDynamicResource } from "../diagnostics/trackResources";
18
+ import { StatsValue, addToStats, addToStatsValue, createStatsValue, getStatsTop } from "socket-function/src/profiling/stats";
19
+ import { formatStats } from "socket-function/src/profiling/statsFormat";
20
+ import { formatNumber, formatTime } from "socket-function/src/formatting/format";
21
+ import { isClient } from "../config2";
22
+ import { remoteWatcher } from "../1-path-client/RemoteWatcher";
23
+ import { debugLog, isDebugLogEnabled } from "./debugLogs";
24
+ import { diskLog } from "../diagnostics/logs/diskLogger";
25
+
26
+ /*
27
+ - commitWrites <= creating writes
28
+ - ingestValues <= forwardWrites
29
+ - ingestValidStates <= forwarded valid states
30
+ = loop
31
+ - setValidState
32
+ - gather PathValue changes
33
+ - getValidStateChangedTriggers
34
+ - calculateInitialValidState
35
+ - REMOTE.ingestValidStates => forwarded valid states
36
+ - pathWatcher.triggerValuesChanged => forwardWrites + local clients
37
+ - broadcastValues
38
+ */
39
+
40
+ class PathValueCommitter {
41
+ private pendingCommits = new Set<Promise<unknown>>();
42
+ private addCommitPromise(promise: Promise<unknown>) {
43
+ logErrors(promise);
44
+ this.pendingCommits.add(promise);
45
+ void promise.finally(() => this.pendingCommits.delete(promise));
46
+ }
47
+ public async waitForValuesToCommit() {
48
+ await Promise.all(this.pendingCommits);
49
+ // HACK: Wait a bit more, for the websocket to send the values (and for some batching, etc, etc)
50
+ //await new Promise(resolve => setTimeout(resolve, 100));
51
+ await delay("afterio");
52
+ }
53
+
54
+ @measureFnc
55
+ // Returns true if all the writes are accepted by some node (this should almost always be the case)
56
+ public commitValues(values: PathValue[], predictWrites: "predictWrites" | undefined): void {
57
+ if (values.length === 0) return;
58
+ ActionsHistory.OnWrite(values);
59
+
60
+ let now = Date.now();
61
+
62
+ // Validate the writes are in order (and not too old), just in case some delay in our code
63
+ // causes them to be too old before we even get to send them! (Or if addWrites is called
64
+ // from an endpoint?)
65
+ {
66
+ let maxAge = now - MAX_ACCEPTED_CHANGE_AGE;
67
+ let prevTime = 0;
68
+ for (let pathValue of values) {
69
+ if (pathValue.time.time < maxAge) {
70
+ // NOTE: LIKELY caused by synchronizing taking too long, which is likely due to the PathValueServer massively lagging
71
+ // - Check the caller proxyWatcher startTime vs lastSyncTime, to see how long it took from start to sync
72
+ // (assuming the caller is a commitFunction, and not just a general watcher. If it is a general
73
+ // watcher it might have taken a really long time to run, in which case check the watchFunction).
74
+ let message = `MAX_CHANGE_AGE_EXCEEDED! Cannot commit write, that is before the max age ${pathValue.time.time} < ${maxAge}. Acceping this write would result in changes in the past that wouldn't propagate correctly`;
75
+ console.error(red(message));
76
+ debugbreak(2);
77
+ debugger;
78
+ throw new Error(message);
79
+ }
80
+ if (pathValue.time.time < prevTime) {
81
+ debugbreak(2);
82
+ debugger;
83
+ throw new Error(`PathValues must be in increasing order, but they were not.`);
84
+ }
85
+ if (pathValue.time.version % 1 !== 0) {
86
+ debugbreak(2);
87
+ debugger;
88
+ throw new Error(`PathValues must have an integer version, but they did not. Path: ${pathValue.path}, Time: ${pathValue.time.time}, Version: ${pathValue.time.version}`);
89
+ }
90
+ prevTime = pathValue.time.time;
91
+ }
92
+ }
93
+
94
+ let pathValuesToIngest: PathValue[] = [];
95
+ if (predictWrites) {
96
+ pathValuesToIngest = values;
97
+ } else {
98
+ pathValuesToIngest = values.filter(pathValue => pathValueAuthority2.isSelfAuthority(pathValue.path));
99
+ }
100
+
101
+ // NOTE: Error out early on the client, so we get better errors AND so we can avoid even ingesting the values
102
+ // TODO: Move this error to PathValueProxyWatcher, so it can be thrown on the exact line that does the write.
103
+ // - This can be down by caching path (probably just the domain), and telling PathValueProxyWatcher
104
+ // to throw on any accesses to it.
105
+ // - PathValueProxyWatcher could even populate this cache itself on reads, so it will probably always
106
+ // thrown on the first write attempt.
107
+ // (It could even populate on the first domain, so it would always populate on the first write
108
+ // on the current domain).
109
+ if (!isNode()) {
110
+ let remoteWrites = values.filter(pathValue => !pathValueAuthority2.isSelfAuthority(pathValue.path));
111
+ if (remoteWrites.length > 0) {
112
+ throw new Error(`Cannot commit writes to paths that are not local to this node. Paths: ${remoteWrites.map(x => x.path).join(", ")}`);
113
+ }
114
+ }
115
+
116
+ // NOTE: This function will just ignore writes we aren't an authority on, so we can just give it everything.
117
+ this.ingestValues(pathValuesToIngest, undefined, undefined);
118
+
119
+ // If all values are local, then there is nothing to broadcast
120
+ if (values.some(x => !pathValueAuthority2.isLocalPath(x.path))) {
121
+ this.addCommitPromise(this.broadcastValues(new Set(values)));
122
+ }
123
+ }
124
+
125
+ private broadcastValues = batchFunction(
126
+ { delay: 10, throttleWindow: 500 },
127
+ async function internal_forwardWrites(valuesBatched: Set<PathValue>[]) {
128
+ let values = new Set(valuesBatched.flatMap(x => Array.from(x)));
129
+
130
+ let valuesPerOtherAuthority = new Map<string, PathValue[]>();
131
+ for (let pathValue of values) {
132
+ if (isDebugLogEnabled()) {
133
+ debugLog("CREATE VALUE", { path: pathValue.path, time: pathValue.time.time });
134
+ }
135
+
136
+ let otherAuthorities = await pathValueAuthority2.getWriteNodes(pathValue.path);
137
+ otherAuthorities = otherAuthorities.filter(x => !isOwnNodeId(x));
138
+ if (otherAuthorities.length === 0 && !pathValueAuthority2.isSelfAuthority(pathValue.path)) {
139
+ throw new Error(`There are no authorities for path ${pathValue.path}. The write will be lost.`);
140
+ }
141
+ for (let otherAuthority of otherAuthorities) {
142
+ let values = valuesPerOtherAuthority.get(otherAuthority);
143
+ if (!values) {
144
+ values = [];
145
+ valuesPerOtherAuthority.set(otherAuthority, values);
146
+ }
147
+ values.push(pathValue);
148
+ }
149
+ }
150
+
151
+ // for (let [authorityId, values] of valuesPerOtherAuthority) {
152
+ // console.log(`Sending ${values.length} values to ${authorityId} (${pathValueAuthority2.getAuthorityPaths(authorityId)?.map(x => pathValueAuthority2.getArchiveDirectory(x)).join(" | ")})`);
153
+ // // for (let value of values) {
154
+ // // console.log(" " + value.path);
155
+ // // }
156
+ // }
157
+
158
+ // Don't send to bad nodes for 60 seconds
159
+ const nodeIgnoreTime = Date.now() - 1000 * 60;
160
+ let promises = Array.from(valuesPerOtherAuthority.entries()).map(async ([otherAuthority, values]) => {
161
+ let disconnected = SocketFunction.getLastDisconnectTime(otherAuthority);
162
+ if (disconnected && disconnected > nodeIgnoreTime) {
163
+ // If it disconnected recently... don't send to it for a little bit, so we don't spend
164
+ // all of our time spamming disconnected nodes
165
+ return;
166
+ }
167
+
168
+ let isTrusted = await isTrustedByNode(otherAuthority);
169
+ if (!isTrusted) {
170
+ throw new Error(`Tried to write to paths on authorities not trusted by us. You probably need to call a function instead of directly writing to the server schema. Authority: ${otherAuthority}, Paths: ${values.map(x => getPathFromStr(x.path).join(".")).join(", ")}`);
171
+ }
172
+
173
+ // NOTE: We don't retry on failure, because... we broadcasted it anyways, AND this is supposed
174
+ // to be on a trusted node (which should always be a server), so... either our network went
175
+ // down, or all nodes went down. Either way, it likely won't fix itself quickly, and so these
176
+ // writes are going to too old and therefore rejected by the time a server is up anyways.
177
+ markArrayAsSplitable(values);
178
+ const { Querysub } = await import("../4-querysub/Querysub");
179
+ let serializedValues = await pathValueSerializer.serialize(values, { compress: Querysub.COMPRESS_NETWORK });
180
+ diskLog(`Send PathValues to server`, { valueCount: values.length, targetId: otherAuthority, });
181
+ let forwardPromise = PathValueController.nodes[otherAuthority].forwardWrites(
182
+ serializedValues,
183
+ undefined,
184
+ "watchLocks"
185
+ );
186
+ pathValueCommitter.addCommitPromise(forwardPromise);
187
+ logErrors(forwardPromise);
188
+ // NOTE: Usually we don't need watch the locks for our values because upon
189
+ // forwarding them the server will automatically watch them. However...
190
+ // if the call errors out, we can't know if we are watching them, so
191
+ // we have to explicitly find a server to tell us if our writes go through
192
+ // (we can't even just drop our writes, as we don't differentiate predicted
193
+ // and real writes, so we can't tell if it got through and was sent back,
194
+ // or if it was never committed!)
195
+ forwardPromise.catch(() => {
196
+
197
+ // TODO: For the first few seconds, look for new writers and retry. However,
198
+ // after the write is too old, just log the error to the user, as
199
+ // at that point even if a writer appears the write will be too stale.
200
+
201
+ // Watch the readLocks, so we know if the values are valid or not (this
202
+ // code has retry logic).
203
+ // NOTE: Technically if this talks to another authority, and then the original authority
204
+ // comes back, we might be syncing valid states from multiple authorities, and as
205
+ // they aren't timestamped, we could receive an older value later. Which should
206
+ // correct itself, but... we would also be subscribed to multiple authorities,
207
+ // for the same path, which can result in thrashing. But... this probably won't happen...
208
+ lockWatcher.watchValueLocks(values);
209
+ });
210
+ });
211
+ // await, so "waitForValuesToCommit" works correctly
212
+ await Promise.all(promises.map(x => timeoutToUndefined(timeInSecond * 30, errorToUndefined(x))));
213
+ }
214
+ );
215
+
216
+
217
+ private getExistingWatchRemoteNodeId = (path: string): string | undefined => undefined;
218
+ public setGetRemoteWatchNodeId(fnc: (path: string) => string | undefined) {
219
+ this.getExistingWatchRemoteNodeId = fnc;
220
+ }
221
+
222
+ /** When ingesting remote values we batch a bit. Otherwise a stream of values will often result in WAY too many
223
+ * clientside evaluations. The network will already add a lot of delay, so this shouldn't be noticeable.
224
+ * - AND, with the throttleWindow, we generally won't even be waiting anyways.
225
+ * - The delay is 16ms, as we won't render to the screen faster than that anyways, so handling updates
226
+ * at a rate faster than that will just pointlessly add lag.
227
+ */
228
+ public ingestRemoteValues = batchFunction(
229
+ { delay: 16, throttleWindow: 1000, name: "ingestRemoteValues" },
230
+ async (batched: {
231
+ pathValues: PathValue[],
232
+ parentsSynced?: string[];
233
+ sourceNodeId: string;
234
+ initialTrigger?: "initialTrigger";
235
+ }[]) => {
236
+ let initialTriggers = { values: new Set<PathValue>(), parentPaths: new Set<string>() };
237
+ // Check received values to make sure the authority for them is the same as the sourceNodeId
238
+ // (OR that WE are the authority). If not... it means we received values after changing the watcher,
239
+ // which requires us to ignore the other values
240
+ // (Clients get most of their values from the same server, so they shouldn't require this check)
241
+ if (!isClient()) {
242
+ const self = this;
243
+ measureBlock(function ignoreUnrequestedValues() {
244
+ for (let batch of batched) {
245
+ batch.pathValues = batch.pathValues.filter(value => {
246
+ if (value.lockCount === 0 && value.locks.length > 0) {
247
+ console.error(red(`Ignoring value with invalid lockCount. Was ${value.lockCount}, but we have ${value.locks.length} locks. locks are optional, but lockCount isn't. We should never have locks without having lockCount set. ${debugPathValuePath(value)}`));
248
+ return false;
249
+ }
250
+
251
+ // If we are the authority, accept it (we are implicitly watching all of our own paths)
252
+ if (nodePathAuthority.isSelfAuthority(value.path)) return true;
253
+ let watchingAuthorityId = self.getExistingWatchRemoteNodeId(value.path);
254
+ // If we AREN'T watching it... it's actually fine, we can receive any values.
255
+ // When we start watching, those values will get clobbered.
256
+ if (watchingAuthorityId === undefined) return true;
257
+ if (watchingAuthorityId === batch.sourceNodeId) return true;
258
+ // Also warn, because... if we get a lot of these, there might be a bug.
259
+ // A few when we change watches is possible, but it should be rare.
260
+ console.warn(`Ignoring a value that was received from a different authority than the one we are watching. Path: ${value.path}, Watching Authority (should be source): ${watchingAuthorityId}, Actual Source Authority: ${batch.sourceNodeId}`);
261
+ return false;
262
+ });
263
+ }
264
+ });
265
+ }
266
+
267
+ // The first time we receive value for a watch from a specific node, we need to clobber our history
268
+ // (unless we are the authority). Otherwise bad rejected values can stick around,
269
+ // because they are newer, and our new source doesn't know we have them.
270
+ measureBlock(function resetOnInitialTrigger() {
271
+ for (let batch of batched) {
272
+ if (!batch.initialTrigger) continue;
273
+ for (let pathValue of batch.pathValues) {
274
+ initialTriggers.values.add(pathValue);
275
+ authorityStorage.resetForInitialTrigger(pathValue.path);
276
+ }
277
+ // Reset child paths as well, otherwise extra keys that have been deleted
278
+ // don't get properly removed.
279
+ if (batch.parentsSynced) {
280
+ for (let parentPath of batch.parentsSynced) {
281
+ initialTriggers.parentPaths.add(parentPath);
282
+ let allChildPaths = authorityStorage.getPathsFromParent(parentPath) || [];
283
+ let childChecker = remoteWatcher.getChildPatchWatchChecker({
284
+ parentPath,
285
+ nodeId: batch.sourceNodeId
286
+ });
287
+ for (let childPath of allChildPaths) {
288
+ if (childChecker.isWatchedByNodeId(childPath)) {
289
+ authorityStorage.resetForInitialTrigger(childPath);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ });
296
+
297
+ let pathValues = batched.map(x => x.pathValues).flat();
298
+ let parentsSynced = batched.map(x => x.parentsSynced ?? []).flat();
299
+ let parentSyncedSources = new Map<string, string[]>();
300
+ for (let batch of batched) {
301
+ if (!batch.parentsSynced) continue;
302
+ for (let parentSynced of batch.parentsSynced) {
303
+ let sources = parentSyncedSources.get(parentSynced);
304
+ if (!sources) {
305
+ sources = [];
306
+ parentSyncedSources.set(parentSynced, sources);
307
+ }
308
+ sources.push(batch.sourceNodeId);
309
+ }
310
+ }
311
+
312
+ // Ignore outdated PathValues if we are the authority.
313
+ // - This CAN leave our servers in a bad state. However...
314
+ // 1) We will resync with the disk eventually (in a few hours), fixing the state
315
+ // (IF some servers accepted the values, and other ignored them).
316
+ // 1.1) We should also audit between PathValueServers, which should fix the state
317
+ // MUCH faster.
318
+ // 2) If we DON'T ignore these values, we might end up rejecting an archived value,
319
+ // which breaks a lot of values, and which cannot be automatically recovered from.
320
+ measureBlock(function discardInvalidGenesisValues() {
321
+ let maxAge = Date.now() - MAX_ACCEPTED_CHANGE_AGE;
322
+ // NOTE: We could add less to future age here, but it's fine. This is the time
323
+ // a services (not a client, as services validate clients) can write in the future,
324
+ // which will prevent writes on that path until after that time has passed.
325
+ let maxFutureAge = Date.now() + MAX_ACCEPTED_CHANGE_AGE;
326
+ pathValues = pathValues.filter(value => {
327
+ if (!nodePathAuthority.isSelfAuthority(value.path)) return true;
328
+ if (value.time.time < maxAge) {
329
+ console.error(red(`Ignoring a value that is ${formatTime(Date.now() - value.time.time)} pass change time, which would break assumptions made by archives / locks about change stability. ${debugPathValuePath(value)}`));
330
+ return false;
331
+ }
332
+ if (value.time.time > maxFutureAge) {
333
+ console.error(red(`Ignoring a value that is ${formatTime(value.time.time - Date.now())} in the future, which would break writes to that path until that time passes. ${debugPathValuePath(value)}`));
334
+ return false;
335
+ }
336
+ return true;
337
+ });
338
+ });
339
+
340
+ // NOTE: Values MUST be synchronously ingested, otherwise our "initialTrigger" resetting will break things.
341
+ this.ingestValues(pathValues, parentsSynced, parentSyncedSources, initialTriggers);
342
+ // Wait a microtick to allow triggered watchers to finish.
343
+ await Promise.resolve();
344
+ if (!isNode()) {
345
+ // Wait a frame, to allow painting to happen. (requestAnimationFrame waits until before paint,
346
+ // so we have to wait twice).
347
+ await new Promise(resolve => requestAnimationFrame(resolve));
348
+ await new Promise(resolve => requestAnimationFrame(resolve));
349
+ }
350
+ }
351
+ );
352
+
353
+ @measureFnc
354
+ public ingestValues(
355
+ pathValues: PathValue[],
356
+ parentsSynced: string[] | undefined,
357
+ parentSyncedSources: Map<string, string[]> | undefined,
358
+ initialTriggers?: { values: Set<PathValue>; parentPaths: Set<string> }
359
+ ): void {
360
+ if (pathValues.length === 0 && !parentsSynced?.length) return;
361
+
362
+ let now = Date.now();
363
+
364
+ authorityStorage.ingestValues(pathValues, parentsSynced, parentSyncedSources);
365
+
366
+ // NOTE: This doesn't seem to be where we lag, so we aren't tracking it anymore.
367
+ for (let value of pathValues) {
368
+ let lag = now - value.time.time;
369
+ trackLag(now, lag);
370
+ }
371
+
372
+ // NOTE: Also triggers rejections of watched paths
373
+ this.ingestValidStates([], parentsSynced, undefined, pathValues, initialTriggers);
374
+ }
375
+
376
+ // NOTE: Technically, because we trigger all remotes any time a value changes, even
377
+ // if it was changed by a remote, this could result in excessive notifications. However,
378
+ // the only watchers are ourself, and remotes that consider us an authority.
379
+ // AND, if we are an authority, we aren't asking any remotes for valid states. So...
380
+ // it works out, with authorities receiving few/none external triggerWatchersForValidStateChange calls,
381
+ // and non-authorities having few/no remote watchers.
382
+ @measureFnc
383
+ public ingestValidStates(
384
+ remoteLocksChanged: WriteState[],
385
+ parentsSynced?: string[],
386
+ recomputeValidState?: "recomputeValidState",
387
+ alsoPathValues?: PathValue[],
388
+ initialTriggers?: { values: Set<PathValue>; parentPaths: Set<string> }
389
+ ) {
390
+ if (remoteLocksChanged.length === 0 && !parentsSynced?.length && !alsoPathValues?.length) return;
391
+
392
+ if (alsoPathValues?.some(x => x.path.includes("#BREAK"))) {
393
+ debugger;
394
+ }
395
+
396
+ let now = Date.now();
397
+
398
+ let valuesChanged = new Set<PathValue>();
399
+
400
+ if (alsoPathValues) {
401
+ let complexNewValues: PathValue[] = [];
402
+ // TODO: We can avoid values being updated in the updateValidStatesFromCache call,
403
+ // and then also being used in setWriteValidState. This might save some time.
404
+ // It is rare though, as valid states are mostly only for function predictions,
405
+ // and usually arrive after the ValuePaths? Maybe...
406
+ writeValidStorage.updateValidStatesFromCache(alsoPathValues);
407
+ for (let pathValue of alsoPathValues) {
408
+ // All the values are assumed to have changed
409
+ valuesChanged.add(pathValue);
410
+ if (pathValue.locks.length > 0) {
411
+ complexNewValues.push(pathValue);
412
+ continue;
413
+ }
414
+ let testValidState: WriteState = {
415
+ path: pathValue.path,
416
+ isValid: !!pathValue.valid,
417
+ time: pathValue.time,
418
+ isTransparent: pathValue.isTransparent || pathValue.canGCValue || false,
419
+ };
420
+ let callbacks = lockToCallback.getValidStateChangedTriggers(testValidState);
421
+ if (callbacks.some(x => typeof x !== "string")) {
422
+ complexNewValues.push(pathValue);
423
+ continue;
424
+ }
425
+ // If we get here, it is a simple value, with nothing watching it, and no possibility of
426
+ // being rejected, so just mark it as valid.
427
+ writeValidStorage.setWriteValidState(testValidState);
428
+ }
429
+ if (complexNewValues.length > 0) {
430
+ let newValidStates = writeValidStorage.computeValidStates(complexNewValues, now);
431
+ for (let change of newValidStates) {
432
+ remoteLocksChanged.push(change);
433
+ }
434
+ }
435
+ }
436
+
437
+ {
438
+ let allWrites = new Set<WriteState>();
439
+ let pendingWriteChanges = new Set<WriteState>(remoteLocksChanged);
440
+ while (pendingWriteChanges.size > 0) {
441
+ let curWriteChanges = pendingWriteChanges;
442
+ pendingWriteChanges = new Set();
443
+
444
+ let triggeredSelfChanges = new Set<PathValue[]>();
445
+
446
+ for (let changedWrite of curWriteChanges) {
447
+ // NOTE: Predicted values are ingested, causing them to have a valid state. SO,
448
+ // if they are invalidated, a change will occur here (via the invalid value being forwarded,
449
+ // which is convert to triggering a valid state).
450
+ let changed = true;
451
+
452
+ // If we are recomputing it, don't use the changedWrite value, and assume it has changed,
453
+ // recomputing it always.
454
+ if (!recomputeValidState) {
455
+ changed = writeValidStorage.setWriteValidState(changedWrite);
456
+ }
457
+ if (changed) {
458
+ // NOTE: If the value is invalid, the writer will get both the invalid value, AND a valid state change.
459
+ // This is unintentional, due to the fact that most watchers aren't watching for valid state changes,
460
+ // BUT, the writer might not be watching the path! But, if the writer is watching the path...
461
+ // we send both values, because at this don't know if it is the writer not not.
462
+ // - We can tell via looking to see if it is a valid state watcher, but... that happens in a different
463
+ // function, and... optimizing the reject path shouldn't matter, as rejections should rarely
464
+ // happen anyways...
465
+ let valueChanged = authorityStorage.getValueAtTime(changedWrite.path, changedWrite.time);
466
+ if (valueChanged) {
467
+ valuesChanged.add(valueChanged);
468
+ }
469
+ // Have to send the previous value, as the caller might not have it (as we might start
470
+ // rejecting everything, result in the latest valid state being WAY in the past, to the point
471
+ // where it is impossible for any caller to have the value synced!
472
+ if (!changedWrite.isValid) {
473
+ let prevValue = authorityStorage.getValueBeforeTime(changedWrite.path, changedWrite.time);
474
+ if (prevValue) {
475
+ valuesChanged.add(prevValue);
476
+ }
477
+ }
478
+ }
479
+
480
+ allWrites.add(changedWrite);
481
+
482
+ let callbacks = lockToCallback.getValidStateChangedTriggers(changedWrite);
483
+ for (let valuesWatching of callbacks) {
484
+ if (typeof valuesWatching !== "string") {
485
+ triggeredSelfChanges.add(valuesWatching);
486
+ }
487
+ }
488
+ }
489
+
490
+ // TODO: I'm not sure if we need to sort these before updating them?
491
+ // If we experience rejections and then undoing of rejections... we might need to...
492
+ let flatTriggeredChanges = Array.from(new Set(Array.from(triggeredSelfChanges).flat()));
493
+ let changes = writeValidStorage.computeValidStates(flatTriggeredChanges, now, "alreadyWatching");
494
+ for (let change of changes) {
495
+ pendingWriteChanges.add(change);
496
+ }
497
+ // Anything triggered needs to be recomputed
498
+ recomputeValidState = "recomputeValidState";
499
+ }
500
+
501
+ // Handle valid state watches (mostly other authorities)
502
+ let validChangesPerNode = new Map<string, WriteState[]>();
503
+ for (let write of allWrites) {
504
+ let callbacks = lockToCallback.getValidStateChangedTriggers(write);
505
+ for (let valuesWatching of callbacks) {
506
+ if (typeof valuesWatching !== "string") continue;
507
+ let nodeId = valuesWatching;
508
+ let changes = validChangesPerNode.get(nodeId);
509
+ if (!changes) {
510
+ changes = [];
511
+ validChangesPerNode.set(nodeId, changes);
512
+ }
513
+ changes.push(write);
514
+ }
515
+ }
516
+ // NOTE: Valid watchers are for lock watching of other authorities. The others nodes don't
517
+ // want our values, and cannot properly ingest them (as they wouldn't be able to recursively resolve
518
+ // them, they would be too large, etc, etc).
519
+ for (let [nodeId, validStates] of validChangesPerNode) {
520
+ if (validStates.length > 0) {
521
+ logErrors(PathValueController.nodes[nodeId].onValidChange(validStates));
522
+ }
523
+ }
524
+ }
525
+
526
+ // Path watchers are for clients (ex FunctionRunner, browser, server scripts, etc),
527
+ // which might be on other machines.
528
+ pathWatcher.triggerValuesChanged(valuesChanged, parentsSynced, initialTriggers);
529
+ }
530
+ }
531
+
532
+ let lagWindowSize = 2000;
533
+ let lastLagInfo = {
534
+ value: createStatsValue(),
535
+ startTime: Date.now(),
536
+ };
537
+ let currentLagInfo = {
538
+ value: createStatsValue(),
539
+ startTime: Date.now(),
540
+ };
541
+ export function getReceiveLag() {
542
+ let stats = lastLagInfo.value;
543
+ if (stats.sum === 0) return undefined;
544
+ let top = getStatsTop(stats);
545
+ if (!top.topHeavy) {
546
+ return `${formatTime(stats.sum / stats.count)} TCP LAG`;
547
+ }
548
+ let bottomValue = stats.sum - top.value;
549
+ let bottomCount = stats.count - top.count;
550
+ return `${formatTime(top.value / top.count)} * ${formatNumber(top.count)} + ${formatTime(bottomValue / bottomCount)} * ${formatNumber(bottomCount)} LAG`;
551
+ }
552
+ registerMeasureInfo(getReceiveLag);
553
+ function trackLag(now: number, lag: number) {
554
+ if (now - currentLagInfo.startTime > lagWindowSize) {
555
+ lastLagInfo = currentLagInfo;
556
+ currentLagInfo = {
557
+ value: createStatsValue(),
558
+ startTime: now,
559
+ };
560
+ }
561
+ addToStatsValue(currentLagInfo.value, lag);
562
+ }
563
+
564
+
565
+ export const pathValueCommitter = new PathValueCommitter();