querysub 0.324.0 → 0.326.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 (29) hide show
  1. package/package.json +3 -2
  2. package/src/-a-archives/archiveCache.ts +2 -1
  3. package/src/-a-auth/certs.ts +2 -1
  4. package/src/-c-identity/IdentityController.ts +1 -0
  5. package/src/-d-trust/NetworkTrust2.ts +26 -27
  6. package/src/-e-certs/EdgeCertController.ts +13 -4
  7. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +9 -5
  8. package/src/4-querysub/Querysub.ts +3 -1
  9. package/src/diagnostics/logs/FastArchiveAppendable.ts +5 -3
  10. package/src/diagnostics/logs/FastArchiveViewer.tsx +43 -35
  11. package/src/diagnostics/logs/LogViewer2.tsx +35 -34
  12. package/src/diagnostics/logs/TimeRangeSelector.tsx +18 -2
  13. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +171 -34
  14. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +13 -7
  15. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +16 -4
  16. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +78 -0
  17. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +27 -83
  18. package/src/diagnostics/managementPages.tsx +2 -1
  19. package/src/functional/SocketChannel.ts +5 -1
  20. package/src/library-components/ATag.tsx +1 -0
  21. package/src/library-components/SyncedController.ts +14 -3
  22. package/src/library-components/SyncedControllerLoadingIndicator.tsx +3 -2
  23. package/src/library-components/URLParam.ts +35 -5
  24. package/src/library-components/icons.tsx +3 -0
  25. package/src/library-components/niceStringify.ts +1 -1
  26. package/src/library-components/urlResetGroups.ts +14 -0
  27. package/src/misc/formatJSX.tsx +7 -1
  28. package/src/server.ts +2 -1
  29. package/testEntry2.ts +16 -5
