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
@@ -1,239 +1,277 @@
1
- import { SocketFunction } from "socket-function/SocketFunction";
2
- import { errorToUndefined, logErrors } from "../errors";
3
- import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
4
- import { authorityStorage, ReadLock, pathWatcher, PathValue, PathValueSnapshot, WriteState, writeValidStorage, lockToCallback, isCoreQuiet, WatchConfig, MAX_ACCEPTED_CHANGE_AGE, MAX_CHANGE_AGE, debugPathValuePath } from "./pathValueCore";
5
- import { measureCodeSync, measureFnc } from "socket-function/src/profiling/measure";
6
- import { ActionsHistory } from "../diagnostics/ActionsHistory";
7
- import { isNode } from "socket-function/src/misc";
8
- import { magenta, red } from "socket-function/src/formatting/logColors";
9
- import { AuthorityPath, pathValueAuthority2 } from "./NodePathAuthorities";
10
- import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
11
- import { pathValueCommitter } from "./PathValueCommitter";
12
- import { Benchmark } from "../diagnostics/benchmark";
13
- import { formatNumber, formatTime } from "socket-function/src/formatting/format";
14
- import { sha256 } from "js-sha256";
15
- import debugbreak from "debugbreak";
16
- import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
17
- import { auditLog, isDebugLogEnabled } from "./auditLogs";
18
- import { debugNodeId, debugNodeThread } from "../-c-identity/IdentityController";
19
- import { logDisk } from "../diagnostics/logs/diskLogger";
20
- import { isDiskAudit } from "../config";
21
- import { decodeNodeId } from "../-a-auth/certs";
22
- export { pathValueCommitter };
23
-
24
- class PathValueControllerBase {
25
- /**
26
- Use for writers to send writes to authorities, and for authorities to send writes to watchers.
27
- - Values we are an authority on will have their valid states computed (recursively), otherwise
28
- we just trust the remote valid states.
29
-
30
- Triggers all watcher of the values, as well as all watches of any changed valid states.
31
-
32
- NOTE: We don't check for max age in forwarded values, and instead just insert them, as
33
- we trust our other authorities to not forward us bad values (and rejecting it would
34
- cause inconsistencies between nodes, which is worse).
35
- */
36
- public async forwardWrites(
37
- valuesBuffers: Buffer[],
38
- parentsSynced?: string[],
39
- watchLocks?: "watchLocks",
40
- initialTrigger?: "initialTrigger"
41
- ) {
42
- let machineId = SocketFunction.getCaller().nodeId;
43
- let callerId = SocketFunction.getCaller().nodeId;
44
-
45
- const values = await errorToUndefined(pathValueSerializer.deserialize(valuesBuffers) as Promise<PathValue[]>);
46
- if (!values) {
47
- return;
48
- }
49
-
50
- Benchmark.onReceivedValues(values);
51
- ActionsHistory.OnRead(values);
52
-
53
- // NOTE: We don't check for age here, because this only matters for new writes, and this isn't the correct place to check for this.
54
- // // IMPORTANT!
55
- // // We DO NOT reject old values here. Only trusted servers can call this function, and... writes are
56
- // // sent to multiple servers, so if we reject a write here it has a high chance of resulting in
57
- // // different writes between servers. Because writes are usually accepts, and receiving an old
58
- // // write is likely due to an issue on our end (our internet lagging, our server lagging, etc),
59
- // // the write will likely be accepted (as most writes are accepted), so defaulting to accept
60
- // // it will likely result in a good state.
61
- // let threshold = Date.now() - MAX_CHANGE_AGE;
62
- // for (let value of values) {
63
- // let pastThreshold = threshold - value.time.time;
64
- // if (pastThreshold > 0) {
65
- // console.error(red(`Received a value that is ${formatTime(pastThreshold)} pass change time, which will cause it to be forcefully accepted (locks will be ignored): ${value.path}`));
66
- // }
67
- // }
68
-
69
- // Set up VALID watches for the caller. Because we are only adding the locks, this won't trigger
70
- // a valid state send unless the initial valid state is rejected.
71
- // IMPORTANT! This needs to be done here, and cannot be done in another call, as we NEED the valid
72
- // states for created values to be watched immediately, otherwise the creator (such as the FunctionRunner),
73
- // might miss the rejection (and not rerun the rejected function).
74
- if (watchLocks) {
75
- for (let value of values) {
76
- lockToCallback.watchLock({
77
- path: value.path,
78
- startTime: value.time,
79
- endTime: value.time,
80
- readIsTransparent: value.canGCValue || false,
81
- }, callerId);
82
- }
83
- }
84
-
85
- if (isDebugLogEnabled() || isDiskAudit()) {
86
- let sourceNodeId = debugNodeId(callerId);
87
- for (let value of values) {
88
- auditLog("RECEIVE VALUE", {
89
- path: value.path,
90
- time: value.time.time,
91
- sourceNodeId,
92
- sourceNodeThreadId: decodeNodeId(sourceNodeId)?.threadId,
93
- });
94
- }
95
- }
96
- console.info("Received PathValues via forwardWrites", { valueCount: values.length, callerId, });
97
-
98
- if (isCoreQuiet) {
99
- await pathValueCommitter.ingestRemoteValues({
100
- pathValues: values,
101
- parentsSynced,
102
- sourceNodeId: callerId,
103
- initialTrigger,
104
- });
105
- } else {
106
- let sumAge = 0;
107
- let now = Date.now();
108
- for (let value of values) {
109
- sumAge += now - value.time.time;
110
- }
111
- console.log(`(${now}) Received writes: ${values.length}, ${formatTime(sumAge / values.length)} AGE, parents: ${parentsSynced?.length} from ${machineId}`);
112
- await measureCodeSync(function forwardWritesMeasureOverhead() {
113
- return pathValueCommitter.ingestRemoteValues({
114
- pathValues: values,
115
- parentsSynced,
116
- sourceNodeId: callerId,
117
- initialTrigger,
118
- });
119
- });
120
- }
121
- }
122
-
123
- /** Watches that the startTime is valid, and that there are no valid values between startTime/endTime
124
- * - Puts the onus of syncing the lock, and it's readLocks, recursively, on the callee.
125
- * Of course, the callee has to be an authority for this it work, and authorities are
126
- * automatically doing this. Due to authorities being sharded between of large swathes of data,
127
- * as well as data (hopefully) being relatively localized to our shard lines, it is expected
128
- * there will be relatively few watchLockValid calls. Also, it is expected that relatively
129
- * few readLocks will become invalid (rejected).
130
- * - Notifies of invalid states by calling PathValidWatcher.onValidChange (which the client watches
131
- * statically using createValidStateWatcher).
132
- * - All the lock paths are assumed to be ours (we are the authority for them)
133
- * - Triggers invalid range readLocks by passing the valid state of the value that conflicted.
134
- * As any watchers will be watching this range locally, this will trigger them to ingest the valid
135
- * state, and trigger the value that uses it, recalculate the value that uses it (as they are the
136
- * authority on that), and then realize it is invalid and reject it.
137
- */
138
- public async watchLockValid(locks: ReadLock[]) {
139
- let callerId = SocketFunction.getCaller().nodeId;
140
- for (let lock of locks) {
141
- lockToCallback.watchLock(lock, callerId);
142
- }
143
- let now = Date.now();
144
- let validStates: WriteState[] = locks.map(lock => writeValidStorage.getWriteState(lock, now));
145
- logErrors(PathValueController.nodes[callerId].onValidChange(validStates));
146
- }
147
-
148
- // TODO: Batch these calls before processing them
149
- public async onValidChange(config: WriteState[]) {
150
- if (!isCoreQuiet || !isNode() || ClientWatcher.DEBUG_READS) {
151
- let rejected = config.filter(x => !x.isValid);
152
- if (rejected.length > 0) {
153
- console.group(red(`Received rejection of paths`));
154
- for (let value of rejected) {
155
- console.log(debugPathValuePath(value));
156
- }
157
- console.groupEnd();
158
- }
159
- }
160
-
161
- // Ignore any remote changes for values that WE are an authority on!
162
- config = config.filter(x => !pathValueAuthority2.isSelfAuthority(x.path));
163
- pathValueCommitter.ingestValidStates(config);
164
- }
165
-
166
- /** Returns serialized PathValue[] */
167
- public async getSnapshot(config: { authorityPath: AuthorityPath; }): Promise<Buffer[]> {
168
- let snapshot = await authorityStorage.getSnapshot(config.authorityPath);
169
- let values = Object.values(snapshot.values).flat();
170
- let buffers = await pathValueSerializer.serialize(values);
171
- let totalSize = buffers.reduce((a, b) => a + b.length, 0);
172
- console.log(`Sending snapshot: ${formatNumber(values.length)} values, ${formatNumber(buffers.length)} buffers, ${formatNumber(totalSize)}B`);
173
- return buffers;
174
- }
175
-
176
-
177
- // Returns the initial states of the watches as well
178
- // If there is no value, we call return { path, value: undefined, time: { time: 0, creatorId: 0 }, readLocks: [] }
179
- // to indicate there is no value.
180
- public async watchLatest(config: WatchConfig) {
181
- let callerId = SocketFunction.getCaller().nodeId;
182
- if (isDebugLogEnabled()) {
183
- let sourceNodeId = debugNodeId(callerId);
184
- for (let value of config.paths) {
185
- auditLog("WATCH PATH", { path: value, sourceNodeId, sourceNodeThreadId: debugNodeThread(callerId) });
186
- }
187
- for (let value of config.parentPaths) {
188
- auditLog("WATCH PARENT PATH", { path: value, sourceNodeId });
189
- }
190
- }
191
- pathWatcher.watchPath({ paths: config.paths, parentPaths: config.parentPaths, callback: callerId, initialTrigger: true });
192
- }
193
- public async unwatchLatest(config: WatchConfig) {
194
- let callerId = SocketFunction.getCaller().nodeId;
195
- if (isDebugLogEnabled()) {
196
- let sourceNodeId = debugNodeId(SocketFunction.getCaller().nodeId);
197
- for (let value of config.paths) {
198
- auditLog("UNWATCH PATH", { path: value, sourceNodeId, sourceNodeThreadId: debugNodeThread(callerId) });
199
- }
200
- for (let value of config.parentPaths) {
201
- auditLog("UNWATCH PARENT PATH", { path: value, sourceNodeId });
202
- }
203
- }
204
- pathWatcher.unwatchPath({ paths: config.paths, parentPaths: config.parentPaths, callback: callerId });
205
- }
206
-
207
- public async ping() { }
208
-
209
- public async test(obj: unknown) {
210
- return obj;
211
- }
212
- }
213
-
214
- export const PathValueController = SocketFunction.register(
215
- "PathValueController-1e062e2c-81c9-497b-b414-a46d0a4c2313",
216
- new PathValueControllerBase(),
217
- () => ({
218
- forwardWrites: {
219
- },
220
-
221
- watchLockValid: {},
222
- onValidChange: {},
223
-
224
- watchLatest: {},
225
- unwatchLatest: {},
226
-
227
- getSnapshot: {},
228
-
229
- ping: {},
230
- test: {},
231
- }),
232
- () => ({
233
- hooks: [requiresNetworkTrustHook],
234
- }),
235
- {
236
- noFunctionMeasure: !isNode(),
237
- }
238
- );
239
-
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { isNodeTrusted, requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
3
+ import { PathValue, WriteState, WatchConfig, MAX_CHANGE_AGE, getCompressNetwork, authorityStorage } from "./pathValueCore";
4
+ import { pathWatcher } from "./PathWatcher";
5
+ import { validStateComputer } from "./ValidStateComputer";
6
+ import { ActionsHistory } from "../diagnostics/ActionsHistory";
7
+ import { isNode, nextId, timeInSecond, timeoutToUndefined } from "socket-function/src/misc";
8
+ import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
9
+ import { pathValueCommitter } from "./PathValueCommitter";
10
+ import { Benchmark } from "../diagnostics/benchmark";
11
+ import { auditLog, isDebugLogEnabled } from "./auditLogs";
12
+ import { debugNodeId, debugNodeThread } from "../-c-identity/IdentityController";
13
+ import { isDiskAudit } from "../config";
14
+ import { decodeNodeId } from "../-a-auth/certs";
15
+ import { areNodeIdsEqual, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
16
+ import { getNodeIdIP } from "socket-function/src/nodeCache";
17
+ import { authorityLookup } from "./AuthorityLookup";
18
+ import { timeoutToError } from "../errors";
19
+ import { AuthoritySpec, PathRouter } from "./PathRouter";
20
+ export { pathValueCommitter };
21
+
22
+ // The sync test page has some functions which related to PathValueController significantly, and should always be exposed if PathValueController is exposed.
23
+ if (isNode()) {
24
+ setImmediate(() => {
25
+ import("../diagnostics/SyncTestPage");
26
+ });
27
+ }
28
+
29
+
30
+ // NOTE: initialTriggers = clobber values on these paths (or all children of a parent), And take the values that we've provided. This allows you to get a client into a nice state by just saying, I don't know what you had before from other servers, but drop all that. Here's the correct state.
31
+
32
+ export class PathValueControllerBase {
33
+ public static async createValues(config: {
34
+ nodeId: string;
35
+ pathValues: PathValue[];
36
+ }): Promise<void | "refused"> {
37
+ const { Querysub } = await import("../4-querysub/Querysub");
38
+ let { pathValues, nodeId } = config;
39
+
40
+ let serializedValues = await pathValueSerializer.serialize(pathValues, { compress: Querysub.COMPRESS_NETWORK });
41
+ if (isDebugLogEnabled()) {
42
+ for (let value of pathValues) {
43
+ auditLog("SEND CREATED VALUE", {
44
+ path: value.path,
45
+ timeId: value.time.time,
46
+ targetNodeThreadId: debugNodeThread(nodeId),
47
+ transparent: value.isTransparent,
48
+ source: value.source,
49
+ totalValues: pathValues.length,
50
+ });
51
+ }
52
+ }
53
+ return await PathValueController.nodes[nodeId].sendData({
54
+ valueBuffers: serializedValues,
55
+ initialCreation: true,
56
+ });
57
+ }
58
+ private static logSendValues(config: {
59
+ nodeId: string;
60
+ pathValues: PathValue[];
61
+ initialTriggers?: { values: Set<string>; parentPaths: Set<string> },
62
+ }) {
63
+ let { nodeId, pathValues, initialTriggers } = config;
64
+ if (isDebugLogEnabled()) {
65
+ for (let pathValue of pathValues) {
66
+ auditLog("SEND VALUE", {
67
+ path: pathValue.path,
68
+ timeId: pathValue.time.time,
69
+ watcher: nodeId,
70
+ isValid: pathValue.valid,
71
+ nodeId: debugNodeId(nodeId),
72
+ targetNodeId: debugNodeId(nodeId),
73
+ targetNodeThreadId: debugNodeThread(nodeId),
74
+ transparent: pathValue.isTransparent,
75
+ canGC: pathValue.canGCValue,
76
+ initialTrigger: initialTriggers?.values.has(pathValue.path),
77
+ triggerId: nextId(),
78
+ totalChanges: pathValues.length,
79
+ });
80
+ }
81
+ }
82
+ }
83
+ public static async sendValues(config: {
84
+ nodeId: string;
85
+ pathValues: PathValue[];
86
+ initialTriggers?: { values: Set<string>; parentPaths: Set<string> },
87
+ }) {
88
+ let changes = config.pathValues;
89
+ let { nodeId, initialTriggers } = config;
90
+ let allowSource = await isNodeTrusted(nodeId) || getNodeIdIP(nodeId) === "127.0.0.1";
91
+ let buffers = await pathValueSerializer.serialize(changes, {
92
+ noLocks: true,
93
+ compress: getCompressNetwork(),
94
+ stripSource: !allowSource,
95
+ });
96
+ this.logSendValues({ nodeId, pathValues: changes, initialTriggers, });
97
+ return await PathValueController.nodes[nodeId].sendData({
98
+ valueBuffers: buffers,
99
+ initialTriggers: initialTriggers,
100
+ });
101
+ }
102
+
103
+ public async sendData(config: {
104
+ // If it's the initial creation, then we can actually reject them. However, we do need to provide a rejection pathway for the client side to know that we have accepted none of the changes and that it should remove them locally.
105
+ initialCreation?: boolean;
106
+
107
+ valueBuffers?: Buffer[];
108
+ initialTriggers?: {
109
+ /** This means that we will provided the full history for all these paths. If you have any other history, it should be removed. */
110
+ values: Set<string>;
111
+ /** This means that we will have provided all of the child values for these parent paths, and so if you have any child values which were not provided in our value list, those values should be removed. */
112
+ parentPaths: Set<string>;
113
+ },
114
+
115
+ }): Promise<void | "refused"> {
116
+ let callerId = SocketFunction.getCaller().nodeId;
117
+ let { initialCreation, valueBuffers } = config;
118
+
119
+ let values: PathValue[] = [];
120
+ if (valueBuffers) {
121
+ values = await pathValueSerializer.deserialize(valueBuffers);
122
+ Benchmark.onReceivedValues(values);
123
+ ActionsHistory.OnRead(values);
124
+ }
125
+
126
+ if (initialCreation) {
127
+ let sourceNodeId = debugNodeId(callerId);
128
+ let rejected = false;
129
+ let threshold = Date.now() - MAX_CHANGE_AGE;
130
+ for (let value of values) {
131
+ let pastThreshold = threshold - value.time.time;
132
+ if (pastThreshold > 0) {
133
+ rejected = true;
134
+ console.error(`Rejecting values as one is too old. It likely got caught in the pipeline too long and now can't be committed without trying to undone history which can already been committed to disk.`, {
135
+ path: value.path,
136
+ timeId: value.time.time,
137
+ sourceNodeId,
138
+ sourceNodeThreadId: decodeNodeId(sourceNodeId)?.threadId,
139
+ totalValueCount: values.length,
140
+ });
141
+ }
142
+ }
143
+ if (rejected) {
144
+ return "refused";
145
+ }
146
+ }
147
+
148
+ if (isDebugLogEnabled() || isDiskAudit()) {
149
+ let sourceNodeId = debugNodeId(callerId);
150
+ for (let value of values) {
151
+ auditLog("RECEIVE VALUE", {
152
+ path: value.path,
153
+ // NOTE: Yes, this might conflict as we're throwing away a lot of information, however in practice no, it's not going to conflict when we're debugging, and this + path will be globally unique for every value we investigate.
154
+ timeId: value.time.time,
155
+ sourceNodeId,
156
+ sourceNodeThreadId: decodeNodeId(sourceNodeId)?.threadId,
157
+ });
158
+ }
159
+ }
160
+
161
+ try {
162
+ let parentSyncs = Array.from(config.initialTriggers?.parentPaths || [])
163
+ .map(x => ({ parentPath: x, sourceNodeId: callerId }));
164
+ let initialTriggers = config.initialTriggers || { values: new Set(), parentPaths: new Set() };
165
+ validStateComputer.ingestValuesAndValidStates({
166
+ pathValues: values,
167
+ parentSyncs,
168
+ initialTriggers,
169
+ });
170
+ } catch (error) {
171
+ console.error("Error ingesting values and valid states", error);
172
+ }
173
+
174
+
175
+ if (config.initialCreation) {
176
+ // NOTE: This will mean that all of the authorities for a value will write it to disk. So it will be written to disk multiple times. This should be fine, and it might be a good idea for redundancy, etc. And eventually, they will get compressed to one value, so it's not inefficient.
177
+ await PathValueControllerBase.authorityShareValues({ pathValues: values });
178
+ }
179
+ }
180
+
181
+ public static async authorityShareValues(config: {
182
+ pathValues: PathValue[];
183
+ }) {
184
+ let { pathValues } = config;
185
+ let valuesPerOtherAuthority = PathRouter.getAllAuthoritiesForValues(pathValues);
186
+ let promises = Array.from(valuesPerOtherAuthority.entries()).map(async ([otherAuthority, values]) => {
187
+ try {
188
+ await timeoutToError(timeInSecond * 15, PathValueControllerBase.sendValues({
189
+ nodeId: otherAuthority,
190
+ pathValues: values,
191
+ }), () => new Error(`Timeout forwarding shared values (${values.length} values) to authority ${otherAuthority}`));
192
+ } catch (error: any) {
193
+ console.error(error.message, { otherAuthority, count: values.length });
194
+ }
195
+ });
196
+ await Promise.all(promises);
197
+ }
198
+
199
+
200
+ public async watchLatest(config: WatchConfig) {
201
+ let callerId = SocketFunction.getCaller().nodeId;
202
+ if (isDebugLogEnabled()) {
203
+ let sourceNodeId = debugNodeId(callerId);
204
+ for (let value of config.paths) {
205
+ auditLog("WATCH PATH", { path: value, sourceNodeId, sourceNodeThreadId: debugNodeThread(callerId) });
206
+ }
207
+ for (let value of config.parentPaths) {
208
+ auditLog("WATCH PARENT PATH", { path: value, sourceNodeId });
209
+ }
210
+ }
211
+ pathWatcher.watchPath({
212
+ nodeId: callerId,
213
+ paths: config.paths,
214
+ parentPaths: config.parentPaths,
215
+ initialTrigger: true,
216
+ fullHistory: config.fullHistory,
217
+ });
218
+ }
219
+ public async unwatchLatest(config: WatchConfig) {
220
+ let callerId = SocketFunction.getCaller().nodeId;
221
+ if (isDebugLogEnabled()) {
222
+ let sourceNodeId = debugNodeId(SocketFunction.getCaller().nodeId);
223
+ for (let value of config.paths) {
224
+ auditLog("UNWATCHING PATH", { path: value, sourceNodeId, sourceNodeThreadId: debugNodeThread(callerId) });
225
+ }
226
+ for (let value of config.parentPaths) {
227
+ auditLog("UNWATCHING PARENT PATH", { path: value, sourceNodeId });
228
+ }
229
+ }
230
+ pathWatcher.unwatchPath({ paths: config.paths, parentPaths: config.parentPaths, callback: callerId });
231
+ }
232
+
233
+ public static async getInitialValues(config: {
234
+ nodeId: string;
235
+ spec: AuthoritySpec;
236
+ // Only returns values with a create time in this window
237
+ startTime: number;
238
+ endTime: number;
239
+ }): Promise<PathValue[]> {
240
+ let buffers = await PathValueController.nodes[config.nodeId].getInitialValues(config);
241
+ return await pathValueSerializer.deserialize(buffers);
242
+ }
243
+ // NOTE: Sends all the responses using the regular
244
+ public async getInitialValues(config: {
245
+ spec: AuthoritySpec;
246
+ // Only returns values with a create time in this window
247
+ startTime: number;
248
+ endTime: number;
249
+ }): Promise<Buffer[]> {
250
+ let values = authorityStorage.getAllValues(config);
251
+ let buffers = await pathValueSerializer.serialize(values, {
252
+ noLocks: true,
253
+ compress: getCompressNetwork(),
254
+ });
255
+ return buffers;
256
+ }
257
+ }
258
+
259
+ export const PathValueController = SocketFunction.register(
260
+ "PathValueController-1e062e2c-81c9-497b-b414-a46d0a4c2313",
261
+ new PathValueControllerBase(),
262
+ () => ({
263
+ sendData: {},
264
+
265
+ getInitialValues: {},
266
+
267
+ watchLatest: {},
268
+ unwatchLatest: {},
269
+ }),
270
+ () => ({
271
+ hooks: [requiresNetworkTrustHook],
272
+ }),
273
+ {
274
+ noFunctionMeasure: !isNode(),
275
+ }
276
+ );
277
+