querysub 0.403.0 → 0.404.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 (107) hide show
  1. package/.cursorrules +2 -0
  2. package/bin/audit-imports.js +4 -0
  3. package/package.json +7 -4
  4. package/spec.txt +77 -0
  5. package/src/-a-archives/archiveCache.ts +9 -4
  6. package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
  7. package/src/-a-auth/certs.ts +0 -12
  8. package/src/-c-identity/IdentityController.ts +12 -3
  9. package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
  10. package/src/-g-core-values/NodeCapabilities.ts +12 -2
  11. package/src/0-path-value-core/AuthorityLookup.ts +239 -0
  12. package/src/0-path-value-core/LockWatcher2.ts +150 -0
  13. package/src/0-path-value-core/PathRouter.ts +535 -0
  14. package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
  15. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +65 -0
  16. package/src/0-path-value-core/PathValueCommitter.ts +222 -488
  17. package/src/0-path-value-core/PathValueController.ts +277 -239
  18. package/src/0-path-value-core/PathWatcher.ts +534 -0
  19. package/src/0-path-value-core/ShardPrefixes.ts +31 -0
  20. package/src/0-path-value-core/ValidStateComputer.ts +303 -0
  21. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
  23. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
  24. package/src/0-path-value-core/auditLogs.ts +2 -0
  25. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
  26. package/src/0-path-value-core/pathValueArchives.ts +490 -492
  27. package/src/0-path-value-core/pathValueCore.ts +195 -1496
  28. package/src/0-path-value-core/startupAuthority.ts +74 -0
  29. package/src/1-path-client/RemoteWatcher.ts +90 -82
  30. package/src/1-path-client/pathValueClientWatcher.ts +808 -815
  31. package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
  32. package/src/2-proxy/archiveMoveHarness.ts +182 -214
  33. package/src/2-proxy/garbageCollection.ts +9 -8
  34. package/src/2-proxy/schema2.ts +21 -1
  35. package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
  36. package/src/3-path-functions/PathFunctionRunner.ts +943 -766
  37. package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
  38. package/src/3-path-functions/pathFunctionLoader.ts +2 -2
  39. package/src/3-path-functions/syncSchema.ts +592 -521
  40. package/src/4-deploy/deployFunctions.ts +19 -4
  41. package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
  42. package/src/4-deploy/deployMain.ts +51 -68
  43. package/src/4-deploy/edgeClientWatcher.tsx +1 -1
  44. package/src/4-deploy/edgeNodes.ts +2 -2
  45. package/src/4-dom/qreact.tsx +2 -4
  46. package/src/4-dom/qreactTest.tsx +7 -13
  47. package/src/4-querysub/Querysub.ts +21 -8
  48. package/src/4-querysub/QuerysubController.ts +45 -29
  49. package/src/4-querysub/permissions.ts +2 -2
  50. package/src/4-querysub/querysubPrediction.ts +80 -70
  51. package/src/4-querysub/schemaHelpers.ts +5 -1
  52. package/src/5-diagnostics/GenericFormat.tsx +14 -9
  53. package/src/archiveapps/archiveGCEntry.tsx +9 -2
  54. package/src/archiveapps/archiveJoinEntry.ts +87 -84
  55. package/src/archiveapps/archiveMergeEntry.tsx +2 -0
  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 +67 -0
  102. package/test.ts +288 -95
  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/lockTest.ts +0 -127
@@ -1,34 +1,38 @@
1
- import { cacheJSONArgsEqual, cacheWeak, lazy } from "socket-function/src/caching";
2
- import { pathValueCommitter } from "../0-path-value-core/PathValueController";
3
- import { CLIENTSIDE_PREDICT_LEEWAY, MAX_ACCEPTED_CHANGE_AGE, MAX_CHANGE_AGE, PathValue, ReadLock, Time, authorityStorage, compareTime, debugTime, getCreatorId, getNextTime, lockToCallback, predictionLockVersion, timeMinusEpsilon } from "../0-path-value-core/pathValueCore";
4
- import { remoteWatcher } from "../1-path-client/RemoteWatcher";
5
- import { proxyWatcher, atomicObjectRead, DryRunResult, getCurrentCallCreationProxy, debug_getQueueOrder } from "../2-proxy/PathValueProxyWatcher";
6
- import { getPathFromProxy, getProxyPath } from "../2-proxy/pathValueProxy";
7
- import { CallSpec, FunctionResult, FunctionSpec, debugCallSpec, functionSchema, overrideCurrentCall } from "../3-path-functions/PathFunctionRunner";
1
+ import { cacheJSONArgsEqual, lazy } from "socket-function/src/caching";
2
+ import { MAX_CHANGE_AGE, PathValue, ReadLock, Time, authorityStorage, predictionLockVersion } from "../0-path-value-core/pathValueCore";
3
+ import { validStateComputer } from "../0-path-value-core/ValidStateComputer";
4
+ import { proxyWatcher, atomicObjectRead, DryRunResult, getCurrentCallCreationProxy } from "../2-proxy/PathValueProxyWatcher";
5
+ import { getProxyPath } from "../2-proxy/pathValueProxy";
6
+ import { CallSpec, FunctionResult, FunctionSpec, functionSchema, overrideCurrentCall } from "../3-path-functions/PathFunctionRunner";
8
7
  import { getModuleFromConfig, setGitURLMapping } from "../3-path-functions/pathFunctionLoader";
