querysub 0.14.0 → 0.16.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -2,7 +2,7 @@ import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { getArchives } from "../-a-archives/archives";
3
3
  import { getDomain, isDevDebugbreak, isNoNetwork } from "../config";
4
4
  import { measureWrap } from "socket-function/src/profiling/measure";
5
- import { isNode, sha256Hash, timeInMinute, timeInSecond } from "socket-function/src/misc";
5
+ import { isNode, sha256Hash, throttleFunction, timeInMinute, timeInSecond } from "socket-function/src/misc";
6
6
  import { errorToUndefined, errorToUndefinedSilent, ignoreErrors, logErrors, timeoutToUndefined, timeoutToUndefinedSilent } from "../errors";
7
7
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
8
8
  import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
@@ -21,9 +21,9 @@ import { getPublicIP } from "../misc/networking";
21
21
 
22
22
  import dns from "dns/promises";
23
23
  import { isDefined } from "../misc";
24
- import { diskLog } from "../diagnostics/logs/diskLogger";
24
+ import { diskLog, noDiskLogPrefix } from "../diagnostics/logs/diskLogger";
25
25
 
26
- let HEARTBEAT_INTERVAL = timeInMinute * 5;
26
+ let HEARTBEAT_INTERVAL = timeInMinute * 2;
27
27
  // Interval which we check other heartbeats
28
28
  let CHECK_INTERVAL = HEARTBEAT_INTERVAL;
29
29
  // If the heartbeat is older than thing, it fails the dead check
@@ -37,6 +37,8 @@ let SUICIDE_HEARTBEAT_THRESHOLD = timeInMinute * 15;
37
37
 
38
38
  let CLIENTSIDE_POLL_RATE = timeInMinute * 5;
39
39
 
40
+ let API_AUDIT_RATE = timeInSecond * 5;
41
+
40
42
  let MEMORY_AUDIT_RATE = timeInMinute * 5;
41
43
  let MEMORY_AUDIT_COUNT = 3;
42
44
 
@@ -64,6 +66,9 @@ export function getOwnNodeId(): string {
64
66
  }
65
67
  return nodeId;
66
68
  }
