querysub 0.403.0 → 0.405.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 (108) hide show
  1. package/.cursorrules +2 -0
  2. package/bin/audit-imports.js +4 -0
  3. package/bin/join.js +1 -1
  4. package/package.json +7 -4
  5. package/spec.txt +77 -0
  6. package/src/-a-archives/archiveCache.ts +9 -4
  7. package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
  8. package/src/-a-auth/certs.ts +0 -12
  9. package/src/-c-identity/IdentityController.ts +12 -3
  10. package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
  11. package/src/-g-core-values/NodeCapabilities.ts +12 -2
  12. package/src/0-path-value-core/AuthorityLookup.ts +239 -0
  13. package/src/0-path-value-core/LockWatcher2.ts +150 -0
  14. package/src/0-path-value-core/PathRouter.ts +543 -0
  15. package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
  16. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +73 -0
  17. package/src/0-path-value-core/PathValueCommitter.ts +222 -488
  18. package/src/0-path-value-core/PathValueController.ts +277 -239
  19. package/src/0-path-value-core/PathWatcher.ts +534 -0
  20. package/src/0-path-value-core/ShardPrefixes.ts +31 -0
  21. package/src/0-path-value-core/ValidStateComputer.ts +303 -0
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
  23. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
  24. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
  25. package/src/0-path-value-core/auditLogs.ts +2 -0
  26. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
  27. package/src/0-path-value-core/pathValueArchives.ts +491 -492
  28. package/src/0-path-value-core/pathValueCore.ts +195 -1496
  29. package/src/0-path-value-core/startupAuthority.ts +74 -0
  30. package/src/1-path-client/RemoteWatcher.ts +90 -82
  31. package/src/1-path-client/pathValueClientWatcher.ts +808 -815
  32. package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
  33. package/src/2-proxy/archiveMoveHarness.ts +182 -214
  34. package/src/2-proxy/garbageCollection.ts +9 -8
  35. package/src/2-proxy/schema2.ts +21 -1
  36. package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
  37. package/src/3-path-functions/PathFunctionRunner.ts +943 -766
  38. package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
  39. package/src/3-path-functions/pathFunctionLoader.ts +2 -2
  40. package/src/3-path-functions/syncSchema.ts +596 -521
  41. package/src/4-deploy/deployFunctions.ts +19 -4
  42. package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
  43. package/src/4-deploy/deployMain.ts +51 -68
  44. package/src/4-deploy/edgeClientWatcher.tsx +6 -1
  45. package/src/4-deploy/edgeNodes.ts +2 -2
  46. package/src/4-dom/qreact.tsx +2 -4
  47. package/src/4-dom/qreactTest.tsx +7 -13
  48. package/src/4-querysub/Querysub.ts +21 -8
  49. package/src/4-querysub/QuerysubController.ts +45 -29
  50. package/src/4-querysub/permissions.ts +2 -2
  51. package/src/4-querysub/querysubPrediction.ts +80 -70
  52. package/src/4-querysub/schemaHelpers.ts +5 -1
  53. package/src/5-diagnostics/GenericFormat.tsx +14 -9
  54. package/src/archiveapps/archiveGCEntry.tsx +9 -2
  55. package/src/archiveapps/archiveJoinEntry.ts +96 -84
  56. package/src/bits.ts +19 -0
  57. package/src/config.ts +21 -3
  58. package/src/config2.ts +23 -48
  59. package/src/deployManager/components/DeployPage.tsx +7 -3
  60. package/src/deployManager/machineSchema.ts +4 -1
  61. package/src/diagnostics/ActionsHistory.ts +3 -8
  62. package/src/diagnostics/AuditLogPage.tsx +2 -3
  63. package/src/diagnostics/FunctionCallInfo.tsx +141 -0
  64. package/src/diagnostics/FunctionCallInfoState.ts +162 -0
  65. package/src/diagnostics/MachineThreadInfo.tsx +1 -1
  66. package/src/diagnostics/NodeViewer.tsx +37 -48
  67. package/src/diagnostics/SyncTestPage.tsx +241 -0
  68. package/src/diagnostics/auditImportViolations.ts +185 -0
  69. package/src/diagnostics/listenOnDebugger.ts +3 -3
  70. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
  71. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
  72. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
  73. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
  74. package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
  75. package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
  76. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +34 -16
  77. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
  78. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
  79. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
  80. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
  81. package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
  82. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
  83. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
  84. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
  85. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
  86. package/src/diagnostics/managementPages.tsx +10 -3
  87. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
  88. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
  89. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
  90. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
  91. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
  92. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
  93. package/src/diagnostics/pathAuditer.ts +486 -0
  94. package/src/diagnostics/pathAuditerCallback.ts +20 -0
  95. package/src/diagnostics/watchdog.ts +8 -1
  96. package/src/library-components/URLParam.ts +1 -1
  97. package/src/misc/hash.ts +1 -0
  98. package/src/path.ts +21 -7
  99. package/src/server.ts +54 -47
  100. package/src/user-implementation/loginEmail.tsx +1 -1
  101. package/tempnotes.txt +65 -0
  102. package/test.ts +298 -97
  103. package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
  104. package/src/0-path-value-core/PathController.ts +0 -1
  105. package/src/5-diagnostics/diskValueAudit.ts +0 -218
  106. package/src/5-diagnostics/memoryValueAudit.ts +0 -438
  107. package/src/archiveapps/archiveMergeEntry.tsx +0 -48
  108. package/src/archiveapps/lockTest.ts +0 -127
