querysub 0.52.0 → 0.53.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.52.0",
3
+ "version": "0.53.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",
@@ -15,7 +15,7 @@ import { markArrayAsSplitable } from "socket-function/src/fixLargeNetworkCalls";
15
15
  import { registerDynamicResource, registerMapArrayResource, registerResource } from "../diagnostics/trackResources";
16
16
  import { binarySearchIndex, isNode, isNodeTrue, last, promiseObj, sort, timeInHour, timeInMinute, timeInSecond, QueueLimited } from "socket-function/src/misc";
17
17
  import { isNodeTrusted, isTrusted, isTrustedByNode } from "../-d-trust/NetworkTrust2";
18
- import { AuthorityPath, LOCAL_DOMAIN_PATH, pathValueAuthority2 } from "./NodePathAuthorities";
18
+ import { AuthorityPath, LOCAL_DOMAIN, LOCAL_DOMAIN_PATH, pathValueAuthority2 } from "./NodePathAuthorities";
19
19
  import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
20
20
  import { isNoNetwork } from "../config";
21
21
  import { formatTime } from "socket-function/src/formatting/format";
@@ -1126,6 +1126,7 @@ class PathWatcher {
1126
1126
  }>());
1127
1127
  // If we don't limit the length, then this will memory leak. And if we only track
1128
1128
  // the values in watchers, then rolling key syncs will be lost.
1129
+ // DOES NOT track local values, as they are used internally, but we aren't waiting for them to be synced.
1129
1130
  private syncHistoryForHarvest = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
1130
1131
  private syncHistoryForDebug = new QueueLimited<{ start: number; end: number; path: string; }>(1_000_000);
1131
1132
 
