querysub 0.441.0 → 0.442.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 (30) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/bin/mcp-indexed-logs.js +6 -0
  3. package/package.json +7 -4
  4. package/spec.txt +1 -0
  5. package/src/-a-archives/archiveCache.ts +1 -1
  6. package/src/-e-certs/EdgeCertController.ts +2 -8
  7. package/src/-f-node-discovery/NodeDiscovery.ts +14 -7
  8. package/src/-g-core-values/NodeCapabilities.ts +4 -0
  9. package/src/0-path-value-core/AuthorityLookup.ts +9 -4
  10. package/src/0-path-value-core/LockWatcher2.ts +1 -0
  11. package/src/0-path-value-core/PathValueController.ts +1 -1
  12. package/src/0-path-value-core/PathWatcher.ts +17 -19
  13. package/src/0-path-value-core/pathValueArchives.ts +20 -2
  14. package/src/0-path-value-core/pathValueCore.ts +5 -3
  15. package/src/1-path-client/RemoteWatcher.ts +17 -22
  16. package/src/1-path-client/pathValueClientWatcher.ts +1 -1
  17. package/src/2-proxy/PathValueProxyWatcher.ts +5 -5
  18. package/src/4-querysub/Querysub.ts +24 -7
  19. package/src/4-querysub/QuerysubController.ts +9 -2
  20. package/src/archiveapps/archiveJoinEntry.ts +1 -1
  21. package/src/diagnostics/ValuePathWarning.tsx +68 -0
  22. package/src/diagnostics/logs/IndexedLogs/BufferIndex.ts +113 -1
  23. package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +4 -4
  24. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +37 -2
  25. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +389 -0
  26. package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts +190 -0
  27. package/src/diagnostics/logs/diskLogger.ts +3 -0
  28. package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +2 -1
  29. package/src/diagnostics/managementPages.tsx +5 -1
  30. package/src/library-components/SyncedController.ts +2 -1
@@ -40,6 +40,7 @@ import { getDomain, isBootstrapOnly } from "../config";
40
40
  import { flushPredictionQueueBase, runInPredictionQueue, syncHasPendingPredictionsBase } from "./predictionQueue";
41
41
  import { PathRouter } from "../0-path-value-core/PathRouter";
42
42
  import { authorityLookup } from "../0-path-value-core/AuthorityLookup";
43
+ import { PathValueArchives } from "../0-path-value-core/pathValueArchives";
43
44
 
44
45
  let yargObj = isNodeTrue() && yargs(process.argv)
45
46
  .option("fncfilter", { type: "string", default: "", desc: `Sets the filterable state for function calls, causing them to target specific FunctionRunners. If no FunctionRunner matches, all functions will fail to run. For example: "devtestserver" will match a FunctionRunner that uses the "devtestserver" filter. Merges with the existing filterable state if a client sets it explicitly.` })
@@ -490,7 +491,7 @@ export class QuerysubControllerBase {
490
491
 
491
492
  if (removedPaths.size > 0) {
492
493
  let [removedParentPaths, removedJustPaths] = splitParentPaths(removedPaths);
493
- pathWatcher.unwatchPath({ callback: callerId, paths: removedJustPaths, parentPaths: removedParentPaths });
494
+ pathWatcher.unwatchPath({ callback: callerId, paths: removedJustPaths, parentPaths: removedParentPaths, reason: "QuerysubController.watch removed paths (permissions)" });
494
495
  }
495
496
  if (allowedPaths.size > 0) {
496
497
  let [newParentPathsAllowed, newPathsAllowed] = splitParentPaths(allowedPaths);
@@ -531,7 +532,7 @@ export class QuerysubControllerBase {
531
532
  delPermissionsPath(appendToPathStr(path, ""), true);
532
533
  }
533
534
  }
534
- pathWatcher.unwatchPath({ callback: callerId, ...config });
535
+ pathWatcher.unwatchPath({ callback: callerId, ...config, reason: "QuerysubController.unwatch" });
535
536
  }