@@ -0,0 +1,486 @@
1
+ import { binarySearchBasic2, timeInMinute, timeInSecond } from "socket-function/src/misc";
2
+ import { PathRouter } from "../0-path-value-core/PathRouter";
3
+ import { authorityStorage, MAX_CHANGE_AGE, PathValue, compareTime, Time, createMissingEpochValue, epochTime } from "../0-path-value-core/pathValueCore";
4
+ import { formatNumber, formatPercent, formatTime } from "socket-function/src/formatting/format";
5
+ import { timeoutToError } from "../errors";
6
+ import { setPathInteractedCallback } from "./pathAuditerCallback";
7
+ import { registerResource } from "./trackResources";
8
+ import { registerMeasureInfo } from "socket-function/src/profiling/measure";
9
+ import { lazy } from "socket-function/src/caching";
10
+ import { runInfinitePoll, runInSerial } from "socket-function/src/batching";
11
+ import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
12
+ import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
13
+ import { validStateComputer } from "../0-path-value-core/ValidStateComputer";
14
+ import { SocketFunction } from "socket-function/SocketFunction";
15
+ import { decodeNodeId } from "../-a-auth/certs";
16
+ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
17
+ import { assertIsManagementUser } from "./managementPages";
18
+ import { isNode } from "typesafecss";
19
+ import { isClient } from "../config2";
20
+ import { isLocal } from "../config";
21
+ import { pathWatcher } from "../0-path-value-core/PathWatcher";
22
+
23
+ if (!isClient()) {
24
+ // Comment this line out to disable our functionality
25
+ // NOTE: I think this should stay on even in production because it should be able to fix lots of issues.
26
+ setPathInteractedCallback(onPathInteracted);
27
+ }
28
+
29
+ const CHECK_INTERVAL = timeInMinute * 3;
30
+ const AUDIT_PATHS_PER_CHECK = 1000;
31
+ const DROP_AGE_THRESHOLD = CHECK_INTERVAL * 5;
32
+
33
+ const ERROR_AGE_THRESHOLD = timeInMinute;
34
+ const WARN_THRESHOLD = timeInSecond * 5;
35
+ const AUDIT_TIMEOUT = timeInMinute * 2;
36
+
37
+ type PathInteractionPriority = 0 | 1 | 2; // 0: read, 1: write, 2: rejected (higher is more important)
38
+
39
+ interface PathInteraction {
40
+ path: string;
41
+ time: number;
42
+ }
43
+
44
+ interface AuditStats {
45
+ totalAudited: number;
46
+ totalDiscarded: number;
47
+ errorCount: number;
48
+ warningCount: number;
49
+ fixedCount: number;
50
+ }
51
+
52
+ interface PendingValidityCheck {
53
+ path: string;
54
+ ourTimeId: number;
55
+ remoteTimeId: number | undefined;
56
+ ourValid: boolean;
57
+ remoteValid: boolean | undefined;
58
+ remoteNodeId: string;
59
+ discoveredAt: number;
60
+ }
61
+
62
+ class CyclicList<T> {
63
+ private items: (T | undefined)[];
64
+ private startIndex: number = 0;
65
+ private count: number = 0;
66
+ private capacity: number;
67
+
68
+ constructor(capacity: number) {
69
+ this.capacity = capacity;
70
+ this.items = new Array(capacity);
71
+ }
72
+
73
+ public push(item: T): void {
74
+ let writeIndex = (this.startIndex + this.count) % this.capacity;
75
+ this.items[writeIndex] = item;
76
+ if (this.count < this.capacity) {
77
+ this.count++;
78
+ } else {
79
+ this.startIndex = (this.startIndex + 1) % this.capacity;
80
+ }
81
+ }
82
+
83
+ public length(): number {
84
+ return this.count;
85
+ }
86
+
87
+ public at(index: number): T {
88
+ if (index >= this.count) throw new Error(`Index ${index} is out of bounds for list of length ${this.count}`);
89
+ let actualIndex = (this.startIndex + index) % this.capacity;
90
+ let item = this.items[actualIndex];
91
+ if (item === undefined) throw new Error(`Item at index ${index} is undefined`);
92
+ return item;
93
+ }
94
+
95
+ public shift(count: number): void {
96
+ let toRemove = Math.min(count, this.count);
97
+ this.startIndex = (this.startIndex + toRemove) % this.capacity;
98
+ this.count -= toRemove;
99
+ }
100
+ }
101
+
102
+ const CYCLIC_LIST_CAPACITY = 100000;
103
+
104
+ const pathLists: CyclicList<PathInteraction>[] = [
105
+ new CyclicList<PathInteraction>(CYCLIC_LIST_CAPACITY), // priority 0: read
106
+ new CyclicList<PathInteraction>(CYCLIC_LIST_CAPACITY), // priority 1: write
107
+ new CyclicList<PathInteraction>(CYCLIC_LIST_CAPACITY), // priority 2: rejected
108
+ ];
109
+
110
+ let lastAuditTime: number = 0;
111
+ let stats: AuditStats = {
112
+ totalAudited: 0,
113
+ totalDiscarded: 0,
114
+ errorCount: 0,
115
+ warningCount: 0,
116
+ fixedCount: 0,
117
+ };
118
+
119
+ const pendingValidityChecks: PendingValidityCheck[] = [];
120
+ const pendingValidityCheckPaths = new Set<string>();
121
+
122
+ function onPathInteracted(path: string, priority: PathInteractionPriority): void {
123
+ if (PathRouter.isLocalPath(path)) return;
124
+ ensureAuditing();
125
+ pathLists[priority].push({ path, time: Date.now() });
126
+ }
127
+
128
+ const ensureAuditing = lazy(() => {
129
+ runInfinitePoll(CHECK_INTERVAL, runAuditNowSerial);
130
+ });
131
+
132
+ const runAuditNowSerial = runInSerial(runAuditNow);
133
+
134
+ async function runAuditNow() {
135
+ await PathRouter.waitUntilReady();
136
+ let now = Date.now();
137
+
138
+ let pathsToAudit = new Set<string>();
139
+ let perList = Math.floor(AUDIT_PATHS_PER_CHECK / pathLists.length);
140
+
141
+ for (let priority = 0; priority < pathLists.length; priority++) {
142
+ let list = pathLists[priority];
143
+ let len = list.length();
144
+
145
+ let splitIndex = 0;
146
+ for (let i = 0; i < len; i++) {
147
+ let item = list.at(i);
148
+ if (item && item.time > lastAuditTime) {
149
+ splitIndex = i;
150
+ break;
151
+ }
152
+ }
153
+
154
+ let takeAfter = Math.min(Math.floor(perList / 2), len - splitIndex);
155
+ let takeBefore = Math.min(perList - takeAfter, splitIndex);
156
+
157
+ for (let i = 0; i < takeAfter; i++) {
158
+ pathsToAudit.add(list.at(splitIndex + i).path);
159
+ }
160
+
161
+ for (let i = 0; i < takeBefore; i++) {
162
+ pathsToAudit.add(list.at(i).path);
163
+ }
164
+
165
+ let discarded = 0;
166
+ for (let i = 0; i < len; i++) {
167
+ let item = list.at(i);
168
+ if (now - item.time > DROP_AGE_THRESHOLD) {
169
+ discarded++;
170
+ } else {
171
+ break;
172
+ }
173
+ }
174
+ list.shift(discarded);
175
+ stats.totalDiscarded += discarded;
176
+ }
177
+
178
+ lastAuditTime = now;
179
+ let paths = Array.from(pathsToAudit);
180
+ // If we stopped watching it, or were never watching it in the first place, we don't want to audit it. Or if we haven't finished watching it anyways.
181
+ paths = paths.filter(path => pathWatcher.isWatching(path) && authorityStorage.isSynced(path) || PathRouter.isSelfAuthority(path));
182
+ if (paths.length === 0) return;
183
+
184
+ let pathObjs: { path: string }[] = paths.map(path => ({ path }));
185
+ let valuesByAuthority = PathRouter.getAllAuthoritiesForValues(pathObjs);
186
+
187
+ await Promise.all(
188
+ Array.from(valuesByAuthority.entries()).map(([nodeId, pathsToTest]) =>
189
+ timeoutToError(
190
+ AUDIT_TIMEOUT,
191
+ auditAuthority(nodeId, pathsToTest, now),
192
+ () => new Error(`Path audit timed out for node ${nodeId}`)
193
+ )
194
+ )
195
+ );
196
+
197
+ await processPendingValidityChecks(now);
198
+ }
199
+
200
+ async function processPendingValidityChecks(now: number) {
201
+ let threshold = now - MAX_CHANGE_AGE;
202
+ let splitIndex = binarySearchBasic2(pendingValidityChecks, check => check.discoveredAt, { discoveredAt: threshold } as PendingValidityCheck);
203
+ if (splitIndex < 0) splitIndex = ~splitIndex;
204
+
205
+ for (let i = 0; i < splitIndex; i++) {
206
+ let pendingCheck = pendingValidityChecks[i];
207
+ let currentValue = authorityStorage.getValueAtTime(pendingCheck.path);
208
+ if (currentValue && currentValue.valid && currentValue.time !== epochTime) {
209
+ trackSyncAge({
210
+ path: pendingCheck.path,
211
+ ourTimeId: pendingCheck.ourTimeId,
212
+ remoteTimeId: pendingCheck.remoteTimeId,
213
+ ourValid: pendingCheck.ourValid,
214
+ remoteValid: pendingCheck.remoteValid,
215
+ remoteNodeId: pendingCheck.remoteNodeId,
216
+ reason: "Pending validity check aged past MAX_CHANGE_AGE. Forcing sync to all nodes.",
217
+ });
218
+ let allAuthorities = PathRouter.getAllAuthoritiesForValues([currentValue]);
219
+ for (let [authorityNodeId, _] of allAuthorities.entries()) {
220
+ let serialized = await pathValueSerializer.serialize([currentValue]);
221
+ await PathAuditerController.nodes[authorityNodeId].ingestPathValues(serialized);
222
+ }
223
+ }
224
+ }
225
+
226
+ if (splitIndex > 0) {
227
+ for (let i = 0; i < splitIndex; i++) {
228
+ pendingValidityCheckPaths.delete(pendingValidityChecks[i].path);
229
+ }
230
+ pendingValidityChecks.splice(0, splitIndex);
231
+ }
232
+ }
233
+
234
+ function trackSyncAge(config: {
235
+ path: string;
236
+ ourTimeId: number;
237
+ remoteTimeId: number | undefined;
238
+ ourValid: boolean;
239
+ remoteValid: boolean | undefined;
240
+ remoteNodeId: string;
241
+ reason: string;
242
+ }) {
243
+ let age = Date.now() - config.ourTimeId;
244
+ if (age >= ERROR_AGE_THRESHOLD) {
245
+ stats.errorCount++;
246
+ console.error(config.reason, {
247
+ path: config.path,
248
+ age,
249
+ ourTimeId: config.ourTimeId,
250
+ remoteTimeId: config.remoteTimeId,
251
+ ourValid: config.ourValid,
252
+ remoteValid: config.remoteValid,
253
+ remoteNodeId: config.remoteNodeId,
254
+ remoteNodeThreadId: decodeNodeId(config.remoteNodeId)?.threadId,
255
+ });
256
+ } else if (age >= WARN_THRESHOLD) {
257
+ stats.warningCount++;
258
+ console.warn(config.reason, {
259
+ path: config.path,
260
+ age,
261
+ ourTimeId: config.ourTimeId,
262
+ remoteTimeId: config.remoteTimeId,
263
+ ourValid: config.ourValid,
264
+ remoteValid: config.remoteValid,
265
+ remoteNodeId: config.remoteNodeId,
266
+ remoteNodeThreadId: decodeNodeId(config.remoteNodeId)?.threadId,
267
+ });
268
+ } else {
269
+ stats.fixedCount++;
270
+ }
271
+ }
272
+
273
+ async function auditAuthority(nodeId: string, pathsToAudit: { path: string }[], now: number) {
274
+ let requests: PathTimeRequest[] = [];
275
+
276
+ for (let pathObj of pathsToAudit) {
277
+ let path = pathObj.path;
278
+ let ourLatest = authorityStorage.getValueAtTime(path);
279
+
280
+ // Ask for their latest valid (time: undefined means latest)
281
+ requests.push({ path, time: undefined });
282
+
283
+ if (ourLatest?.time) {
284
+ // Ask for their state at our specific time
285
+ requests.push({ path, time: ourLatest.time });
286
+ }
287
+
288
+ let history = authorityStorage.getValuePlusHistory(path);
289
+ if (history.length > 1) {
290
+ let randomIndex = Math.floor(Math.random() * (history.length - 1)) + 1;
291
+ let randomValue = history[randomIndex];
292
+ requests.push({ path, time: randomValue.time });
293
+ }
294
+ }
295
+
296
+ let responses = await PathAuditerController.nodes[nodeId].getValidStates(requests);
297
+
298
+ let valuesToRequest: PathTimeRequest[] = [];
299
+ let valuesToSend: PathValue[] = [];
300
+ let pathsToForceSync = new Set<string>();
301
+
302
+ for (let response of responses) {
303
+ let ourValue = authorityStorage.getValueAtTime(response.path) || createMissingEpochValue(response.path);
304
+ if (ourValue.isTransparent && response.isTransparent) continue;
305
+
306
+ // it's latest valid is newer than ours
307
+ // - Ask it for the value of the latest
308
+ if (response.valid && response.time && compareTime(response.time, ourValue.time) > 0) {
309
+ valuesToRequest.push({ path: response.path, time: response.time });
310
+ let authorities = PathRouter.getAllAuthorities(response.path);
311
+ require("debugbreak")(2);
312
+ debugger;
313
+ trackSyncAge({
314
+ path: response.path,
315
+ ourTimeId: ourValue.time.time,
316
+ remoteTimeId: response.time.time,
317
+ ourValid: ourValue.valid ?? false,
318
+ remoteValid: response.valid,
319
+ remoteNodeId: nodeId,
320
+ reason: "Remote has newer latest value, asking for it",
321
+ });
322
+ }
323
+ // Our latest valid = server does not have & it's latest valid is older than ours
324
+ // - Send it our value
325
+ else if (response.valid === undefined && (!response.time || compareTime(ourValue.time, response.time) > 0)) {
326
+ valuesToSend.push(ourValue);
327
+ require("debugbreak")(2);
328
+ debugger;
329
+ trackSyncAge({
330
+ path: response.path,
331
+ ourTimeId: ourValue.time.time,
332
+ remoteTimeId: response.time?.time,
333
+ ourValid: ourValue.valid ?? false,
334
+ remoteValid: response.valid,
335
+ remoteNodeId: nodeId,
336
+ reason: "Remote is missing our value, sending it to them",
337
+ });
338
+ }
339
+ // Our latest valid = server has, but says it's not valid
340
+ // - If time < MAX_CHANGE_AGE old, record this, and wait until MAX_CHANGE_AGE (put it in some other kind of queue), and then check again
341
+ // - If time >= MAX_CHANGE_AGE old, then tell it to change it's valid state to ours. Also, tell all other authorities that use this path.
342
+ // NOTE: We skew towards making values valid instead of invalid. So if there's disagreement, we always take the valid value instead of the invalid value.
343
+ // NOTE: The opposite case, if the remote is valid and ours is invalid, is handled by our latest value being older than the remote value, as we're never going to ask about an invalid value. It'll just implicitly change what the latest valid value is.
344
+ else if (!response.valid && ourValue.valid && ourValue.time !== epochTime) {
345
+ let age = now - ourValue.time.time;
346
+ if (age >= MAX_CHANGE_AGE) {
347
+ pathsToForceSync.add(response.path);
348
+ require("debugbreak")(2);
349
+ debugger;
350
+ trackSyncAge({
351
+ path: response.path,
352
+ ourTimeId: ourValue.time.time,
353
+ remoteTimeId: response.time?.time,
354
+ ourValid: ourValue.valid,
355
+ remoteValid: response.valid,
356
+ remoteNodeId: nodeId,
357
+ reason: "Remote says our value is invalid, but we think it's valid. Telling all nodes about this value to ensure it's in sync everywhere.",
358
+ });
359
+ } else {
360
+ if (!pendingValidityCheckPaths.has(response.path)) {
361
+ let newCheck: PendingValidityCheck = {
362
+ path: response.path,
363
+ ourTimeId: ourValue.time.time,
364
+ remoteTimeId: response.time?.time,
365
+ ourValid: ourValue.valid ?? false,
366
+ remoteValid: response.valid,
367
+ remoteNodeId: nodeId,
368
+ discoveredAt: now,
369
+ };
370
+ let insertIndex = binarySearchBasic2(pendingValidityChecks, check => check.discoveredAt, newCheck);
371
+ if (insertIndex < 0) insertIndex = ~insertIndex;
372
+ pendingValidityChecks.splice(insertIndex, 0, newCheck);
373
+ pendingValidityCheckPaths.add(response.path);
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ if (valuesToRequest.length > 0) {
380
+ let receivedValues = await PathAuditerController.nodes[nodeId].getPathValues(valuesToRequest);
381
+ if (receivedValues.length > 0) {
382
+ validStateComputer.ingestValuesAndValidStates({
383
+ pathValues: receivedValues,
384
+ parentSyncs: [],
385
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
386
+ });
387
+ }
388
+ }
389
+
390
+ if (valuesToSend.length > 0) {
391
+ let serialized = await pathValueSerializer.serialize(valuesToSend);
392
+ await PathAuditerController.nodes[nodeId].ingestPathValues(serialized);
393
+ }
394
+
395
+ if (pathsToForceSync.size > 0) {
396
+ for (let path of pathsToForceSync) {
397
+ let valueToForce = authorityStorage.getValueAtTime(path);
398
+ if (valueToForce) {
399
+ let allAuthorities = PathRouter.getAllAuthoritiesForValues([valueToForce]);
400
+ for (let [authorityNodeId, _] of allAuthorities.entries()) {
401
+ let serialized = await pathValueSerializer.serialize([valueToForce]);
402
+ await PathAuditerController.nodes[authorityNodeId].ingestPathValues(serialized);
403
+ }
404
+ }
405
+ }
406
+ }
407
+
408
+ stats.totalAudited += pathsToAudit.length;
409
+ }
410
+
411
+ type PathTimeRequest = {
412
+ path: string;
413
+ time?: { time: number; version: number; creatorId: number };
414
+ };
415
+
416
+ type ValidStateResponse = {
417
+ path: string;
418
+ time?: { time: number; version: number; creatorId: number };
419
+ valid: boolean | undefined;
420
+ isTransparent: boolean;
421
+ };
422
+
423
+ class PathAuditerService {
424
+ public async getValidStates(requests: PathTimeRequest[]): Promise<ValidStateResponse[]> {
425
+ let results: ValidStateResponse[] = [];
426
+ for (let request of requests) {
427
+ let value = authorityStorage.getValueAtTime(request.path, request.time);
428
+ results.push({
429
+ path: request.path,
430
+ time: value?.time,
431
+ valid: value === undefined ? undefined : value.valid,
432
+ isTransparent: !!value?.isTransparent,
433
+ });
434
+ }
435
+ return results;
436
+ }
437
+
438
+ public async getPathValues(requests: PathTimeRequest[]): Promise<PathValue[]> {
439
+ let results: PathValue[] = [];
440
+ for (let request of requests) {
441
+ let value = authorityStorage.getValueAtTime(request.path, request.time);
442
+ if (value) {
443
+ results.push(value);
444
+ }
445
+ }
446
+ return results;
447
+ }
448
+
449
+ public async ingestPathValues(serializedValues: Buffer[]): Promise<void> {
450
+ let values = await pathValueSerializer.deserialize(serializedValues);
451
+ validStateComputer.ingestValuesAndValidStates({
452
+ pathValues: values,
453
+ parentSyncs: [],
454
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
455
+ });
456
+ }
457
+
458
+ public async runAuditNow_forBrowser(nodeId: string): Promise<void> {
459
+ await PathAuditerController.nodes[nodeId].runAuditNow();
460
+ }
461
+
462
+ public async runAuditNow(): Promise<void> {
463
+ await runAuditNowSerial();
464
+ }
465
+ }
466
+
467
+ export const PathAuditerController = SocketFunction.register(
468
+ "PathAuditerService-019d324f-9058-71ab-b1d0-384f9418d35b",
469
+ new PathAuditerService(),
470
+ () => ({
471
+ getValidStates: {},
472
+ getPathValues: {},
473
+ ingestPathValues: {},
474
+ runAuditNow_forBrowser: {},
475
+ runAuditNow: {},
476
+ }),
477
+ () => ({
478
+ hooks: [assertIsManagementUser],
479
+ })
480
+ );
481
+
482
+ registerMeasureInfo(() => {
483
+ if (stats.totalDiscarded === 0) return "";
484
+ let percentage = formatPercent(stats.totalAudited / (stats.totalAudited + stats.totalDiscarded) * 100);
485
+ return `AUDIT ${formatNumber(stats.totalAudited)} (${percentage}) E:${stats.errorCount} W:${stats.warningCount} F:${stats.fixedCount} `;
486
+ });
@@ -0,0 +1,20 @@
1
+ let callback: ((path: string, priority: 0 | 1 | 2) => void) | undefined;
2
+
3
+ let disabled = false;
4
+ /*
5
+ 0: read
6
+ 1: write
7
+ 2: rejected
8
+ (higher is more important)
9
+ */
10
+ export function onPathInteracted(path: string, priority: 0 | 1 | 2) {
11
+ if (disabled) return;
12
+ callback?.(path, priority);
13
+ }
14
+ export function setPathInteractedCallback(_callback: ((path: string, priority: 0 | 1 | 2) => void) | undefined) {
15
+ callback = _callback;
16
+ }
17
+
18
+ export function disablePathAuditer() {
19
+ disabled = true;
20
+ }
@@ -7,9 +7,11 @@ import { registerPeriodic } from "./periodic";
7
7
  import { getOwnMachineId } from "../-a-auth/certs";
8
8
  import { SocketFunction } from "socket-function/SocketFunction";
9
9
  import { formatPercent, formatTime } from "socket-function/src/formatting/format";
10
- import { pathWatcher } from "../0-path-value-core/pathValueCore";
10
+ import { pathWatcher } from "../0-path-value-core/PathWatcher";
11
11
  import { addStatPeriodic, addStatSumPeriodic, addTimeProfileDistribution, registerNodeMetadata } from "../-0-hooks/hooks";
12
12
  import { logDisk } from "./logs/diskLogger";
13
+ import { getDebuggerUrl } from "./listenOnDebugger";
14
+ import { isPublic } from "../config";
13
15
 
14
16
  let lastProfile: MeasureProfile | undefined;
15
17
 
@@ -87,6 +89,11 @@ function logProfileMeasuresTimingsNow() {
87
89
  minTimeToLog: minTimeToLog,
88
90
  }));
89
91
 
92
+ if (isNode() && !isPublic()) {
93
+ let debuggerValue = getDebuggerUrl();
94
+ process.stdout.write(`\n${debuggerValue}\n`);
95
+ }
96
+
90
97
  lastProfile = profile;
91
98
  // if (isNode()) {
92
99
  // if (SocketFunction.mountedNodeId) {
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { deepCloneJSON, throttleFunction, timeInMinute } from "socket-function/src/misc";
4
4
  import { isNode } from "typesafecss";
5
- import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
6
5
  import { atomicObjectRead, doAtomicWrites, noAtomicSchema, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
7
6
  import { syncSchema } from "../3-path-functions/syncSchema";
8
7
  import { Querysub } from "../4-querysub/QuerysubController";
@@ -12,6 +11,7 @@ import { logErrors } from "../errors";
12
11
  import { canHaveChildren } from "socket-function/src/types";
13
12
  import { URLOverride } from "./ATag";
14
13
  import { lazy } from "socket-function/src/caching";
14
+ import { LOCAL_DOMAIN } from "../0-path-value-core/PathRouter";
15
15
 
16
16
  export interface URLParam<T = unknown> {
17
17
  value: T;
package/src/misc/hash.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import debugbreak from "debugbreak";
2
2
 
3
+ /** Returns an integer */
3
4
  export function fastHash(text: string): number {
4
5
  // Murmur, but we try to use more than 32 bits, as a double can store far more than 32 bits.
5
6
  let hash1 = 5380942 ^ text.length;
package/src/path.ts CHANGED
@@ -85,6 +85,10 @@ export function getPathStr4(path0: string, path1: string, path2: string, path3:
85
85
  }
86
86
 
87
87
  export function appendToPathStr(pathStr: string, pathPart: string) {
88
+ if (pathStr[pathStr.length - 1] !== pathDelimitChar) {
89
+ // Might be a hacked path
90
+ pathStr = hack_stripPackedPath(pathStr);
91
+ }
88
92
  return pathStr + escapePathPart(pathPart) + pathDelimitEscaped;
89
93
  }
90
94
  export function prependToPathStr(pathStr: string, pathPart: string) {
@@ -143,28 +147,38 @@ export function getPathAfterLastChild(pathStr: string) {
143
147
  return pathStr.slice(0, -pathDelimitEscaped.length) + afterChildSuffix;
144
148
  }
145
149
 
150
+ function getStartOfLastPart(pathStr: string) {
151
+ // I'm not sure if this is what we should be doing for hacked paths. In theory, a hacked path basically represents select these children, selecting specific children. So, in theory, that should mean... The parent is the parent of those children. However, we also use it to keep track of the parent sync, in which case we are syncing the parent. And so the parent we want, if we ever recall this, would be actually the parent of that. So I think in theory we should uncomment this code, but in practice we need to keep it commented.
152
+ // if(hack_isPackedPath(pathStr)) {
153
+ // let index = pathStr.lastIndexOf(pathDelimitEscaped);
154
+ // if(index < 0) return pathStr.length;
155
+ // return index + pathDelimitEscaped.length;
156
+ // }
157
+ let index = pathStr.lastIndexOf(pathDelimitEscaped);
158
+ if (index < 0) return pathStr.length;
159
+ let index2 = pathStr.lastIndexOf(pathDelimitEscaped, index - 1);
160
+ if (index2 < 0) return 0;
161
+ return index2 + pathDelimitEscaped.length;
162
+ }
163
+
146
164
  /** === getPathStr(getPathFromStr(pathStr).slice(0, -1)), getParentPathStr(rootPathStr) === rootPathStr */
147
165
  export function getParentPathStr(pathStr: string) {
148
166
  // Allow empty string? Better than throwing on it, I guess
149
167
  if (pathStr === "") {
150
168
  return "";
151
169
  }
152
- // NOTE: If pathStr.length is too small, our index start becomes negative, and so this works without
153
170
  // an additional check.
154
- let index = pathStr.lastIndexOf(pathDelimitEscaped, pathStr.length - pathDelimitEscaped.length - pathDelimitEscaped.length + 1);
155
- return pathStr.slice(0, index + pathDelimitEscaped.length);
171
+ return pathStr.slice(0, getStartOfLastPart(pathStr));
156
172
  }
157
173
 
158
174
  /** === getPathFromStr(pathStr).slice(-1)[0] */
159
175
  export function getLastPathPart(pathStr: string) {
160
- let index = pathStr.lastIndexOf(pathDelimitEscaped, pathStr.length - pathDelimitEscaped.length - pathDelimitEscaped.length + 1);
161
- return unescapePathPart(pathStr.slice(index + pathDelimitEscaped.length, -pathDelimitEscaped.length));
176
+ return unescapePathPart(pathStr.slice(getStartOfLastPart(pathStr) - pathDelimitEscaped.length, -pathDelimitEscaped.length));
162
177
  }
163
178
 
164
179
  /** === getPathStr(getPathFromStr(pathStr).slice(0, -1)) */
165
180
  export function removePathLastPart(pathStr: string) {
166
- let index = pathStr.lastIndexOf(pathDelimitEscaped, pathStr.length - pathDelimitEscaped.length - pathDelimitEscaped.length + 1);
167
- return pathStr.slice(0, index + pathDelimitEscaped.length);
181
+ return pathStr.slice(0, getStartOfLastPart(pathStr));
168
182
  }
169
183
 
170
184
  /** getPathDepth(pathStr) === getPathFromStr(pathStr).length */