@@ -4,6 +4,8 @@ import { getAllNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery"
4
4
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
5
5
  import { errorToUndefinedSilent } from "../errors";
6
6
  import { assertIsManagementUser } from "../diagnostics/managementPages";
7
+ import { IdentityController_getCurrentReconnectNodeId, IdentityController_getMachineId } from "../-c-identity/IdentityController";
8
+ import { blue } from "socket-function/src/formatting/logColors";
7
9
 
8
10
  export class SocketChannel<T> {
9
11
  constructor(private globalUniqueChannelName: string) { }
@@ -34,7 +36,9 @@ export class SocketChannel<T> {
34
36
  private remoteWatchers = new Set<string>();
35
37
  public async _internal_watchMessages() {
36
38
  let caller = SocketFunction.getCaller();
39
+ let callerNodeId = IdentityController_getCurrentReconnectNodeId();
37
40
  this.remoteWatchers.add(caller.nodeId);
41
+ console.info(blue(`New watcher for channel ${this.globalUniqueChannelName} from ${caller.nodeId} (${callerNodeId})`));
38
42
  SocketFunction.onNextDisconnect(caller.nodeId, () => {
39
43
  this.remoteWatchers.delete(caller.nodeId);
40
44
  });
@@ -44,7 +48,7 @@ export class SocketChannel<T> {
44
48
  void Array.from(this.remoteWatchers).map(async (nodeId) => {
45
49
  try {
46
50
  await this.controller.nodes[nodeId]._internal_onMessage(message);
47
- } catch {
51
+ } catch (e) {
48
52
  this.remoteWatchers.delete(nodeId);
49
53
  }
50
54
  });
@@ -2,6 +2,7 @@ import preact from "preact";
2
2
  import { css, isNode } from "typesafecss";
3
3
  import { URLParam, parseSearchString, encodeSearchString } from "./URLParam";
4
4
  import { qreact } from "../4-dom/qreact";
5
+ import { niceStringify } from "../niceStringify";
5
6
 
6
7
  export type URLOverride<T = unknown> = {
7
8
  param: URLParam<T>;
@@ -40,11 +40,11 @@ onHotReload(() => {
40
40
  export function syncedIsAnyLoading() {
41
41
  return Querysub.fastRead(() => {
42
42
  for (let controllerId in syncedData()) {
43
- for (let fncs of Object.values(syncedData()[controllerId])) {
44
- for (let fnc of Object.values(fncs)) {
43
+ for (let [nodeId, fncs] of Object.entries(syncedData()[controllerId])) {
44
+ for (let [fncName, fnc] of Object.entries(fncs)) {
45
45
  for (let obj of Object.values(fnc)) {
46
46
  if (atomic(obj.promise)) {
47
- return true;
47
+ return `${fncName} (on ${nodeId})`;
48
48
  }
49
49
  }
50
50
  }
@@ -109,6 +109,7 @@ export function getSyncedController<T extends SocketRegistered>(
109
109
  resetAll(): void;
110
110
  refreshAll(): void;
111
111
  isAnyLoading(): boolean;
112
+ base: T;
112
113
  } {
113
114
  if (isNode()) {
114
115
  let result = cache((nodeId: string) => {
@@ -160,6 +161,7 @@ export function getSyncedController<T extends SocketRegistered>(
160
161
  result.isAnyLoading = () => {
161
162
  notAllowedOnServer();
162
163
  };
164
+ result.base = controller;
163
165
  return result;
164
166
  }
165
167
  let id = nextId();
@@ -307,11 +309,19 @@ export function getSyncedController<T extends SocketRegistered>(
307
309
  obj.promise = undefined;
308
310
  obj.invalidated = true;
309
311
  call(...args);
312
+ // Assign to itself, to preset the type assumptions typescript makes (otherwise we get an error below)
313
+ obj = obj as any;
310
314
  let promise = atomic(obj.promise);
311
315
  if (!promise) {
312
316
  debugger;
313
317
  throw new Error(`Impossible, called function, but promise is not found for ${fncName}`);
314
318
  }
319
+ // Don't cache promise calls
320
+ void promise.finally(() => {
321
+ if (obj.promise === promise) {
322
+ obj.promise = undefined;
323
+ }
324
+ });
315
325
  return promise;
316
326
  });
317
327
  };
@@ -404,5 +414,6 @@ export function getSyncedController<T extends SocketRegistered>(
404
414
  }
405
415
  });
406
416
  };
417
+ result.base = controller;
407
418
  return result;
408
419
  }
@@ -7,7 +7,8 @@ export class SyncedControllerLoadingIndicator extends qreact.Component {
7
7
  timeLastLoaded = Date.now();
8
8
  lastWasLoaded = false;
9
9
  render() {
10
- if (!syncedIsAnyLoading()) {
10
+ let loadingPath = syncedIsAnyLoading();
11
+ if (!loadingPath) {
11
12
  if (!this.lastWasLoaded) {
12
13
  console.log(`Loaded all SyncedController calls in ${formatTime(Date.now() - this.timeLastLoaded)}`);
13
14
  }
@@ -33,7 +34,7 @@ export class SyncedControllerLoadingIndicator extends qreact.Component {
33
34
  .borderRadius("50%")
34
35
  + " " + spinAnimationClass
35
36
  }></div>
36
- <span>Syncing data...</span>
37
+ <span title={loadingPath}>Syncing data...</span>
37
38
  <style>
38
39
  {`
39
40
  @keyframes ${spinAnimationClass} {
@@ -34,16 +34,37 @@ if (!isNode()) {
34
34
  loadSearchCache = parseSearchString(location.search);
35
35
  }
36
36
 
37
+ declare global {
38
+ var urlResetLinks: Map<string, Set<string>>;
39
+ }
40
+ // changed => reset
41
+ let resetLinks: Map<string, Set<string>> = globalThis.urlResetLinks || (globalThis.urlResetLinks = new Map());
42
+
37
43
 
38
44
  export class URLParam<T = unknown> {
39
- constructor(urlKey: string, defaultValue: T) {
40
- return createURLSync(urlKey, defaultValue);
45
+ constructor(urlKey: string, defaultValue: T, config?: URLParamConfig) {
46
+ return createURLSync(urlKey, defaultValue, config);
41
47
  }
42
48
  }
43
- /** const myVariable = createURLSync("myvar", 0) */
44
- export function createURLSync<T>(urlKey: string, defaultValue: T, config?: {
49
+ export type URLParamConfig = {
45
50
  storage?: "url" | "localStorage";
46
- }): URLParam<T> {
51
+ // Reset when any of these urlKeys change
52
+ reset?: { (): { urlKey: string }[] }[];
53
+ };
54
+ /** const myVariable = createURLSync("myvar", 0) */
55
+ export function createURLSync<T>(urlKey: string, defaultValue: T, config?: URLParamConfig): URLParam<T> {
56
+ setImmediate(() => {
57
+ for (let reset of config?.reset || []) {
58
+ for (let resetSource of reset()) {
59
+ let lookup = resetLinks.get(resetSource.urlKey);
60
+ if (!lookup) {
61
+ lookup = new Set();
62
+ resetLinks.set(resetSource.urlKey, lookup);
63
+ }
64
+ lookup.add(urlKey);
65
+ }
66
+ }
67
+ });
47
68
 
48
69
  let prev = globalURLLookup[urlKey];
49
70
  if (prev) {
@@ -104,6 +125,15 @@ export function createURLSync<T>(urlKey: string, defaultValue: T, config?: {
104
125
  }
105
126
  },
106
127
  set value(value: T) {
128
+ let resetTargets = resetLinks.get(urlKey);
129
+ if (resetTargets) {
130
+ for (let resetTarget of resetTargets) {
131
+ let param = globalURLLookup[resetTarget];
132
+ if (param) {
133
+ param.reset();
134
+ }
135
+ }
136
+ }
107
137
  if (!proxyWatcher.inWatcher()) {
108
138
  Querysub.commit(() => {
109
139
  param.value = value;
@@ -9,6 +9,9 @@ import { renderToString } from "./renderToString";
9
9
  import { debugTime } from "../../src/0-path-value-core/pathValueCore";
10
10
  import { measureBlock } from "socket-function/src/profiling/measure";
11
11
 
12
+ // TODO: Create more icons with: https://www.recraft.ai/project/e2a2200f-bed2-4426-b8f1-e5120b9dc990
13
+ // - This should also make creating animated SVGs a lot easier. If the base is nice, and we add comments, we can probably get the AI to help animate it
14
+
12
15
  // Most icons are from https://www.figma.com/file/eVpKKmt8uOKmSYKW4LyusF/Free-Icon-Pack-1600%2B-icons-(Community)?node-id=1654-9894&t=0bDbK0bA9KGpswRE-0
13
16
 
14
17
  // TODO: Add a build step that does this (storing the .svgs in a file), so we don't
@@ -10,7 +10,7 @@ let specialStringValuesEncode = new Map([...specialStringValuesDecode].map(([key
10
10
  export function serializeURLParam(value: unknown): string | undefined {
11
11
  if (value === true) return undefined;
12
12
  // If it is a string and doesn't look JSON encoded, then just use it raw.
13
- if (typeof value === "string" && !lookJSONEncoded(value)) {
13
+ if (typeof value === "string" && !lookJSONEncoded(value) && value !== "") {
14
14
  return value;
15
15
  }
16
16
  let specialValue = specialStringValuesEncode.get(value);
@@ -0,0 +1,14 @@
1
+ import { URLParam, URLParamConfig } from "./URLParam";
2
+
3
+
4
+ export const pageURL = new URLParam("page", "");
5
+ export const tabURL = new URLParam("tab", "");
6
+ export const managementPageURL = new URLParam("managementpage", "");
7
+
8
+ export const filterURL = new URLParam("filter", "", { reset: [mainResets] });
9
+ export const startTimeURL = new URLParam("startTime", 0, { reset: [mainResets] });
10
+ export const endTimeURL = new URLParam("endTime", 0, { reset: [mainResets] });
11
+
12
+ export function mainResets(): URLParam[] {
13
+ return [pageURL, tabURL, managementPageURL];
14
+ }
@@ -1,6 +1,12 @@
1
1
  import { qreact } from "../4-dom/qreact";
2
2
  import { formatTime, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
3
+ import { Querysub } from "../4-querysub/QuerysubController";
3
4
 
4
5
  export function formatDateJSX(time: number) {
5
- return <span title={formatVeryNiceDateTime(time)}>{formatTime(Date.now() - time)}</span>;
6
+ let diff = time - Querysub.nowDelayed(10 * 1000);
7
+ let ago = diff < 0;
8
+ if (ago) {
9
+ diff = -diff;
10
+ }
11
+ return <span title={formatVeryNiceDateTime(time)}>{!ago && "IN "}{formatTime(diff)}{ago && " AGO"}</span>;
6
12
  }
package/src/server.ts CHANGED
@@ -33,7 +33,8 @@ async function main() {
33
33
 
34
34
  Error.stackTraceLimit = 20;
35
35
 
36
- //SocketFunction.logMessages = true;
36
+ // SocketFunction.logMessages = true;
37
+ // SocketFunction.silent = false;
37
38
 
38
39
  // ClientWatcher.DEBUG_READS = true;
39
40
  // ClientWatcher.DEBUG_WRITES = true;
package/testEntry2.ts CHANGED
@@ -3,13 +3,23 @@ import { getOwnMachineId } from "./src/-a-auth/certs";
3
3
  import { getOwnThreadId } from "./src/-f-node-discovery/NodeDiscovery";
4
4
  import { shutdown } from "./src/diagnostics/periodic";
5
5
  import { testTCPIsListening } from "socket-function/src/networking";
6
+ import { Querysub } from "./src/4-querysub/QuerysubController";
7
+ import { timeInSecond } from "socket-function/src/misc";
6
8
 
7
9
  export async function testMain() {
8
- let test = await testTCPIsListening("1.1.1.1", 443);
9
- console.log(test);
10
- // await delay(0);
10
+ Querysub;
11
+ //let test = await testTCPIsListening("1.1.1.1", 443);
12
+ //console.log(test);
13
+ // Writing heartbeat 2025/09/14 08:37:46 PM for self (5ac8a2fa78fce4ea.971ed8b01743d123.querysubtest.com:13900)
14
+ await delay(timeInSecond);
15
+ await Querysub.hostService("test");
16
+ await delay(timeInSecond * 5);
11
17
  // console.log(getOwnThreadId());
12
- // console.error(`Test warning for ErrorWarning testing`);
18
+ // Log an error every 30 seconds forever.
19
+ while (true) {
20
+ console.error(`Test warning for im testing ${Date.now()}`);
21
+ await delay(timeInSecond * 30);
22
+ }
13
23
  // console.log(getOwnThreadId());
14
24
  // await shutdown();
15
25
  //await Querysub.hostService("test");
@@ -24,7 +34,8 @@ export async function testMain() {
24
34
  // }
25
35
  // await testLogs.flushNow();
26
36
  // }
27
- // await shutdown();
37
+ await delay(timeInSecond * 15);
38
+ await shutdown();
28
39
  }
29
40
  async function main() {
30
41