@@ -1396,7 +1397,7 @@ class PathWatcher {
1396
1397
  for (let value of valuesChanged) {
1397
1398
  let path = value.path;
1398
1399
  let latestWatches = this.watchers.get(path);
1399
- if (config?.pathsAreSynced && latestWatches && !latestWatches.receivedTime) {
1400
+ if (config?.pathsAreSynced && latestWatches && !latestWatches.receivedTime && !path.startsWith(LOCAL_DOMAIN_PATH)) {
1400
1401
  latestWatches.receivedTime = time;
1401
1402
  let obj = { start: latestWatches.requestedTime, end: time, path };
1402
1403
  this.syncHistoryForHarvest.push(obj);
@@ -516,8 +516,9 @@ export class PathValueProxyWatcher {
516
516
  public getCallback = (pathStr: string, syncParentKeys?: "parentKeys", readTransparent?: "readTransparent"): { value: unknown } | undefined => {
517
517
  if (PathValueProxyWatcher.BREAK_ON_READS.size > 0 && proxyWatcher.isAllSynced()) {
518
518
  if (isRecursiveMatch(PathValueProxyWatcher.BREAK_ON_READS, pathStr)) {
519
- const unwatch = () => PathValueProxyWatcher.BREAK_ON_READS.delete(pathStr); unwatch;
519
+ const unwatch = () => removeMatches(PathValueProxyWatcher.BREAK_ON_READS, pathStr);
520
520
  debugger;
521
+ unwatch;
521
522
  }
522
523
  }
523
524
  const watcher = this.runningWatcher;
@@ -591,14 +592,15 @@ export class PathValueProxyWatcher {
591
592
  private setCallback = (pathStr: string, value: unknown, inRecursion = false, allowSpecial = false): void => {
592
593
  if (PathValueProxyWatcher.BREAK_ON_WRITES.size > 0 && proxyWatcher.isAllSynced()) {
593
594
  if (isRecursiveMatch(PathValueProxyWatcher.BREAK_ON_WRITES, pathStr)) {
594
- let unwatch = () => PathValueProxyWatcher.BREAK_ON_WRITES.delete(pathStr); unwatch;
595
+ let unwatch = () => removeMatches(PathValueProxyWatcher.BREAK_ON_WRITES, pathStr);
595
596
  debugger;
597
+ unwatch;
596
598
  }
597
599
  }
598
600
 
599
601
  if (PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.size > 0 && proxyWatcher.isAllSynced()) {
600
602
  if (isRecursiveMatch(PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES, pathStr)) {
601
- let unwatch = () => PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.delete(pathStr); unwatch;
603
+ let unwatch = () => removeMatches(PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES, pathStr);
602
604
  PathValueProxyWatcher.BREAK_ON_CALL.add(
603
605
  getPathStr2(getCurrentCall().ModuleId, getCurrentCall().FunctionId)
604
606
  );
@@ -607,7 +609,7 @@ export class PathValueProxyWatcher {
607
609
 
608
610
  if (PathValueProxyWatcher.LOG_WRITES_INCLUDES.size > 0 && proxyWatcher.isAllSynced()) {
609
611
  if (isRecursiveMatch(PathValueProxyWatcher.LOG_WRITES_INCLUDES, pathStr)) {
610
- let unwatch = () => PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(pathStr); unwatch;
612
+ let unwatch = () => removeMatches(PathValueProxyWatcher.LOG_WRITES_INCLUDES, pathStr);
611
613
  console.log(`Write path "${pathStr}" = ${value}`);
612
614
  }
613
615
  }
@@ -1709,6 +1711,7 @@ export class PathValueProxyWatcher {
1709
1711
  }
1710
1712
  return watcher;
1711
1713
  }
1714
+
1712
1715
  public isAllSynced() {
1713
1716
  return !this.getTriggeredWatcher().hasAnyUnsyncedAccesses();
1714
1717
  }
@@ -1720,6 +1723,41 @@ export class PathValueProxyWatcher {
1720
1723
  return this.runningWatcher;
1721
1724
  }
1722
1725
 
1726
+ /** Watches everything we were watching last watch. Is essential for efficient watching code,
1727
+ * which needs access to a lot of state, but doesn't want to process the entire state
1728
+ * every time any value changes.
1729
+ */
1730
+ public reuseLastWatches() {
1731
+ let watcher = this.getTriggeredWatcher();
1732
+ for (let path of watcher.lastUnsyncedAccesses) {
1733
+ if (!authorityStorage.isSynced(path)) {
1734
+ watcher.pendingUnsyncedAccesses.add(path);
1735
+ }
1736
+ }
1737
+ for (let path of watcher.lastUnsyncedParentAccesses) {
1738
+ if (!authorityStorage.isParentSynced(path)) {
1739
+ watcher.pendingUnsyncedParentAccesses.add(path);
1740
+ }
1741
+ }
1742
+
1743
+
1744
+ function efficientSetMerge(set1: Set<string>, set2: Set<string>) {
1745
+ let bigSet = set1.size > set2.size ? set1 : set2;
1746
+ let smallSet = set1.size > set2.size ? set2 : set1;
1747
+ for (let path of smallSet) {
1748
+ bigSet.add(path);
1749
+ }
1750
+ return bigSet;
1751
+ }
1752
+
1753
+ // NOTE: If we don't clone, then pathValueClientWatcher cannot detect changes, and we
1754
+ // end up staying watched on everything!
1755
+ // - We should really fix this though, as if we can avoid this clone, we can have really efficient
1756
+ // incremental watches.
1757
+ watcher.pendingWatches.paths = efficientSetMerge(watcher.pendingWatches.paths, new Set(watcher.lastWatches.paths));
1758
+ watcher.pendingWatches.parentPaths = efficientSetMerge(watcher.pendingWatches.parentPaths, new Set(watcher.lastWatches.parentPaths));
1759
+ }
1760
+
1723
1761
  /** NOTE: This explicitly causes the watcher to be unsynced until function
1724
1762
  * can be evaluated without any pending promises. This makes this significantly
1725
1763
  * more powerful than waiting on localState.
@@ -223,25 +223,9 @@ export class PathFunctionRunner {
223
223
  // so that it is automatically done at the start of our trigger.
224
224
  const watcher = proxyWatcher.getTriggeredWatcher();
225
225
 
226
- // Preserve previous watches explicitly (so we don't have to use the proxy, which would be slower)
227
- {
228
- for (let path of watcher.lastUnsyncedAccesses) {
229
- if (!authorityStorage.isSynced(path)) {
230
- watcher.pendingUnsyncedAccesses.add(path);
231
- }
232
- }
233
- for (let path of watcher.lastUnsyncedParentAccesses) {
234
- if (!authorityStorage.isParentSynced(path)) {
235
- watcher.pendingUnsyncedParentAccesses.add(path);
236
- }
237
- }
238
- // NOTE: If we don't clone, then pathValueClientWatcher cannot detect changes, and we
239
- // end up staying watched on everything!
240
- watcher.pendingWatches.paths = new Set(watcher.lastWatches.paths);
241
- watcher.pendingWatches.parentPaths = new Set(watcher.lastWatches.parentPaths);
242
- }
243
-
244
-
226
+ // Preserve previous watches explicitly (so we just do a diff, instead of accessing every
227
+ // value every time).
228
+ proxyWatcher.reuseLastWatches();
245
229
 
246
230
  let data = () => functionSchema()[domainName].PathFunctionRunner;
247
231
 
@@ -75,6 +75,10 @@ export type VirtualDOM = VirtualDOMBase | VirtualDOMBase[];
75
75
  type GetTFromAttributes<T> = T extends preact.JSX.HTMLAttributes<infer U> ? U : never;
76
76
 
77
77
  export namespace qreact {
78
+ export type VNode = preact.ComponentChildren;
79
+ export type ComponentChildren = preact.ComponentChildren;
80
+ export type VDom = preact.ComponentChildren;
81
+ export type Component = InstanceType<typeof Component>;
78
82
  export namespace JSX {
79
83
  export type IntrinsicElements = {
80
84
  [key in keyof preact.JSX.IntrinsicElements]: (
@@ -129,6 +133,7 @@ export interface QComponentStatic {
129
133
  * - Results in fewer re-renders, but can be slower if the props are large.
130
134
  */
131
135
  jsonComparePropUpdates?: boolean;
136
+
132
137
  /** Decomposes props into individual primitive values when writing (the default state of
133
138
  * most synced writes).
134
139
  * - Results in minimal re-renders, even skipping intermediate component re-renders
@@ -196,6 +201,8 @@ export class qreact {
196
201
  public static hasEventHandlers = hasEventHandlers;
197
202
  public static getCurrentComponentId = getCurrentComponentId;
198
203
 
204
+ public static cancelRender = cancelRender;
205
+
199
206
  public static allComponents = new Set<ExternalRenderClass>();
200
207
 
201
208
  public static DEBUG_TIME = 0;
@@ -319,6 +326,8 @@ export type ExternalRenderClass = {
319
326
  readonly mountDOMWatcher: SyncWatcher;
320
327
  readonly debugName: string;
321
328
  readonly instance: preact.Component<{ [key: string]: unknown }>;
329
+ readonly childComponents: Map<VirtualDOM, number>;
330
+ getInstance(id: number): ExternalRenderClass | undefined;
322
331
  getVSCodeLink(): string | undefined;
323
332
  // TODO: We can add more types here as needed, exposing parts of QRenderClass which
324
333
  // are safe for external callers to access.
@@ -377,6 +386,11 @@ if (!isNode()) {
377
386
  document.addEventListener("mouseup", () => mouseIsDown = false);
378
387
  }
379
388
 
389
+ let cancelNextRender = false;
390
+ function cancelRender() {
391
+ cancelNextRender = true;
392
+ }
393
+
380
394
 
381
395
  let renderClasses = new Map<number, QRenderClass>();
382
396
  // NOTE: In theory this COULD BE our root Component, but... it makes compatibility easier
@@ -552,6 +566,7 @@ class QRenderClass {
552
566
 
553
567
  if (self.disposed || self.disposing) return;
554
568
  let vNode: VirtualDOM;
569
+ cancelNextRender = false;
555
570
  try {
556
571
  let time = Date.now();
557
572
  QRenderClass.renderingComponentId = self.id;
@@ -565,6 +580,11 @@ class QRenderClass {
565
580
  }
566
581
  registerOwnTime("render", time);
567
582
 
583
+ if (cancelNextRender) {
584
+ // Simple by NOT writing to vNodeForRender, we cancel the render
585
+ return;
586
+ }
587
+
568
588
  time = Date.now();
569
589
 
570
590
  vNode = QRenderClass.normalizeVNode({ vNode, array: true });
@@ -579,6 +599,7 @@ class QRenderClass {
579
599
  vNode = QRenderClass.normalizeVNode({ vNode, array: true });
580
600
  } finally {
581
601
  QRenderClass.renderingComponentId = undefined;
602
+ cancelNextRender = true;
582
603
  }
583
604
 
584
605
  self.data().vNodeForRender = atomicObjectWriteNoFreeze({ value: vNode });
@@ -1475,6 +1496,11 @@ class QRenderClass {
1475
1496
  if (!instance) throw new Error(`Could not find component with id ${id}`);
1476
1497
  return instance;
1477
1498
  }
1499
+ public getInstance(id: number) {
1500
+ let instance = renderClasses.get(id);
1501
+ if (!instance) throw new Error(`Could not find component with id ${id}`);
1502
+ return instance;
1503
+ }
1478
1504
  public static getData(id: number) {
1479
1505
  return schema()[LOCAL_DOMAIN].qreact.components[id];
1480
1506
  }
@@ -2055,7 +2081,7 @@ export function getSourceVSCodeLink(element: DOMNode | undefined) {
2055
2081
  Command palette: You can trigger VS Code commands using vscode://file/{file}?command={commandId}.
2056
2082
  Extensions: Some VS Code extensions may add support for additional URL parameters.
2057
2083
  */
2058
- let path = `vscode://file/${sourceInfo.sourceFileName}`;
2084
+ let path = `cursor://file/${sourceInfo.sourceFileName}`;
2059
2085
  if (sourceInfo.lineNumber) {
2060
2086
  path += `:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`;
2061
2087
  path += `?selection=${sourceInfo.lineNumber},${sourceInfo.columnNumber}`;
@@ -182,9 +182,11 @@ export class Querysub {
182
182
  public static debugWrites = (value: unknown) => proxyWatcher.debug_breakOnWrite(value);
183
183
  public static breakOnWrite = (value: unknown) => proxyWatcher.debug_breakOnWrite(value);
184
184
  public static logWrites = (value: unknown) => proxyWatcher.debug_logOnWrite(value);
185
+ public static reuseLastWatches = () => proxyWatcher.reuseLastWatches();
185
186
 
186
187
  public static watchUnsyncedComponents = () => qreact.watchUnsyncedComponents();
187
188
  public static watchAnyUnsyncedComponents = () => qreact.watchUnsyncedComponents().size > 0;
189
+ public static cancelRender = () => qreact.cancelRender();
188
190
 
189
191
  public static doAtomicWrites = <T>(callback: () => T): T => doAtomicWrites(callback);
190
192
 
@@ -83,15 +83,18 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
83
83
  if (
84
84
  canHaveChildren(innerContent) && "props" in innerContent
85
85
  && canHaveChildren(innerContent.props)
86
- && "children" in innerContent.props
87
- && (
88
- Array.isArray(innerContent.props.children) && innerContent.props.children.length === 1
89
- || !Array.isArray(innerContent.props.children)
90
- )
91
- // AND, it is a div or span (a tags shouldn't be unwrapped)
86
+ // NOTE: I'm not sure why we were only elevating the children if we only had 1 child?
87
+ // Changing this will break things, but it should be better overall.
88
+ // && "children" in innerContent.props
89
+ // && (
90
+ // Array.isArray(innerContent.props.children) && innerContent.props.children.length === 1
91
+ // || !Array.isArray(innerContent.props.children)
92
+ // )
93
+ // AND, it is a div (a tags shouldn't be unwrapped)
92
94
  && (innerContent.type === "div")
93
95
  ) {
94
- attributes.class += " " + innerContent.props.class;
96
+ attributes.class += " " + (innerContent.props.class || "");
97
+ attributes.class += " " + (innerContent.props.className || "");
95
98
  let baseOnClick = attributes.onClick;
96
99
  let props = innerContent.props;
97
100
  attributes.onClick = (e) => {
@@ -20,6 +20,7 @@ import { isCallerDynamicModule, isDynamicModule } from "../3-path-functions/path
20
20
 
21
21
  // Map, so hot reloading doesn't break things
22
22
  let componentButtons = new Map<string, { title: string, callback: (component: ExternalRenderClass) => void }>();
23
+ let componentUIs = new Map<string, (component: ExternalRenderClass) => qreact.ComponentChildren>();
23
24
 
24
25
  export function addComponentButton(config: {
25
26
  title: string;
@@ -32,6 +33,17 @@ export function addComponentButton(config: {
32
33
  componentButtons.set(config.title, config);
33
34
  }
34
35
 
36
+ export function addComponentUI(config: {
37
+ key: string;
38
+ createUI: (component: ExternalRenderClass) => qreact.ComponentChildren;
39
+ }) {
40
+ if (isCallerDynamicModule()) return;
41
+ if (!isHotReloading() && componentUIs.has(config.key)) {
42
+ throw new Error(`Component UI with key ${config.key} already exists`);
43
+ }
44
+ componentUIs.set(config.key, config.createUI);
45
+ }
46
+
35
47
  export const enableDebugComponents = lazy(function enableDebugComponents() {
36
48
  console.log(magenta(`middleclick + shift on components to debug them`));
37
49
  // mousedown is more reliable than click, because the click will be aborted if they middle click
@@ -197,12 +209,13 @@ class WatchModal extends qreact.Component<{
197
209
  <Button onClick={() => component.instance.forceUpdate()}>
198
210
  Rerender
199
211
  </Button>
200
- {componentButtons.size > 0 && Array.from(componentButtons.values()).map(({ title, callback }) => (
212
+ {Array.from(componentButtons.values()).map(({ title, callback }) => (
201
213
  <Button onClick={() => callback(component)}>
202
214
  {title}
203
215
  </Button>
204
216
  ))}
205
217
  </div>
218
+ {Array.from(componentUIs.values()).map((createUI) => createUI(component))}
206
219
 
207
220
 
208
221
  <InputLabelURL hot label="Filter" url={pathFilter} />
@@ -23,6 +23,7 @@ export type ATagProps = (
23
23
  * it results in a page load on click.
24
24
  */
25
25
  rawLink?: boolean;
26
+ lightMode?: boolean;
26
27
  }
27
28
  );
28
29
 
@@ -33,14 +34,16 @@ export class ATag extends qreact.Component<ATagProps> {
33
34
  return <div {...props as any}>{children}</div>;
34
35
  }
35
36
  let isCurrent = values?.every(value => value.param.value === value.value);
37
+ let lightness = props.lightMode ? 40 : 80;
36
38
  return (
37
39
  <a
38
40
  tabIndex={0}
39
41
  {...props}
40
42
  className={
41
- (isCurrent ? css.color("hsl(110, 75%, 80%)") : css.color("hsl(210, 100%, 80%)"))
43
+ (isCurrent ? css.color(`hsl(110, 75%, ${lightness}%)`) : css.color(`hsl(210, 100%, ${lightness}%)`))
42
44
  + css.textDecoration("none")
43
- .textDecoration("underline", "hover").outline("3px solid hsl(204, 100%, 50%)", "focus")
45
+ .textDecoration("underline", "hover")
46
+ .outline("3px solid hsl(204, 100%, 50%)", "focus")
44
47
  + (props.className ?? props.class)
45
48
 
46
49
  }
@@ -31,6 +31,7 @@ export class ButtonSelector<T> extends qreact.Component<{
31
31
  .border("1px solid hsl(0, 0%, 5%)").pad(4, 6)
32
32
  .color("white")
33
33
  }
34
+ title={String(selectedValue)}
34
35
  >
35
36
  {title}
36
37
  </div>}