69
+ export function getMountNodeId(): string | undefined | "" {
70
+ return SocketFunction.mountedNodeId;
71
+ }
67
72
  export function getOwnNodeIdAssert(): string {
68
73
  let nodeId = SocketFunction.mountedNodeId;
69
74
  if (!nodeId) {
@@ -154,19 +159,8 @@ function addNodeIdBase(nodeId: string) {
154
159
  allNodeIds2.add(nodeId);
155
160
  onNodesChanged();
156
161
  }
157
- function removeNodeIds(nodeIds: string[]) {
158
- diskLog("removeNodeIds", { nodeIds });
159
- nodeIds = nodeIds.filter(nodeId => allNodeIds2.has(nodeId));
160
- if (nodeIds.length === 0) return;
161
- for (let nodeId of nodeIds) {
162
- if (logging) {
163
- console.log(red(`Removed node directly ${nodeId}`));
164
- }
165
- allNodeIds2.delete(nodeId);
166
- }
167
- onNodesChanged();
168
- }
169
162
  function setNodeIds(nodeIds: string[]) {
163
+ nodeIds = nodeIds.filter(x => x !== SPECIAL_NODE_ID_FOR_UNMOUNTED_NODE);
170
164
  diskLog("setNodeIds", { nodeIds });
171
165
  // Also try all localhost ports, if we are in dev mode
172
166
  if (isNode() && isDevDebugbreak()) {
@@ -250,25 +244,18 @@ async function syncArchives() {
250
244
  async function runHeartbeatAuditLoop() {
251
245
  await onNodeDiscoveryReady();
252
246
  let deadCount = new Map<string, number>();
253
- await runInfinitePollCallAtStart(CHECK_INTERVAL, async () => {
247
+ // 90% of the normal interval, so we don't run at the same tmie as the other audit
248
+ await runInfinitePollCallAtStart(CHECK_INTERVAL * 0.9, async () => {
254
249
  if (shutdown) return;
255
250
  // Wait a bit longer, to try to prevent all nodes from synchronizing their audit times.
256
- await delay(HEARTBEAT_INTERVAL * Math.random() * 0.1);
251
+ await delay(CHECK_INTERVAL * Math.random() * 0.1);
257
252
  //console.log(magenta(`Auditing node list`));
258
253
 
259
254
  let deadTime = Date.now() - DEAD_THRESHOLD;
260
255
  let nodeIds = await archives().find("");
261
- // Should be unusual, but... if we find new nodes, add them, and let everyone else know?
262
- for (let nodeId of nodeIds) {
263
- if (!allNodeIds2.has(nodeId)) {
264
- console.log(yellow(`Found unexpected node, adding it and broadcasting it, ${nodeId}`));
265
- addNodeId(nodeId);
266
- for (let nodeId of allNodeIds2) {
267
- if (isOwnNodeId(nodeId)) continue;
268
- ignoreErrors(NodeDiscoveryController.nodes[nodeId].addNode(nodeId));
269
- }
270
- }
271
- }
256
+ // We spent the money checking the node list, so we might as well update it
257
+ setNodeIds(nodeIds);
258
+
272
259
  let removedNodeIds: string[] = [];
273
260
  for (let nodeId of nodeIds) {
274
261
  let lastTime = Number((await archives().get(nodeId))?.toString()) || 0;
@@ -291,62 +278,45 @@ async function runHeartbeatAuditLoop() {
291
278
  }
292
279
  }
293
280
 
294
- removeNodeIds(removedNodeIds);
295
-
296
- // Broadcast to all nodes the removal
297
- for (let nodeId of allNodeIds2) {
298
- if (isOwnNodeId(nodeId)) continue;
299
- ignoreErrors(NodeDiscoveryController.nodes[nodeId].removeNodes(removedNodeIds));
281
+ if (removedNodeIds.length > 0) {
282
+ void tellEveryoneNodesChanges();
300
283
  }
301
284
  });
302
285
  }
303
286
 
304
- async function isOutOfSync() {
305
- let auditNodes = shuffle(Array.from(allNodeIds2), Date.now()).slice(0, MEMORY_AUDIT_COUNT);
306
- for (let nodeId of auditNodes) {
307
- if (nodeId === getOwnNodeId()) continue;
308
-
309
- let stableWait = timeInSecond * 30;
310
- while (true) {
311
- let hash = await errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].getAllNodesHash());
312
- if (!hash) break;
313
- let ownHash = getAllNodesHash();
314
- if (hash === ownHash) break;
315
- await delay(stableWait);
316
- let hash2 = await errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].getAllNodesHash());
317
- if (!hash2) break;
318
- if (hash === hash2) {
319
- console.log(yellow(`Node discovery is out of sync, syncing with disk. Told by node ${nodeId}, our hash of ${ownHash} is wrong and should be ${hash2}`));
320
- return true;
321
- }
322
- // Otherwise, the hash changed, so... we maybe we just caught it while it was changing.
323
- // Wait less time, to try to catch up with the rate of change
324
- stableWait /= 2;
325
- if (stableWait < timeInSecond) {
326
- // It is changing so fast we better just sync with the disk
327
- console.log(yellow(`Node discovery is changing fast and likely out of sync, resyncing with disk. Told by node ${nodeId}`));
328
- return true;
329
- }
287
+ async function fastMemorySync(retries = 3) {
288
+ let checkNode = shuffle(Array.from(allNodeIds2).filter(x => x !== getOurNodeId()), Date.now()).slice(0, MEMORY_AUDIT_COUNT).at(0);
289
+ if (!checkNode) return;
290
+ let otherNodes = await errorToUndefinedSilent(NodeDiscoveryController.nodes[checkNode].getAllNodeIds());
291
+ if (!otherNodes) {
292
+ if (retries > 0) {
293
+ await fastMemorySync(retries - 1);
330
294
  }
295
+ return;
296
+ }
297
+ // If they are missing nodes that's fine. We constantly have extra nodes, and have to function correctly
298
+ // with extra nodes. However, if we are missing nodes, we'd prefer to have them quickly, so we should
299
+ // sync now.
300
+ let missingNodes = otherNodes.filter(nodeId => !allNodeIds2.has(nodeId));
301
+ if (missingNodes.length > 0) {
302
+ console.log(yellow(`Node list is missing nodes, resyncing node`), { checkNode, missingNodes, otherNodes });
303
+ await syncArchives();
331
304
  }
332
305
  }
333
306
 
334
307
  async function runMemoryAuditLoop() {
335
308
  await onNodeDiscoveryReady();
336
- runInfinitePoll(MEMORY_AUDIT_RATE, async () => {
337
- // In retrospec, reading all nodes every 5 minutes is fine. It costs about 0.035 USD
338
- // per month to do this, which... is a lot less than the server to run this will cost!
339
- //if (await isOutOfSync()) {
340
- await syncArchives();
341
- //}
342
- });
309
+ runInfinitePoll(MEMORY_AUDIT_RATE, syncArchives);
310
+ runInfinitePoll(API_AUDIT_RATE, fastMemorySync);
343
311
  }
344
312
 
345
313
  async function writeHeartbeat() {
346
314
  if (shutdown) return;
347
315
  let now = Date.now();
348
316
  console.log(green(`Writing heartbeat ${formatDateTime(now)}`));
349
- await archives().set(getOwnNodeId(), Buffer.from(now + ""));
317
+ let nodeId = getMountNodeId();
318
+ if (!nodeId) return;
319
+ await archives().set(nodeId, Buffer.from(now + ""));
350
320
  }
351
321
 
352
322
  async function runMainSyncLoops(discoveryReady: PromiseObj<void>) {
@@ -491,23 +461,22 @@ export async function nodeDiscoveryShutdown() {
491
461
  if (isServer()) {
492
462
  await archives().del(getOwnNodeId());
493
463
  }
494
- // Don't bother to remove the node from ourself, as we will be shutting down shortly
495
- // (but do bother to tell other nodes!)
496
- await Promise.allSettled(Array.from(allNodeIds2).map(async nodeId => {
497
- if (isOwnNodeId(nodeId)) return;
498
- await timeoutToUndefinedSilent(timeInSecond * 5, errorToUndefinedSilent(NodeDiscoveryController.nodes[nodeId].removeNodes([getOwnNodeId()])));
499
- }));
464
+ void tellEveryoneNodesChanges();
500
465
  }
466
+ const tellEveryoneNodesChanges = throttleFunction(1000, function tellEveryoneNodesChanges() {
467
+ for (let nodeId of allNodeIds2) {
468
+ if (isOwnNodeId(nodeId)) continue;
469
+ ignoreErrors(NodeDiscoveryController.nodes[nodeId].resyncNodes());
470
+ }
471
+ });
501
472
 
502
473
 
503
474
  class NodeDiscoveryControllerBase {
504
475
  public async addNode(nodeId: string) {
505
476
  addNodeId(nodeId);
506
477
  }
507
- public async removeNodes(nodeIds: string[]) {
508
- let callerId = SocketFunction.getCaller().nodeId;
509
- console.log(`remote removeNode request`, { callerId, nodeIds });
510
- removeNodeIds(nodeIds);
478
+ public async resyncNodes() {
479
+ await syncArchives();
511
480
  }
512
481
  public async getAllNodesHash(): Promise<string> {
513
482
  return getAllNodesHash();
@@ -529,7 +498,7 @@ const NodeDiscoveryController = SocketFunction.register(
529
498
  new NodeDiscoveryControllerBase(),
530
499
  () => ({
531
500
  addNode: { hooks: [requiresNetworkTrustHook] },
532
- removeNodes: { hooks: [requiresNetworkTrustHook] },
501
+ resyncNodes: { hooks: [requiresNetworkTrustHook] },
533
502
  getAllNodesHash: { hooks: [requiresNetworkTrustHook] },
534
503
  // Skip client hooks, so we don't block on authentication (IdentityController), as some of these functions
535
504
  // are needed for authentication to finish!
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { SocketFunction } from "socket-function/SocketFunction";
6
6
  import { SocketRegistered } from "socket-function/SocketFunctionTypes";
7
- import { errorToUndefined, errorToUndefinedSilent, timeoutToUndefined, timeoutToUndefinedSilent } from "../errors";
7
+ import { errorToUndefined, errorToUndefinedSilent, timeoutToUndefinedSilent } from "../errors";
8
8
  import { getAllNodeIds } from "../-f-node-discovery/NodeDiscovery";
9
9
  import { green, red, yellow } from "socket-function/src/formatting/logColors";
10
10
  import { shuffle } from "../misc/random";
@@ -55,7 +55,6 @@ class PathValueCommitter {
55
55
  // Returns true if all the writes are accepted by some node (this should almost always be the case)
56
56
  public commitValues(values: PathValue[], predictWrites: "predictWrites" | undefined): void {
57
57
  if (values.length === 0) return;
58
-
59
58
  ActionsHistory.OnWrite(values);
60
59
 
61
60
  let now = Date.now();
@@ -318,7 +318,10 @@ export class ClientWatcher {
318
318
  void Promise.resolve().finally(() => { throw e; });
319
319
  }
320
320
  this.activeWatchSpec = undefined;
321
- if (Date.now() - time > ClientWatcher.MAX_TRIGGER_TIME) {
321
+ if (
322
+ Date.now() - time > ClientWatcher.MAX_TRIGGER_TIME
323
+ && !isDevDebugbreak()
324
+ ) {
322
325
  stoppedEarly = true;
323
326
  break;
324
327
  }
@@ -12,7 +12,7 @@ import { getThreadKeyCert } from "../-a-auth/certs";
12
12
  import { ActionsHistory } from "../diagnostics/ActionsHistory";
13
13
  import { registerResource } from "../diagnostics/trackResources";
14
14
  import { ClientWatcher, WatchSpecData, clientWatcher } from "../1-path-client/pathValueClientWatcher";
15
- import { createPathValueProxy, getProxyPath, isValueProxy, isValueProxy2 } from "./pathValueProxy";
15
+ import { createPathValueProxy, getPathFromProxy, getProxyPath, isValueProxy, isValueProxy2 } from "./pathValueProxy";
16
16
  import { authorityStorage, compareTime, ReadLock, epochTime, getNextTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time, getCreatorId } from "../0-path-value-core/pathValueCore";
17
17
  import { runCodeWithDatabase, rawSchema } from "./pathDatabaseProxyBase";
18
18
  import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
@@ -23,7 +23,7 @@ import { LOCAL_DOMAIN_PATH } from "../0-path-value-core/NodePathAuthorities";
23
23
  import { registerPeriodic } from "../diagnostics/periodic";
24
24
  import { remoteWatcher } from "../1-path-client/RemoteWatcher";
25
25
  import { Schema2, Schema2Fncs } from "./schema2";
26
- import { getDomain } from "../config";
26
+ import { devDebugbreak, getDomain } from "../config";
27
27
 
28
28
  import type { CallSpec } from "../3-path-functions/PathFunctionRunner";
29
29
  import type { FunctionMetadata } from "../3-path-functions/syncSchema";
@@ -364,6 +364,25 @@ export type PermissionsChecker = {
364
364
 
365
365
  class SyncWatcherTag { }
366
366
 
367
+ function isRecursiveMatch(paths: Set<string>, path: string): boolean {
368
+ if (paths.size === 0) return false;
369
+ if (paths.has(path)) return true;
370
+ for (let otherPath of paths) {
371
+ if (path.includes(otherPath) || otherPath.includes(path)) {
372
+ return true;
373
+ }
374
+ }
375
+ return false;
376
+ }
377
+ function removeMatches(paths: Set<string>, path: string): void {
378
+ paths.delete(path);
379
+ for (let otherPath of paths) {
380
+ if (path.includes(otherPath) || otherPath.includes(path)) {
381
+ paths.delete(otherPath);
382
+ }
383
+ }
384
+ }
385
+
367
386
  export class PathValueProxyWatcher {
368
387
  public static BREAK_ON_READS = new Set<string>();
369
388
  public static BREAK_ON_WRITES = new Set<string>();
@@ -474,7 +493,8 @@ export class PathValueProxyWatcher {
474
493
  };
475
494
  public getCallback = (pathStr: string, syncParentKeys?: "parentKeys", readTransparent?: "readTransparent"): { value: unknown } | undefined => {
476
495
  if (PathValueProxyWatcher.BREAK_ON_READS.size > 0 && proxyWatcher.isAllSynced()) {
477
- if (PathValueProxyWatcher.BREAK_ON_READS.has(pathStr)) {
496
+ if (isRecursiveMatch(PathValueProxyWatcher.BREAK_ON_READS, pathStr)) {
497
+ const unwatch = () => PathValueProxyWatcher.BREAK_ON_READS.delete(pathStr); unwatch;
478
498
  debugger;
479
499
  }
480
500
  }
@@ -548,13 +568,15 @@ export class PathValueProxyWatcher {
548
568
 
549
569
  private setCallback = (pathStr: string, value: unknown, inRecursion = false, allowSpecial = false): void => {
550
570
  if (PathValueProxyWatcher.BREAK_ON_WRITES.size > 0 && proxyWatcher.isAllSynced()) {
551
- if (PathValueProxyWatcher.BREAK_ON_WRITES.has(pathStr)) {
571
+ if (isRecursiveMatch(PathValueProxyWatcher.BREAK_ON_WRITES, pathStr)) {
572
+ let unwatch = () => PathValueProxyWatcher.BREAK_ON_WRITES.delete(pathStr); unwatch;
552
573
  debugger;
553
574
  }
554
575
  }
555
576
 
556
577
  if (PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.size > 0 && proxyWatcher.isAllSynced()) {
557
- if (PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.has(pathStr)) {
578
+ if (isRecursiveMatch(PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES, pathStr)) {
579
+ let unwatch = () => PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.delete(pathStr); unwatch;
558
580
  PathValueProxyWatcher.BREAK_ON_CALL.add(
559
581
  getPathStr2(getCurrentCall().ModuleId, getCurrentCall().FunctionId)
560
582
  );
@@ -562,10 +584,9 @@ export class PathValueProxyWatcher {
562
584
  }
563
585
 
564
586
  if (PathValueProxyWatcher.LOG_WRITES_INCLUDES.size > 0 && proxyWatcher.isAllSynced()) {
565
- for (let log of PathValueProxyWatcher.LOG_WRITES_INCLUDES) {
566
- if (pathStr.includes(log)) {
567
- console.log(`Write path "${pathStr}" = ${value}`);
568
- }
587
+ if (isRecursiveMatch(PathValueProxyWatcher.LOG_WRITES_INCLUDES, pathStr)) {
588
+ let unwatch = () => PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(pathStr); unwatch;
589
+ console.log(`Write path "${pathStr}" = ${value}`);
569
590
  }
570
591
  }
571
592
  if (value === specialObjectWriteSymbol) {
@@ -597,15 +618,6 @@ export class PathValueProxyWatcher {
597
618
  throw new Error(`Tried to write a non-local path in a "noLocks" watcher, ${watcher.debugName}, path ${pathStr}`);
598
619
  }
599
620
 
600
- if (!pathStr.startsWith(LOCAL_DOMAIN_PATH)) {
601
- // Copy the value, to ensure any proxy values aren't attempted to be written
602
- // to the database. A bit slower, but... it should be fine. We COULD do this
603
- // later on, but this makes it easier to attribute lag and errors to the original source.
604
- // - If we had a proxy, we WOULD copy it eventually, but we would copy it later,
605
- // which would cause an error, because the reader wouldn't be trackable.
606
- value = deepCloneCborx(value);
607
- }
608
-
609
621
  if (watcher.permissionsChecker) {
610
622
  if (!watcher.permissionsChecker.checkPermissions(pathStr).allowed) {
611
623
  if (value !== specialObjectWriteValue) {
@@ -713,6 +725,14 @@ export class PathValueProxyWatcher {
713
725
  };
714
726
  addWrites(pathStr, value);
715
727
  } else {
728
+ if (!pathStr.startsWith(LOCAL_DOMAIN_PATH)) {
729
+ // Copy the value, to ensure any proxy values aren't attempted to be written
730
+ // to the database. A bit slower, but... it should be fine. We COULD do this
731
+ // later on, but this makes it easier to attribute lag and errors to the original source.
732
+ // - If we had a proxy, we WOULD copy it eventually, but we would copy it later,
733
+ // which would cause an error, because the reader wouldn't be trackable.
734
+ value = deepCloneCborx(value);
735
+ }
716
736
  watcher.pendingWrites.set(pathStr, value);
717
737
  }
718
738
  };
@@ -1705,6 +1725,23 @@ export class PathValueProxyWatcher {
1705
1725
  watcher.pendingUnsyncedParentAccesses.delete(path);
1706
1726
  }
1707
1727
  }
1728
+
1729
+ public debug_breakOnWrite(proxy: unknown | (() => unknown)) {
1730
+ let path = typeof proxy === "function" ? getProxyPath(proxy as any) : getPathFromProxy(proxy);
1731
+ if (!path) {
1732
+ console.error(`Value is not a proxy, and so cannot get path. Either pass a proxy, or a getter for a value.`);
1733
+ return;
1734
+ }
1735
+ PathValueProxyWatcher.BREAK_ON_WRITES.add(path);
1736
+ }
1737
+ public debug_logOnWrite(proxy: unknown | (() => unknown)) {
1738
+ let path = typeof proxy === "function" ? getProxyPath(proxy as any) : getPathFromProxy(proxy);
1739
+ if (!path) {
1740
+ console.error(`Value is not a proxy, and so cannot get path. Either pass a proxy, or a getter for a value.`);
1741
+ return;
1742
+ }
1743
+ PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
1744
+ }
1708
1745
  }
1709
1746
 
1710
1747
  // gitHash => domainName => moduleId => schema
@@ -1730,13 +1767,17 @@ function getBaseMatchingSchema(pathStr: string): {
1730
1767
  schema: Schema2;
1731
1768
  nestedPath: string[];
1732
1769
  } | undefined {
1733
- let gitHash = getCurrentCallObj()?.fnc.gitRef || "ambient";
1770
+ let schemaForHash = (
1771
+ schemas.get(getCurrentCallObj()?.fnc.gitRef || "ambient")
1772
+ // Default to the ambient schema for clientside calls.
1773
+ || schemas.get("ambient")
1774
+ );
1734
1775
 
1735
1776
  if (_noAtomicSchema) return undefined;
1736
1777
  if (getPathIndex(pathStr, 3) !== "Data") return undefined;
1737
1778
  let domain = getPathIndex(pathStr, 0)!;
1738
1779
  let moduleId = getPathIndex(pathStr, 2)!;
1739
- let schema = schemas.get(gitHash)?.get(domain)?.get(moduleId);
1780
+ let schema = schemaForHash?.get(domain)?.get(moduleId);
1740
1781
  if (!schema) return undefined;
1741
1782
  let nestedPathStr = getPathSuffix(pathStr, 4);
1742
1783
  let nestedPath = getPathFromStr(nestedPathStr);
@@ -174,7 +174,7 @@ export function writeFunctionCall(config: {
174
174
  return callSpec;
175
175
  }
176
176
 
177
- export function parseArgs(call: CallSpec) {
177
+ export function parseArgs(call: CallSpec): unknown[] {
178
178
  return decodeCborx(Buffer.from(call.argsEncoded, "base64"));
179
179
  }
180
180
 
@@ -177,6 +177,10 @@ export class Querysub {
177
177
  public static unwatchCurrentFnc = () => PathValueProxyWatcher.BREAK_ON_CALL.delete(
178
178
  getPathStr2(getCurrentCall().ModuleId, getCurrentCall().FunctionId)
179
179
  );
180
+ public static watchWrites = (value: unknown) => proxyWatcher.debug_breakOnWrite(value);
181
+ public static debugWrites = (value: unknown) => proxyWatcher.debug_breakOnWrite(value);
182
+ public static breakOnWrite = (value: unknown) => proxyWatcher.debug_breakOnWrite(value);
183
+ public static logWrites = (value: unknown) => proxyWatcher.debug_logOnWrite(value);
180
184
 
181
185
  public static watchUnsyncedComponents = () => qreact.watchUnsyncedComponents();
182
186
  public static watchAnyUnsyncedComponents = () => qreact.watchUnsyncedComponents().size > 0;
@@ -317,12 +321,6 @@ export class Querysub {
317
321
  return isSynced(value);
318
322
  }
319
323
 
320
- public static watchWrites(get: () => unknown) {
321
- let path = getProxyPath(get);
322
- if (!path) throw new Error(`Failed to get path`);
323
- PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
324
- }
325
-
326
324
  public static assertDomainAllowed(path: string) {
327
325
  let domain = getPathIndexAssert(path, 0);
328
326
  if (!Querysub.trustedDomains.has(domain)) {
@@ -875,4 +873,4 @@ setImmediate(() => {
875
873
  registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
876
874
  registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
877
875
 
878
- (global as any).Querysub = Querysub;
876
+ (globalThis as any).Querysub = Querysub;
@@ -486,7 +486,7 @@ export async function getCallWrites(config: {
486
486
  // throw new Error(`Module not found ${call.DomainName}.${call.ModuleId}`);
487
487
  // }
488
488
  let moduleObject = domainObject.PathFunctionRunner[call.ModuleId];
489
- if (!(call.FunctionId in moduleObject.Sources)) {
489
+ if (!(call.FunctionId in moduleObject.Sources) && Querysub.isAllSynced()) {
490
490
  throw new Error(`Function not found ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
491
491
  }
492
492
  let functionSpec = atomicObjectRead(moduleObject.Sources[call.FunctionId]);
package/src/config.ts CHANGED
@@ -39,9 +39,14 @@ export function devDebugbreak() {
39
39
  debugbreak(2);
40
40
  debugger;
41
41
  }
42
+ let debuggedEnabled = false;
42
43
  export function isDevDebugbreak() {
43
- return yargObj.debugbreak;
44
+ return yargObj.debugbreak || !isNode() && location.hostname.startsWith("noproxy.") || debuggedEnabled;
44
45
  }
46
+ export function enableDebugging() {
47
+ debuggedEnabled = true;
48
+ }
49
+ (globalThis as any).enableDebugging = enableDebugging;
45
50
 
46
51
 
47
52
  export function authorityRaw() {
@@ -32,8 +32,11 @@ import { setRecord } from "../-b-authorities/dnsAuthority";
32
32
  import tls from "tls";
33
33
  import net from "net";
34
34
  import { ButtonSelector } from "../library-components/ButtonSelector";
35
- import { assertIsManagementUser } from "./managementPages";
35
+ import { assertIsManagementUser, managementPageURL } from "./managementPages";
36
36
  import { SocketRegistered } from "socket-function/SocketFunctionTypes";
37
+ import { ATag } from "../library-components/ATag";
38
+ import { filterURL, selectedNodeId } from "./logs/DiskLoggerPage";
39
+ import { getSyncedController } from "../library-components/SyncedController";
37
40
 
38
41
 
39
42
  type NodeData = {
@@ -162,7 +165,7 @@ export class NodeViewer extends qreact.Component {
162
165
 
163
166
  let tables: { [key: string]: NodeData[] } = {};
164
167
  tables["PathValueServer"] = [];
165
- tables["FunctionRunner"] = [];
168
+ tables["querysub-function"] = [];
166
169
  tables["Querysub"] = [];
167
170
  for (let datum of nodeDatas) {
168
171
  if (datum.live_authorityPaths?.length) {
@@ -170,7 +173,7 @@ export class NodeViewer extends qreact.Component {
170
173
  } else if (datum.live_exposedControllers?.includes("QuerysubController-6db5ef05-7563-4473-a440-2f64f03fe6ef")) {
171
174
  tables["Querysub"].push(datum);
172
175
  } else if (datum.live_entryPoint?.endsWith("function.js")) {
173
- tables["FunctionRunner"].push(datum);
176
+ tables["querysub-function"].push(datum);
174
177
  } else {
175
178
  let entryName = datum.live_entryPoint?.replaceAll("\\", "/").split("/").pop() || "Unknown";
176
179
  tables[entryName] = tables[entryName] || [];
@@ -179,7 +182,7 @@ export class NodeViewer extends qreact.Component {
179
182
  }
180
183
 
181
184
  let builtinGroups = {
182
- "Default": ["buttons", "devToolsURL", "ip", "uptime", "Heap", "All Memory", "Blocking Lag", "port", "threadId", "machineId", "apiError", "live_entryPoint"],
185
+ "Default": ["buttons", "devToolsURL", "nodeId", "ip", "uptime", "Heap", "All Memory", "Blocking Lag", "port", "threadId", "machineId", "apiError", "live_entryPoint"],
183
186
  };
184
187
  // Column => group
185
188
  let builtInGroupsLookup = new Map<string, string>();
@@ -256,6 +259,23 @@ export class NodeViewer extends qreact.Component {
256
259
  );
257
260
  },
258
261
  },
262
+ nodeId: {
263
+ formatter: (obj) => {
264
+ let str = String(obj);
265
+ if (str.startsWith("http")) return formatValue(obj);
266
+ https://noproxy.querysub.com:1111/?page=test&showingmanagement&managementpage=DiskLoggerPage&nodeId=b4d19fba5f79d48c3.b68f46ffaffad0636.querysub.com%3A41863&filter=%22%5C%22__nodeId%5C%22%3A%5C%22b654f522bf3a67c49.b68f46ffaffad0636.querysub.com%3A33473%5C%22%22
267
+
268
+ return (
269
+ <ATag values={[
270
+ managementPageURL.getOverride("DiskLoggerPage"),
271
+ selectedNodeId.getOverride(str),
272
+ filterURL.getOverride(`"__nodeId":"${str}"`),
273
+ ]}>
274
+ Logs
275
+ </ATag>
276
+ );
277
+ },
278
+ },
259
279
  ip: {},
260
280
  ...x.table?.columns,
261
281
  //capabilities: null,
@@ -500,4 +520,6 @@ export const NodeViewerController = SocketFunction.register(
500
520
  // Auto expose, for getMiscInfo. This is protected via assertIsManagementUser anyways.
501
521
  //noAutoExpose: true,
502
522
  }
503
- );
523
+ );
524
+
525
+ export let nodeViewerController = getSyncedController(NodeViewerController);
@@ -18,7 +18,7 @@ import { LogType } from "../errorLogs/ErrorLogCore";
18
18
  import { canHaveChildren } from "socket-function/src/types";
19
19
  import { measureBlock } from "socket-function/src/profiling/measure";
20
20
  import { binarySearchBasic, list, sort, timeInDay, timeInHour } from "socket-function/src/misc";
21
- import { NodeViewerController } from "../NodeViewer";
21
+ import { NodeViewerController, nodeViewerController } from "../NodeViewer";
22
22
  import { red } from "socket-function/src/formatting/logColors";
23
23
  import { errorMessage, formatValue, genericFormat, warnMessage } from "../../5-diagnostics/GenericFormat";
24
24
  import { hslToRGB } from "socket-function/src/formatting/colors";
@@ -51,9 +51,9 @@ import { TimeRangeSelector } from "../../library-components/TimeRangeSelector";
51
51
  module.hotreload = true;
52
52
  module.hotreload = true;
53
53
 
54
- let selectedNodeId = new URLParam("nodeId", "");
54
+ export let selectedNodeId = new URLParam("nodeId", "");
55
55
 
56
- let filterURL = new URLParam("filter", "");
56
+ export let filterURL = new URLParam("filter", "");
57
57
  let sortOldestFirst = new URLParam("oldestFirst", false);
58
58
  let selectedFields = new URLParam("selectedFields", "");
59
59
 
@@ -426,12 +426,7 @@ const processLogs = cacheShallowConfigArgEqual((config: {
426
426
  return true;
427
427
  })).filter(x => x.length > 0);
428
428
  function logMatches(filter: string, log: LogObj) {
429
- for (let [key, value] of Object.entries(log)) {
430
- if (key.toLowerCase().includes(filter)) return true;
431
- if (canHaveChildren(value) && JSON.stringify(value).toLowerCase().includes(filter)) return true;
432
- if (String(value).toLowerCase().includes(filter)) return true;
433
- }
434
- return false;
429
+ return JSON.stringify(log).toLowerCase().includes(filter);
435
430
  }
436
431
  logMatchesFilter = function logMatchesFilter(log: LogObj) {
437
432
  return (
@@ -567,6 +562,4 @@ export const DiskLoggerController = SocketFunction.register(
567
562
  }
568
563
  );
569
564
 
570
- let diskLoggerController = getSyncedController(DiskLoggerController);
571
-
572
- let nodeViewerController = getSyncedController(NodeViewerController);
565
+ let diskLoggerController = getSyncedController(DiskLoggerController);