536
537
 
537
538
  // NOTE: Calls are going to be temporary and random. Any user can use any call ID, so technically you could clobber other users' call IDs, or your our. There wouldn't really be any benefit. Nothing would really happen if you do that, so I don't believe these need to be kept secret. I think if you know someone else's call ID you might be able to read that data, but also it's securely random, so you're not going to be able to guess the call ID.
@@ -686,6 +687,10 @@ export class QuerysubControllerBase {
686
687
  }
687
688
  return result;
688
689
  }
690
+
691
+ public async debugGetValuePathCount(): Promise<number> {
692
+ return PathValueArchives.getValuePathCount();
693
+ }
689
694
  }
690
695
 
691
696
  export const QuerysubController = SocketFunction.register(
@@ -696,11 +701,13 @@ export const QuerysubController = SocketFunction.register(
696
701
  watch: { compress: true, },
697
702
  unwatch: { compress: true, },
698
703
  addCall: { compress: true, },
704
+ // NOTE: Most of these debug functions are pretty innocuous. A lot of it is actually already exposed, and other parts of it is fine. It's fine for the user to know what node a path is on. It's fine for them to know how many value paths there are.
699
705
  debugGetPathNodeIds: {},
700
706
  debugGetNodeSpecs: {},
701
707
  debugGetSingleReadNode: {},
702
708
  getModulePath: {},
703
709
  getDevFunctionSpecFromCall: {},
710
+ debugGetValuePathCount: {},
704
711
  }),
705
712
  () => ({
706
713
 
@@ -110,7 +110,7 @@ async function runGenesisJoinIteration(config?: { force?: boolean }) {
110
110
  });
111
111
  }
112
112
 
