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 +1 -1
- package/src/0-path-value-core/pathValueCore.ts +3 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +42 -4
- package/src/3-path-functions/PathFunctionRunner.ts +3 -19
- package/src/4-dom/qreact.tsx +27 -1
- package/src/4-querysub/Querysub.ts +2 -0
- package/src/5-diagnostics/Table.tsx +10 -7
- package/src/5-diagnostics/qreactDebug.tsx +14 -1
- package/src/library-components/ATag.tsx +5 -2
- package/src/library-components/ButtonSelector.tsx +1 -0
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
package/src/4-dom/qreact.tsx
CHANGED
|
@@ -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 = `
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
//
|
|
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
|
-
{
|
|
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(
|
|
43
|
+
(isCurrent ? css.color(`hsl(110, 75%, ${lightness}%)`) : css.color(`hsl(210, 100%, ${lightness}%)`))
|
|
42
44
|
+ css.textDecoration("none")
|
|
43
|
-
.textDecoration("underline", "hover")
|
|
45
|
+
.textDecoration("underline", "hover")
|
|
46
|
+
.outline("3px solid hsl(204, 100%, 50%)", "focus")
|
|
44
47
|
+ (props.className ?? props.class)
|
|
45
48
|
|
|
46
49
|
}
|