9
8
  import { logErrors } from "../errors";
10
- import { getParentPathStr, getPathFromStr, getPathStr1, getPathStr2, getPathStr3 } from "../path";
11
- import { Querysub, QuerysubController, QuerysubControllerBase, baseAddCall, registerPredictionBlocker, querysubNodeId } from "./QuerysubController";
9
+ import { getPathFromStr, getPathStr1 } from "../path";
10
+ import { Querysub, QuerysubController, QuerysubControllerBase, registerPredictionBlocker, querysubNodeId } from "./QuerysubController";
12
11
  import { Benchmark } from "../diagnostics/benchmark";
13
12
  import { parseArgs } from "../3-path-functions/PathFunctionHelpers";
14
- import { runInSerial } from "socket-function/src/batching";
15
- import { green, magenta, red, yellow } from "socket-function/src/formatting/logColors";
13
+ import { magenta, red } from "socket-function/src/formatting/logColors";
16
14
  import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
17
15
 
18
16
  import { setFlag } from "socket-function/require/compileFlags";
19
17
  import cbor from "cbor-x";
20
18
  import { FunctionMetadata } from "../3-path-functions/syncSchema";
21
- import { PromiseObj, isNode, nextId, sort } from "socket-function/src/misc";
22
- import { getBrowserUrlNode } from "../-f-node-discovery/NodeDiscovery";
23
- import { isLocal } from "../config";
24
- import { onAllPredictionsFinished } from "../-0-hooks/hooks";
25
- import { t } from "../2-proxy/schema2";
19
+ import { PromiseObj, isNode } from "socket-function/src/misc";
20
+ import { isPublic } from "../config";
26
21
  import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
27
22
  import { cacheAsyncLimitedJSON } from "../functional/promiseCache";
28
23
  import { addEpsilons } from "../bits";
29
24
  setFlag(require, "cbor-x", "allowclient", true);
30
25
  const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
31
26
 
27
+ let onPredictionFinishedCallbacks: Array<(data: { callId: string; result: FunctionResult; functionId: string }) => void> = [];
28
+
29
+ export function onPredictionFinished(callback: (data: {
30
+ callId: string;
31
+ result: FunctionResult;
32
+ functionId: string;
33
+ }) => void) {
34
+ onPredictionFinishedCallbacks.push(callback);
35
+ }
32
36
 
33
37
  // NOTE: Most functions won't use this, as they should use regular api calls. However,
34
38
  // as we are using paths for RequireJS, we explicitly need our local (when serverside).