113
- 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)}`));
113
+ console.log(magenta(`Joining ${formatNumber(usedFiles.length)} => ${transaction.createFiles.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)}`));
114
114
 
115
115
  return [transaction];
116
116
  });
@@ -0,0 +1,68 @@
1
+ import { qreact } from "../4-dom/qreact";
2
+ import { css } from "../4-dom/css";
3
+ import { Querysub, QuerysubController, querysubNodeId } from "../4-querysub/QuerysubController";
4
+ import { isCurrentUserSuperUser } from "../user-implementation/userData";
5
+ import { t } from "../2-proxy/schema2";
6
+
7
+ module.hotreload = true;
8
+
9
+ export class ValuePathWarning extends qreact.Component {
10
+ state = t.state({
11
+ count: t.number
12
+ });
13
+ componentDidMount() {
14
+ Querysub.onCommitFinished(async () => {
15
+ let nodeId = await querysubNodeId();
16
+ if (!nodeId) return;
17
+ let count = await QuerysubController.nodes[nodeId].debugGetValuePathCount();
18
+ Querysub.localCommit(() => this.state.count = count);
19
+ });
20
+ }
21
+ renderBase() {
22
+ if (!isCurrentUserSuperUser()) return undefined;
23
+ let count = this.state.count;
24
+ if (!count) return undefined;
25
+
26
+ if (count <= 150) {
27
+ return <div className={css.hbox(4)}>
28
+ <span>📄</span>
29
+ <span>{count}</span>
30
+ </div>;
31
+ }
32
+
33
+ let bgClass = "";
34
+ let flashing = false;
35
+ if (count > 500) {
36
+ bgClass = css.hsl(0, 80, 60);
37
+ flashing = true;
38
+ } else if (count > 300) {
39
+ bgClass = css.hsl(0, 80, 60);
40
+ } else {
41
+ bgClass = css.hsl(50, 100, 40);
42
+ }
43
+
44
+ let animClassName = "ValuePathWarning-flash";
45
+ return <div className={css.hbox(4).pad2(6, 2) + " " + bgClass + (flashing ? " " + animClassName : "")}>
46
+ <span>📄</span>
47
+ <span>{count}</span>
48
+ <span>warning: high number of files, join is likely not running</span>
49
+ {flashing && <style>{`
50
+ @keyframes ${animClassName}-anim {
51
+ 0%, 100% { background-color: hsl(0, 80%, 60%); }
52
+ 50% { background-color: hsl(0, 80%, 30%); }
53
+ }
54
+ .${animClassName} {
55
+ animation: ${animClassName}-anim 0.6s infinite;
56
+ }
57
+ `}</style>}
58
+ </div>;
59
+ }
60
+ render() {
61
+ try {
62
+ return this.renderBase();
63
+ } catch (error) {
64
+ console.error("Error in rendering ValuePathWarning:", error);
65
+ return undefined;
66
+ }
67
+ }
68
+ }
@@ -9,7 +9,7 @@ import { cacheArgsEqual, cacheLimited, cacheWeak, lazy } from "socket-function/s
9
9
  import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
10
10
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
11
11
  import { magenta, yellow } from "socket-function/src/formatting/logColors";
12
- import { Unit, getAllUnits, Reader, createOffsetReader, SearchParams, IndexedLogResults } from "./BufferIndexHelpers";
12
+ import { Unit, getAllUnits, Reader, BufferReader, createOffsetReader, SearchParams, IndexedLogResults } from "./BufferIndexHelpers";
13
13
  import { createMatchesPattern, getSearchUnits } from "./bufferSearchFindMatcher";
14
14
  import { UnitSet } from "./BufferUnitSet";
15
15
  import { BufferUnitIndex } from "./BufferUnitIndex";
@@ -409,6 +409,118 @@ export class BufferIndex {
409
409
  results.blockSearchTime += Date.now() - blockSearchTimeStart;
410
410
  }
411
411
 
412
+ // Returns the block indices in `index` that could contain a match for `query`.
413
+ // Mirrors the AND/OR scan inside findLocal and BufferUnitIndex.find but does
414
+ // not read or scan data — only the index. Used by MCPIndexedLogs to split
415
+ // search into "find candidate blocks" then "scan those blocks".
416
+ @measureFnc
417
+ public static async findMatchingBlocks(config: {
418
+ index: Buffer;
419
+ dataReader: Reader;
420
+ query: Buffer;
421
+ disableWildCards?: boolean;
422
+ results: IndexedLogResults;
423
+ }): Promise<number[]> {
424
+ let { index, dataReader, query, results } = config;
425
+ let allSearchUnits = getSearchUnits(query, !!config.disableWildCards);
426
+ if (allSearchUnits.length === 0) return [];
427
+
428
+ let type = index[0];
429
+ if (!type) {
430
+ type = (await dataReader.read(0, 1))?.[0];
431
+ }
432
+
433
+ if (type === STREAM_TYPE) {
434
+ if (index.length === 0) {
435
+ index = await BufferIndex.rebuildLocalIndexFromData(dataReader);
436
+ if (index.length === 0) return [];
437
+ }
438
+ index = await BufferIndex.fixPartialIndex({ index, dataReader, results });
439
+
440
+ let decoded = decodeTypeHeader(index);
441
+ if (!decoded) return [];
442
+ let indexEntries = await indexStreamerType.getAllBlocks(decoded.data);
443
+
444
+ let matching: number[] = [];
445
+ for (let i = 0; i < indexEntries.length; i++) {
446
+ let blockIndexData = indexEntries[i];
447
+ for (let or of allSearchUnits) {
448
+ let hasAllUnits = true;
449
+ for (let unit of or) {
450
+ if (!UnitSet.has(blockIndexData, unit)) {
451
+ hasAllUnits = false;
452
+ break;
453
+ }
454
+ }
455
+ if (hasAllUnits) {
456
+ matching.push(i);
457
+ break;
458
+ }
459
+ }
460
+ }
461
+ return matching;
462
+ } else if (type === BULK_TYPE) {
463
+ let candidateSet = new Set<number>();
464
+ for (let or of allSearchUnits) {
465
+ let blocks = BufferUnitIndex.findBlocks({ units: or, index });
466
+ for (let b of blocks) candidateSet.add(b);
467
+ }
468
+ let result = Array.from(candidateSet);
469
+ sort(result, x => x);
470
+ return result;
471
+ }
472
+ return [];
473
+ }
474
+
475
+ // Loads and returns just the buffers in a single block, by index.
476
+ // For STREAM_TYPE, blockIndex matches the index entry order produced by
477
+ // findMatchingBlocks. For BULK_TYPE, blockIndex matches BufferUnitIndex's
478
+ // internal block ordering (also what findMatchingBlocks returns).
479
+ @measureFnc
480
+ public static async getBlockBuffers(config: {
481
+ index: Buffer;
482
+ dataReader: Reader;
483
+ blockIndex: number;
484
+ }): Promise<Buffer[]> {
485
+ let { index, dataReader, blockIndex } = config;
486
+
487
+ let type = index[0];
488
+ if (!type) {
489
+ type = (await dataReader.read(0, 1))?.[0];
490
+ }
491
+
492
+ if (type === STREAM_TYPE) {
493
+ let headerBuf = await dataReader.read(0, 5);
494
+ if (headerBuf.length < 5) return [];
495
+ let headerSize = headerBuf.readInt32LE(1);
496
+ let totalHeaderSize = 1 + 4 + headerSize;
497
+ let dataWithoutHeaderReader = createOffsetReader(dataReader, totalHeaderSize);
498
+
499
+ let blocks = await dataStreamerType.getBlockRange({
500
+ reader: dataWithoutHeaderReader,
501
+ startIndex: blockIndex,
502
+ endIndex: blockIndex + 1,
503
+ });
504
+ if (blocks.length === 0) return [];
505
+ try {
506
+ let decompressed = CompressedStream.decode(blocks[0]);
507
+ return await blockStreamerType.getAllBlocks(decompressed);
508
+ } catch (e) {
509
+ return [];
510
+ }
511
+ } else if (type === BULK_TYPE) {
512
+ let obj = await BufferUnitIndex.getBlock(dataReader, blockIndex);
513
+ let blockReader = new BufferReader(obj.block);
514
+ let bufferCount = await BufferUnitIndex.getBufferCountFromBlock(blockReader);
515
+ let result: Buffer[] = [];
516
+ for (let i = 0; i < bufferCount; i++) {
517
+ result.push(await BufferUnitIndex.getBufferFromBlock(blockReader, i));
518
+ }
519
+ return result;
520
+ }
521
+ return [];
522
+ }
523
+
412
524
  @measureFnc
413
525
  public static async find(config: {
414
526
  index: Buffer;
@@ -611,7 +611,7 @@ export class BufferUnitIndex {
611
611
  }
612
612
 
613
613
  @measureFnc
614
- private static findBlocks(config: {
614
+ public static findBlocks(config: {
615
615
  units: number[];
616
616
  index: Buffer;
617
617
  }): number[] {
@@ -679,12 +679,12 @@ export class BufferUnitIndex {
679
679
  }
680
680
 
681
681
 
682
- private static async getBlockCount(reader: Reader): Promise<number> {
682
+ public static async getBlockCount(reader: Reader): Promise<number> {
683
683
  const headerBuffer = await reader.read(4, 8);
684
684
  return headerBuffer.readUInt32LE(0);
685
685
  }
686
686
 
687
- private static async getBlock(reader: Reader, blockIndex: number, debugOffsets?: {
687
+ public static async getBlock(reader: Reader, blockIndex: number, debugOffsets?: {
688
688
  startOffset: number;
689
689
  endOffset: number;
690
690
  }): Promise<{
@@ -715,7 +715,7 @@ export class BufferUnitIndex {
715
715
  return (await reader.read(0, 4)).readUInt32LE(0);
716
716
  }
717
717
 
718
- private static async getBufferFromBlock(reader: Reader, bufferIndex: number): Promise<Buffer> {
718
+ public static async getBufferFromBlock(reader: Reader, bufferIndex: number): Promise<Buffer> {
719
719
  let startOffset = (await reader.read(4 + bufferIndex * 4, 4)).readUInt32LE(0);
720
720
  let endOffset = (await reader.read(4 + (bufferIndex + 1) * 4, 4)).readUInt32LE(0);
721
721
  let buffer = await reader.read(startOffset, endOffset - startOffset);
@@ -1,4 +1,4 @@
1
- import { lazy } from "socket-function/src/caching";
1
+ import { cache, lazy } from "socket-function/src/caching";
2
2
  import { Archives, nestArchives } from "../../../-a-archives/archives";
3
3
  import { deepCloneJSON, keyByArray, nextId, sort, timeInHour, timeInMinute, timeInSecond, timeoutToUndefinedSilent } from "socket-function/src/misc";
4
4
  import { BufferIndex } from "./BufferIndex";
@@ -27,6 +27,7 @@ import { getAllNodeIds } from "../../../-f-node-discovery/NodeDiscovery";
27
27
  import { NodeCapabilitiesController } from "../../../-g-core-values/NodeCapabilities";
28
28
  import { getLoggers2Async } from "../diskLogger";
29
29
  import { watchAllValues } from "../errorNotifications2/logWatcher";
30
+ import { wrapArchivesWithCache } from "../../../-a-archives/archiveCache";
30
31
 
31
32
  // Ensure it's available so that the controller is listening on all servers
32
33
  watchAllValues;
@@ -113,6 +114,23 @@ export class IndexedLogs<T> {
113
114
  });
114
115
  return archives;
115
116
  };
117
+ public debugGetCachedLogs = cache((config: {
118
+ type: "local" | "public";
119
+ }) => {
120
+ let usePublic = config.type === "public";
121
+ let archives = usePublic ? getArchivesBackblaze(getDomain()) : getArchivesHome(getDomain());
122
+ archives = nestArchives("final-indexed-logs/" + this.config.name, archives);
123
+ archives = wrapArchivesWithCache(archives);
124
+ archives = createArchivesMemoryCache(archives, {
125
+ maxSize: 1024 * 1024 * 1024 * 12,
126
+ maxCount: 1000 * 500,
127
+ fullyImmutable: true,
128
+ });
129
+ return archives;
130
+ });
131
+ public debugIsPublic() {
132
+ return isPublic();
133
+ }
116
134
  private getPublicLogs = lazy((): Archives => {
117
135
  return this.getPublicLogsBase(isPublic());
118
136
  });
@@ -845,9 +863,23 @@ class IndexedLogShim {
845
863
  public async hasLogger(name: string) {
846
864
  return loggerByName.has(name);
847
865
  }
866
+
867
+ public async hasPendingInRange(config: {
868
+ indexedLogsName: string;
869
+ startTime: number;
870
+ endTime: number;
871
+ }): Promise<boolean> {
872
+ let logs = getLogByName(config.indexedLogsName);
873
+ let paths = await logs.getPaths({
874
+ startTime: config.startTime,
875
+ endTime: config.endTime,
876
+ only: "local",
877
+ });
878
+ return paths.some(p => p.logCount === undefined);
879
+ }
848
880
  }
849
881
 
850
- const IndexedLogShimController = SocketFunction.register(
882
+ export const IndexedLogShimController = SocketFunction.register(
851
883
  "IndexedLogShim-019c87b7-73ca-72ec-91b3-2d45ebb616cd",
852
884
  new IndexedLogShim(),
853
885
  () => ({
@@ -862,6 +894,9 @@ const IndexedLogShimController = SocketFunction.register(
862
894
  },
863
895
  hasLogger: {
864
896
  hooks: [assertIsManagementUser]
897
+ },
898
+ hasPendingInRange: {
899
+ hooks: [assertIsManagementUser]
865
900
  }
866
901
  })
867
902
  );