@@ -120,8 +124,11 @@ function predictCallBase(config: {
120
124
  let functionResult: FunctionResult = {
121
125
  // Special prediction values (we don't even really need to put anything here, but it
122
126
  // might as well have the right format, in case a bug causes it to be used somewhere)
123
- runCount: -1,
127
+ lastInternalLoopCount: -1,
128
+ outerLoopCount: -1,
129
+ totalInternalLoopCount: -1,
124
130
  timeTaken: -1,
131
+ totalTime: -1,
125
132
  evalTime: 0,
126
133
  };
127
134
 
@@ -143,7 +150,6 @@ function predictCallBase(config: {
143
150
  path: pathResultWrite,
144
151
  // Technically we are depending on a future time, so... we are depending on ourself? And our value isn't undefined.
145
152
  readIsTransparent: false,
146
- keepRejectedUntil: call.runAtTime.time + CLIENTSIDE_PREDICT_LEEWAY
147
153
  }
148
154
  ],
149
155
  lockCount: 1,
@@ -169,9 +175,6 @@ function predictCallBase(config: {
169
175
  console.log(magenta(`Start predict call`), `${call.DomainName}.${call.FunctionId}`);
170
176
  }
171
177
 
172
- // Watch the result, so we know when our prediction is rejected (which will be as soon as the result
173
- // has a real value).
174
- remoteWatcher.watchLatest({ paths: [pathResultWrite], parentPaths: [] });
175
178
  let debugName = `[predict]|${call.DomainName}.${call.FunctionId}`;
176
179
 
177
180
  let dryRunResult: {
@@ -182,8 +185,13 @@ function predictCallBase(config: {
182
185
 
183
186
  let actualValueFinished = new PromiseObj();
184
187
  function onActualFinished() {
185
- if (authorityStorage.getValueAtTime(pathResultWrite, undefined)?.value) {
186
- actualValueFinished.resolve();
188
+ let resultObj = authorityStorage.getValueAtTime(pathResultWrite, undefined);
189
+ let result = pathValueSerializer.getPathValue(resultObj) as FunctionResult | undefined;
190
+ if (!result) return;
191
+ if (result.lastInternalLoopCount === -1) return;
192
+ actualValueFinished.resolve();
193
+ for (let callback of onPredictionFinishedCallbacks) {
194
+ callback({ callId: call.CallId, result, functionId: call.FunctionId });
187
195
  }
188
196
  }
189
197
  clientWatcher.setWatches({
@@ -191,40 +199,39 @@ function predictCallBase(config: {
191
199
  paths: new Set([pathResultWrite]),
192
200
  parentPaths: new Set(),
193
201
  });
194
- try {
195
202
 
196
- try {
197
- let tempDryRunResult = await Promise.race([
198
- await getCallWrites({ call, debugName, overrides: config.overrides, useFinishReordering: true, metadata: config.metadata }),
199
- actualValueFinished.promise,
200
- ]);
201
- if (!tempDryRunResult) {
202
- if (Querysub.DEBUG_PREDICTIONS || Querysub.DEBUG_CALLS) {
203
- console.log(magenta(`Abort predict call before prediction finished, already received call result`), `${call.DomainName}.${call.FunctionId}`);
204
- }
205
- return undefined;
206
- }
207
- dryRunResult = tempDryRunResult;
208
- } catch (e: any) {
209
-
210
- if (!pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(pathResultWrite, undefined))) {
211
- console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e.stack);
212
- } else {
213
- // NOTE: This case happens a lot, because of how we handle locks. We don't receive locked values, and so
214
- // we assume all values have no locks, and only keep the latest. This is usually fine, but... if we lose
215
- // the race to predict the function against the server updating it, it is likely our prediction will now
216
- // be running before the latest write. In which case (as we don't really store write history), we will read undefined.
217
- // This isn't accurate, but... our write WILL almost certainly be wrong (as the value changed), so we don't log here.
203
+ setTimeout(() => {
204
+ clientWatcher.unwatch(onActualFinished);
205
+ }, 30 * 1000);
206
+
207
+ try {
208
+ let tempDryRunResult = await Promise.race([
209
+ await getCallWrites({ call, debugName, overrides: config.overrides, useFinishReordering: true, metadata: config.metadata }),
210
+ actualValueFinished.promise,
211
+ ]);
212
+ if (!tempDryRunResult) {
213
+ if (Querysub.DEBUG_PREDICTIONS || Querysub.DEBUG_CALLS) {
214
+ console.log(magenta(`Abort predict call before prediction finished, already received call result`), `${call.DomainName}.${call.FunctionId}`);
218
215
  }
219
- return {
220
- writes: [],
221
- readPaths: new Set(),
222
- readParentPaths: new Set(),
223
- replacedWriteValues: [],
224
- };
216
+ return undefined;
225
217
  }
226
- } finally {
227
- clientWatcher.unwatch(onActualFinished);
218
+ dryRunResult = tempDryRunResult;
219
+ } catch (e: any) {
220
+ if (!pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(pathResultWrite, undefined))) {
221
+ console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e.stack);
222
+ } else {
223
+ // NOTE: This case happens a lot, because of how we handle locks. We don't receive locked values, and so
224
+ // we assume all values have no locks, and only keep the latest. This is usually fine, but... if we lose
225
+ // the race to predict the function against the server updating it, it is likely our prediction will now
226
+ // be running before the latest write. In which case (as we don't really store write history), we will read undefined.
227
+ // This isn't accurate, but... our write WILL almost certainly be wrong (as the value changed), so we don't log here.
228
+ }
229
+ return {
230
+ writes: [],
231
+ readPaths: new Set(),
232
+ readParentPaths: new Set(),
233
+ replacedWriteValues: [],
234
+ };
228
235
  }
229
236
  predictions = {
230
237
  writes: dryRunResult.writes,
@@ -274,14 +281,11 @@ function predictCallBase(config: {
274
281
  predict.lockCount = predictLocks.length;
275
282
  }
276
283
 
277
- pathValueCommitter.ingestValues(predictions.writes, undefined, undefined);
278
-
279
- // NOTE: We have to forcefully setup the lock watches, as we are no the authority on these paths
280
- // so it is odd for us to do this! It works out though, because the path is unique enough
281
- // that we don't have to worry about any other valid states coming in for our lock.
282
- // - Also, once we set up this lock, we should get full reject cascade support.
283
- lockToCallback.watchLock(predictResultWrite.locks[0], [predictResultWrite]);
284
- lockToCallback.watchLock(predictLocks[0], predictions.writes);
284
+ validStateComputer.ingestValuesAndValidStates({
285
+ pathValues: predictions.writes,
286
+ parentSyncs: [],
287
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
288
+ });
285
289
 
286
290
  if (Querysub.DEBUG_PREDICTIONS) {
287
291
  console.log(magenta(`Finished and applied predict call`), `${call.DomainName}.${call.FunctionId}`);
@@ -295,13 +299,19 @@ function predictCallBase(config: {
295
299
  if (didCancel) return;
296
300
  didCancel = true;
297
301
  // Reject our predictions, as the call likely never got committed, so it will never be written
298
- pathValueCommitter.ingestValidStates([{
299
- path: pathResultWrite,
300
- isValid: false,
301
- time: predictResultWrite.time,
302
- isTransparent: false,
303
- reason: "prediction timeout rejected",
304
- }]);
302
+ validStateComputer.ingestValuesAndValidStates({
303
+ pathValues: [{
304
+ path: pathResultWrite,
305
+ value: undefined,
306
+ locks: [],
307
+ lockCount: 0,
308
+ valid: false,
309
+ time: predictResultWrite.time,
310
+ isTransparent: false,
311
+ }],
312
+ parentSyncs: [],
313
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
314
+ });
305
315
  }
306
316
 
307
317
  setTimeout(cleanupPrediction, Querysub.PREDICTION_MAX_LIFESPAN);
@@ -357,7 +367,7 @@ const getDevFunctionCache = cacheAsyncLimitedJSON(100_000, getDevFunctionSpecFro
357
367
  const addToModuleLoaderCache = cacheAsyncLimitedJSON(100_000, addModuleToLoader);
358
368
 
359
369
  function getFunctionSpec(call: CallSpec): FunctionSpec | undefined {
360
- if (isLocal()) {
370
+ if (!isPublic()) {
361
371
  let obj = getDevFunctionCache({
362
372
  DomainName: call.DomainName,
363
373
  ModuleId: call.ModuleId,
@@ -1,5 +1,4 @@
1
1
  import { lazy } from "socket-function/src/caching";
2
- import { LOCAL_DOMAIN } from "../0-path-value-core/NodePathAuthorities";
3
2
  import { registerSchemaPrefix } from "../2-proxy/PathValueProxyWatcher";
4
3
  import { rawSchema } from "../2-proxy/pathDatabaseProxyBase";
5
4
  import { Schema2, Schema2T } from "../2-proxy/schema2";
@@ -7,6 +6,11 @@ import { isDynamicallyLoading } from "../config";
7
6
  import { getPathStr2 } from "../path";
8
7
  import { isHotReloading } from "socket-function/hot/HotReloadController";
9
8
  import debugbreak from "debugbreak";
9
+ import { LOCAL_DOMAIN } from "../0-path-value-core/PathRouter";
10
+ if (!registerSchemaPrefix) {
11
+ debugbreak(2);
12
+ debugger;
13
+ }
10
14
 
11
15
  let localSchemaNames = new Set<string>();
12
16
  export function createLocalSchema<T>(name: string): () => T;
@@ -31,7 +31,7 @@ type StringFormatters = (
31
31
  | "<Selector>"
32
32
  );
33
33
 
34
- function d(value: unknown, formattedValue: preact.ComponentChild) {
34
+ function def(value: unknown, formattedValue: preact.ComponentChild) {
35
35
  if (value === undefined || value === null) {
36
36
  return "";
37
37
  }
@@ -40,14 +40,14 @@ function d(value: unknown, formattedValue: preact.ComponentChild) {
40
40
  const startGuessDateRange = +new Date(2010, 0, 1).getTime();
41
41
  const endGuessDateRange = +new Date(2040, 0, 1).getTime();
42
42
  let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.ComponentChild } = {
43
- string: (value) => d(value, String(value || "")),
44
- number: (value) => d(value, formatNumber(Number(value))),
45
- percent: (value) => d(value, formatPercent(Number(value))),
46
- timeSpan: (value) => d(value, formatTime(Number(value))),
47
- date: (value) => d(value, <span title={formatDateTimeDetailed(Number(value))}>{formatDateTime(Number(value))}</span>),
48
- error: (value) => d(value, <span class={errorMessage}>{String(value)}</span>),
49
- toSpaceCase: (value) => d(value, toSpaceCase(String(value))),
50
- "<Selector>": (value) => d(value, <Selector {...JSON.parse(String(value).slice("<Selector>".length))} />),
43
+ string: (value) => def(value, String(value || "")),
44
+ number: (value) => def(value, formatNumber(Number(value))),
45
+ percent: (value) => def(value, formatPercent(Number(value))),
46
+ timeSpan: (value) => def(value, formatTime(Number(value))),
47
+ date: (value) => def(value, <span title={formatDateTimeDetailed(Number(value))}>{formatDateTime(Number(value))}</span>),
48
+ error: (value) => def(value, <span class={errorMessage}>{String(value)}</span>),
49
+ toSpaceCase: (value) => def(value, toSpaceCase(String(value))),
50
+ "<Selector>": (value) => def(value, <Selector {...JSON.parse(String(value).slice("<Selector>".length))} />),
51
51
  link: (value) => {
52
52
  if (value === undefined || value === null) {
53
53
  return "";
@@ -118,6 +118,11 @@ let formatters: { [formatter in StringFormatters]: (value: unknown) => preact.Co
118
118
  if (Array.isArray(value) && !value.some(x => canHaveChildren(x))) {
119
119
  return formatValue(value, "varray:guess");
120
120
  }
121
+ if (canHaveChildren(value)) {
122
+ try {
123
+ return JSON.stringify(value);
124
+ } catch { }
125
+ }
121
126
  return formatters.string(value);
122
127
  },
123
128
  };
@@ -8,6 +8,10 @@ import { runInfinitePollCallAtStart } from "socket-function/src/batching";
8
8
 
9
9
  import yargs from "yargs";
10
10
  import { runAliveCheckerIteration } from "../2-proxy/garbageCollection";
11
+ import { waitForImportBlockers } from "../3-path-functions/pathFunctionLoader";
12
+ import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
13
+ import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
14
+ import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
11
15
 
12
16
  let yargObj = isNodeTrue() && yargs(process.argv)
13
17
  .option("watch", { type: "boolean", desc: "Watch, and GC every N time (every day as of writing this)" })
@@ -16,12 +20,13 @@ let yargObj = isNodeTrue() && yargs(process.argv)
16
20
 
17
21
  // yarn gc --authority testshard.json
18
22
  async function main() {
19
- await Querysub.hostService("gc");
20
-
23
+ disablePathAuditer();
21
24
  let folderRoot = path.resolve(".");
22
25
  folderRoot = folderRoot.replaceAll(/\\/g, "/");
23
26
  const deployPath = path.resolve("./deploy.ts");
24
27
  require(deployPath);
28
+ await waitForImportBlockers();
29
+ await Querysub.hostService("gc");
25
30
 
26
31
  if (yargObj.watch) {
27
32
  await runInfinitePollCallAtStart(timeInDay, runAliveCheckerIteration);
@@ -29,6 +34,8 @@ async function main() {
29
34
  try {
30
35
  // Force, as they are running this manually, and so they probably want to see something happen...
31
36
  await runAliveCheckerIteration({ force: true });
37
+ } catch (error) {
38
+ console.error(error);
32
39
  } finally {
33
40
  process.exit();
34
41
  }
@@ -1,8 +1,6 @@
1
1
  import "../inject";
2
2
 
3
3
  import { logErrors } from "../errors";
4
- import { getOurAuthorities } from "../config2";
5
- import { pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
6
4
  import { PathValueArchives, pathValueArchives } from "../0-path-value-core/pathValueArchives";
7
5
  import { FILE_SIZE_LIMIT, FILE_VALUE_COUNT_LIMIT, PathValue, VALUE_GC_THRESHOLD } from "../0-path-value-core/pathValueCore";
8
6
  import { runInfinitePollCallAtStart } from "socket-function/src/batching";
@@ -13,111 +11,116 @@ import { formatNumber } from "socket-function/src/formatting/format";
13
11
  import { sort } from "socket-function/src/misc";
14
12
  import { Querysub } from "../4-querysub/QuerysubController";
15
13
  import { magenta } from "socket-function/src/formatting/logColors";
14
+ import { PathRouter } from "../0-path-value-core/PathRouter";
15
+ import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
16
+ import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
16
17
 
17
18
  async function runGenesisJoinIteration() {
18
- let authorities = getOurAuthorities();
19
- for (let authority of authorities) {
20
- let locker = await pathValueArchives.getArchiveLocker();
21
- let matchedDirs = await pathValueArchives.getAuthorityDirs(authority);
22
- let authorityDir = pathValueAuthority2.getArchiveDirectory(authority);
23
- let maxAge = Date.now() - VALUE_GC_THRESHOLD;
24
-
25
- let readCache = new Map<string, Buffer>();
26
- let reread = true;
27
- while (reread) {
28
- reread = false;
29
- let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
30
- let originalCount = valueFiles.length;
31
- valueFiles = valueFiles.filter(x => matchedDirs.some(y => x.file.startsWith(y)));
32
- let underPathCount = valueFiles.length;
33
- // Merge the newest first, so if we hit a big file, we can just ignore it,
34
- // and next time merge the smaller files after it.
35
- sort(valueFiles, x => -x.createTime);
36
- valueFiles = valueFiles.filter(x => {
37
- let obj = pathValueArchives.decodeDataPath(x.file);
38
- if (!obj.minTime) return false;
39
- if (obj.sourceType !== "genesis") return false;
40
- return x.createTime >= maxAge;
41
- });
42
- let withinTimeRangeCount = valueFiles.length;
43
-
44
- if (valueFiles.length < 3) return [];
45
-
46
- await measureBlock(async function locker_readFiles() {
47
- let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
48
- let buffers = await readFiles(remainingFiles);
49
- for (let i = 0; i < remainingFiles.length; i++) {
50
- let buffer = buffers[i];
51
- if (!buffer) {
52
- console.log(`File missing, re-reading`, remainingFiles[i].file);
53
- reread = true;
54
- break;
55
- }
56
- readCache.set(remainingFiles[i].file, buffer);
57
- }
58
- });
59
- if (reread) {
60
- console.log(`Files changed, re-reading`);
61
- return [];
62
- }
19
+ let authoritySpec = await getOurAuthoritySpec(true);
20
+ let locker = await pathValueArchives.getArchiveLocker();
21
+ let maxAge = Date.now() - VALUE_GC_THRESHOLD;
63
22
 
64
- let usedFiles: FileInfo[] = [];
65
- let totalSize = 0;
66
- let totalCount = 0;
67
- let allValues: PathValue[][] = [];
68
- for (let valueFile of valueFiles) {
69
- let buffer = readCache.get(valueFile.file)!;
70
- if (totalSize + buffer.length > FILE_SIZE_LIMIT) {
71
- break;
72
- }
73
- let newValues = await PathValueArchives.loadValuesFromBuffer({
74
- path: valueFile.file,
75
- data: buffer,
76
- });
23
+ let readCache = new Map<string, Buffer>();
24
+ let reread = true;
25
+ while (reread) {
26
+ reread = false;
27
+ let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
28
+ let originalCount = valueFiles.length;
29
+ valueFiles = valueFiles.filter(x => PathRouter.overlapsPathIdentifier(authoritySpec, x.file));
30
+ let underPathCount = valueFiles.length;
31
+ // Merge the newest first, so if we hit a big file, we can just ignore it,
32
+ // and next time merge the smaller files after it.
33
+ sort(valueFiles, x => -x.createTime);
34
+ valueFiles = valueFiles.filter(x => {
35
+ let obj = pathValueArchives.decodeDataPath(x.file);
36
+ if (!obj.minTime) return false;
37
+ if (obj.sourceType !== "genesis") return false;
38
+ return x.createTime >= maxAge;
39
+ });
40
+ let withinTimeRangeCount = valueFiles.length;
41
+
42
+ if (valueFiles.length < 3) return [];
77
43
 
78
- let readValues = newValues.values;
79
- if (readValues.length + totalCount > FILE_VALUE_COUNT_LIMIT) {
44
+ await measureBlock(async function locker_readFiles() {
45
+ let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
46
+ let buffers = await readFiles(remainingFiles);
47
+ for (let i = 0; i < remainingFiles.length; i++) {
48
+ let buffer = buffers[i];
49
+ if (!buffer) {
50
+ console.log(`File missing, re-reading`, remainingFiles[i].file);
51
+ reread = true;
80
52
  break;
81
53
  }
82
- allValues.push(readValues);
83
- usedFiles.push(valueFile);
84
- totalSize += buffer.length;
85
- totalCount += readValues.length;
54
+ readCache.set(remainingFiles[i].file, buffer);
55
+ }
56
+ });
57
+ if (reread) {
58
+ console.log(`Files changed, re-reading`);
59
+ return [];
60
+ }
61
+
62
+ let usedFiles: FileInfo[] = [];
63
+ let totalSize = 0;
64
+ let totalCount = 0;
65
+ let allValues: PathValue[][] = [];
66
+ for (let valueFile of valueFiles) {
67
+ let buffer = readCache.get(valueFile.file)!;
68
+ if (totalSize + buffer.length > FILE_SIZE_LIMIT) {
69
+ break;
86
70
  }
87
- if (usedFiles.length <= 1) return [];
71
+ let newValues = await PathValueArchives.loadValuesFromBuffer({
72
+ path: valueFile.file,
73
+ data: buffer,
74
+ });
75
+
76
+ let readValues = newValues.values;
77
+ if (readValues.length + totalCount > FILE_VALUE_COUNT_LIMIT) {
78
+ break;
79
+ }
80
+ allValues.push(readValues);
81
+ usedFiles.push(valueFile);
82
+ totalSize += buffer.length;
83
+ totalCount += readValues.length;
84
+ }
85
+ if (usedFiles.length <= 1) return [];
86
+
87
+ let allCombinedValues = allValues.flat();
88
88
 
89
- let allCombinedValues = allValues.flat();
90
89
 
91
- let dataObj = await pathValueArchives.encodeValuePaths(allCombinedValues, {
90
+ let transaction: ArchiveTransaction = {
91
+ createFiles: [],
92
+ deleteFiles: [],
93
+ };
94
+ for (let file of usedFiles) {
95
+ transaction.deleteFiles.push(file);
96
+ }
97
+
98
+ let targets = PathRouter.getSelfPathIdentifierTargets(allCombinedValues);
99
+ for (let [target, values] of targets) {
100
+
101
+ let dataObj = await pathValueArchives.encodeValuePaths(values, {
92
102
  pathOverrides: {
93
103
  sourceType: "join",
94
104
  },
95
105
  });
96
- if (!dataObj) return [];
97
- let transaction: ArchiveTransaction = {
98
- createFiles: [],
99
- deleteFiles: [],
100
- };
101
- for (let file of usedFiles) {
102
- transaction.deleteFiles.push(file);
103
- }
104
- let newFileName = authorityDir + dataObj?.file;
106
+ if (!dataObj) continue;
107
+ let newFileName = target + "/" + dataObj?.file;
105
108
  transaction.createFiles.push({
106
109
  file: newFileName,
107
110
  data: dataObj.data,
108
111
  });
112
+ }
109
113
 
114
+ console.log(magenta(`Joining ${formatNumber(usedFiles.length)} files with ${formatNumber(allCombinedValues.length)} values in ${formatNumber(totalSize)} bytes. Original count: ${formatNumber(originalCount)}, under path count: ${formatNumber(underPathCount)}, within time range count: ${formatNumber(withinTimeRangeCount)}`));
110
115
 
111
- console.log(magenta(`Joining ${formatNumber(usedFiles.length)} files with ${formatNumber(allCombinedValues.length)} values in ${formatNumber(totalSize)} bytes. Original count: ${formatNumber(originalCount)}, under path count: ${formatNumber(underPathCount)}, within time range count: ${formatNumber(withinTimeRangeCount)}`));
112
-
113
- return [transaction];
114
- });
115
- console.log(`Join result: ${result}`);
116
- }
116
+ return [transaction];
117
+ });
118
+ console.log(`Join result: ${result}`);
117
119
  }
118
120
  }
119
121
 
120
122
  async function main() {
123
+ disablePathAuditer();
121
124
  await Querysub.hostService("join");
122
125
 
123
126
  await runInfinitePollCallAtStart(VALUE_GC_THRESHOLD * 0.8, runGenesisJoinIteration);
@@ -5,6 +5,7 @@ import { logErrors } from "../errors";
5
5
  import { timeInDay, timeInHour } from "socket-function/src/misc";
6
6
  import { runArchiveMover } from "../2-proxy/archiveMoveHarness";
7
7
  import { PathValue, compareTime } from "../0-path-value-core/pathValueCore";
8
+ import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
8
9
 
9
10
  const MERGE_DELAY = timeInHour;
10
11
 
@@ -34,6 +35,7 @@ async function mergeFiles() {
34
35
  }
35
36
 
36
37
  async function main() {
38
+ disablePathAuditer();
37
39
  await Querysub.hostService("merge");
38
40
  await mergeFiles();
39
41
  process.exit();
package/src/bits.ts CHANGED
@@ -53,6 +53,25 @@ export function setLowHighUint32(low: number, high: number): number {
53
53
  return conversionBuffer[0];
54
54
  }
55
55
 
56
+ /** Adds protection against NaN values, changing the result if it would be NaN. This is because NaN values will compare to be not equal even if they are equal in certain cases, and in other cases, they'll always be equal, even if their bits are not equal.
57
+ - This means this is not a reversible operation. However, in a lot of cases, that doesn't matter.
58
+ */
59
+ export function setLowHighUint32Safe(low: number, high: number): number {
60
+ conversionUint32Buffer[0] = low;
61
+ // Prevent NaN by not setting all the exponent bits to 1
62
+ conversionUint32Buffer[1] = high & 0xBFFFFFFF;
63
+ return conversionBuffer[0];
64
+ }
65
+
66
+ // NOTE: Not reversible, see setLowHighUint32Safe
67
+ export function xor64BitsSafe(a: number, b: number): number {
68
+ let high = getHighUint32(a);
69
+ let low = getLowUint32(a);
70
+ let high2 = getHighUint32(b);
71
+ let low2 = getLowUint32(b);
72
+ return setLowHighUint32Safe(low ^ low2, high ^ high2);
73
+ }
74
+
56
75
  // Gets bits that can be stored in a number. Specifically, the first 62 bits,
57
76
  // as 64 bits will not compare correctly when treated as a double.
58
77
  export function getShortNumber(buffer: Buffer): number {
package/src/config.ts CHANGED
@@ -13,7 +13,9 @@ let yargObj = parseArgsFactory()
13
13
  .option("domain", { type: "string", desc: `Sets the domain` })
14
14
  .option("emaildomain", { type: "string", desc: `Sets the domain to use for email` })
15
15
  .option("client", { type: "boolean", desc: `Drops permissions, acting as an unauthenticated node` })
16
- .option("authority", { type: "string", desc: `Defines the base paths we are an authority on (the domain is prepended to them). Either a file path to a JSON(AuthorityPath[]), or a base64 representation of the JSON(AuthorityPath[]).` })
16
+ .option("arange", { type: "string", desc: `Authority range. Ex, 0.5-1.0` })
17
+ .option("aexcludedefault", { type: "boolean", desc: `Exclude default authority values, so we only match our prefixes.` })
18
+ .option("aprefix", { type: "array", desc: `Override the prefixes (pass multiple arguments to add multiple). Can be used with aexcludedefault to restrict an authority to only a few specific prefixes (ex, ` })
17
19
  .option("nobreak", { type: "boolean", desc: "Do not break on errors. Safer to set this than to just not set debugbreak, as some places might break without checking debugbreak, but nobreak works at a level where it is always used." })
18
20
  .option("public", { type: "boolean", desc: "Expose on public ports." })
19
21
  .option("local", { type: "boolean", desc: `If true, uses the local directory instead of the remote git repo. Also hotreloads from disk. Determines the repo to replace through the package.json "repository" property.` })
@@ -27,6 +29,7 @@ let yargObj = parseArgsFactory()
27
29
  // NOTE: I wanna see how long I can keep this on for. Eventually it's gonna become a problem and we're gonna have to turn it off. But for testing it's certainly useful as we don't know exactly what is gonna cause a problem. But it probably will be synchronization related, and every server does synchronization.
28
30
  desc: "Track all audit logs to disk. This might end up writing A LOT of data."
29
31
  })
32
+ .option("logbackblaze", { type: "boolean", desc: "Log all backblaze activity to disk." })
30
33
  .argv
31
34
  ;
32
35
  type QuerysubConfig = {
@@ -48,6 +51,10 @@ let querysubConfig = lazy((): QuerysubConfig => {
48
51
  }
49
52
  });
50
53
 
54
+ export function isLogBackblaze() {
55
+ return !!yargObj.logbackblaze;
56
+ }
57
+
51
58
  export function isNoNetwork() {
52
59
  return yargObj.nonetwork;
53
60
  }
@@ -102,8 +109,18 @@ export function enableDebugging() {
102
109
  (globalThis as any).enableDebugging = enableDebugging;
103
110
 
104
111
 
105
- export function authorityRaw() {
106
- return yargObj.authority;
112
+ export function getAuthorityRange() {
113
+ return yargObj.arange;
114
+ }
115
+
116
+ export function getAuthorityExcludeDefault() {
117
+ return !!yargObj.aexcludedefault;
118
+ }
119
+
120
+ export function getAuthorityPrefix() {
121
+ if (!yargObj.aprefix) return undefined;
122
+ if (Array.isArray(yargObj.aprefix)) return yargObj.aprefix;
123
+ return [yargObj.aprefix];
107
124
  }
108
125
 
109
126
  export function isPublic() {
@@ -113,6 +130,7 @@ export function isPublic() {
113
130
  return !!yargObj.public;
114
131
  }
115
132
 
133
+ /** @deprecated Use !isPublic() instead */
116
134
  export function isLocal() {
117
135
  return !!yargObj.local;
118
136
  }