querysub 0.152.0 → 0.154.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 +6 -6
- package/src/-b-authorities/cloudflareHelpers.ts +11 -2
- package/src/3-path-functions/PathFunctionRunner.ts +168 -97
- package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
- package/src/3-path-functions/pathFunctionLoader.ts +11 -6
- package/src/3-path-functions/syncSchema.ts +10 -1
- package/src/4-deploy/edgeBootstrap.ts +10 -1
- package/src/4-querysub/Querysub.ts +77 -3
- package/src/4-querysub/QuerysubController.ts +22 -2
- package/src/4-querysub/permissions.ts +33 -2
- package/src/4-querysub/querysubPrediction.ts +52 -18
- package/src/archiveapps/archiveGCEntry.tsx +38 -0
- package/src/archiveapps/archiveJoinEntry.ts +121 -0
- package/src/archiveapps/archiveMergeEntry.tsx +47 -0
- package/src/archiveapps/compressTest.tsx +59 -0
- package/src/archiveapps/lockTest.ts +127 -0
- package/src/config.ts +5 -0
- package/src/diagnostics/managementPages.tsx +55 -0
- package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
- package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
- package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
- package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
- package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
- package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
- package/src/email/postmark.tsx +40 -0
- package/src/email/sendgrid.tsx +44 -0
- package/src/functional/UndoWatch.tsx +133 -0
- package/src/functional/diff.ts +858 -0
- package/src/functional/promiseCache.ts +67 -0
- package/src/functional/random.ts +9 -0
- package/src/functional/runCommand.ts +42 -0
- package/src/functional/runOnce.ts +7 -0
- package/src/functional/stats.ts +61 -0
- package/src/functional/throttleRerender.tsx +80 -0
- package/src/library-components/AspectSizedComponent.tsx +88 -0
- package/src/library-components/Histogram.tsx +338 -0
- package/src/library-components/InlinePopup.tsx +67 -0
- package/src/library-components/Notifications.tsx +153 -0
- package/src/library-components/RenderIfVisible.tsx +80 -0
- package/src/library-components/SimpleNotification.tsx +133 -0
- package/src/library-components/TabbedUI.tsx +39 -0
- package/src/library-components/animateAnyElement.tsx +65 -0
- package/src/library-components/errorNotifications.tsx +81 -0
- package/src/library-components/placeholder.ts +18 -0
- package/src/misc/format2.ts +48 -0
- package/src/misc.ts +33 -0
- package/src/misc2.ts +5 -0
- package/src/server.ts +2 -1
- package/src/storage/diskCache.ts +227 -0
- package/src/storage/diskCache2.ts +122 -0
- package/src/storage/fileSystemPointer.ts +72 -0
- package/src/user-implementation/LoginPage.tsx +78 -0
- package/src/user-implementation/RequireAuditPage.tsx +219 -0
- package/src/user-implementation/SecurityPage.tsx +212 -0
- package/src/user-implementation/UserPage.tsx +320 -0
- package/src/user-implementation/addSuperUser.ts +21 -0
- package/src/user-implementation/canSeeSource.ts +41 -0
- package/src/user-implementation/loginEmail.tsx +159 -0
- package/src/user-implementation/setEmailKey.ts +20 -0
- package/src/user-implementation/userData.ts +974 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
2
|
+
import { cacheLimited } from "socket-function/src/caching";
|
|
3
|
+
|
|
4
|
+
// IMPORTANT! See getSyncedController if you just want to run controller functions
|
|
5
|
+
|
|
6
|
+
export function cacheAsyncLimited<Arg, Return>(limit: number, fnc: (arg: Arg) => Promise<Return>) {
|
|
7
|
+
let results = new Map<Arg, { type: "result"; value: Return } | { type: "error"; error: Error; }>();
|
|
8
|
+
let promiseValues = cacheLimited(limit, fnc);
|
|
9
|
+
return function get(arg: Arg) {
|
|
10
|
+
let result = results.get(arg);
|
|
11
|
+
if (result) {
|
|
12
|
+
if (result.type === "result") {
|
|
13
|
+
return result.value;
|
|
14
|
+
} else {
|
|
15
|
+
throw result.error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let promise = promiseValues(arg);
|
|
19
|
+
promise.then(
|
|
20
|
+
value => {
|
|
21
|
+
if (results.size >= limit) results.clear();
|
|
22
|
+
results.set(arg, { type: "result", value });
|
|
23
|
+
},
|
|
24
|
+
error => {
|
|
25
|
+
if (results.size >= limit) results.clear();
|
|
26
|
+
results.set(arg, { type: "error", error });
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: fnc.toString() });
|
|
30
|
+
return undefined;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const cacheAsyncSynced = cacheAsyncLimitedJSON;
|
|
35
|
+
export function cacheAsyncLimitedJSON<Arg, Return>(limit: number, fnc: (arg: Arg) => Promise<Return>) {
|
|
36
|
+
let results = new Map<string, { type: "result"; value: Return } | { type: "error"; error: Error; }>();
|
|
37
|
+
let promiseValues = cacheLimited(limit, (json: string) => fnc(JSON.parse(json)));
|
|
38
|
+
get["clear"] = () => {
|
|
39
|
+
results.clear();
|
|
40
|
+
promiseValues.clear();
|
|
41
|
+
};
|
|
42
|
+
return get;
|
|
43
|
+
function get(arg: Arg) {
|
|
44
|
+
let json = JSON.stringify(arg);
|
|
45
|
+
let result = results.get(json);
|
|
46
|
+
if (result) {
|
|
47
|
+
if (result.type === "result") {
|
|
48
|
+
return result.value;
|
|
49
|
+
} else {
|
|
50
|
+
throw result.error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let promise = promiseValues(json);
|
|
54
|
+
promise.then(
|
|
55
|
+
value => {
|
|
56
|
+
if (results.size >= limit) results.clear();
|
|
57
|
+
results.set(json, { type: "result", value });
|
|
58
|
+
},
|
|
59
|
+
error => {
|
|
60
|
+
if (results.size >= limit) results.clear();
|
|
61
|
+
results.set(json, { type: "error", error });
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: fnc.toString() });
|
|
65
|
+
return undefined;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { critz } from "./stats";
|
|
2
|
+
import { getSeededRandom, shuffle } from "../misc/random";
|
|
3
|
+
|
|
4
|
+
export function getSeededRandomNormal(seed: number, mean: number, stdDev: number) {
|
|
5
|
+
let rand = getSeededRandom(seed);
|
|
6
|
+
return () => {
|
|
7
|
+
return critz(rand()) * stdDev + mean;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import child_process from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export async function runCommand(config: {
|
|
5
|
+
exe: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
cwd?: string;
|
|
8
|
+
cancelPromise?: Promise<void>;
|
|
9
|
+
}) {
|
|
10
|
+
await new Promise<void>((resolve, reject) => {
|
|
11
|
+
let childProc = child_process.spawn(config.exe, config.args, {
|
|
12
|
+
cwd: path.resolve(__dirname, "../.."),
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
});
|
|
15
|
+
childProc.on("error", reject);
|
|
16
|
+
childProc.on("exit", (code) => {
|
|
17
|
+
if (code === 0) {
|
|
18
|
+
resolve();
|
|
19
|
+
} else {
|
|
20
|
+
reject(new Error(`Process exited with code ${code} for command ${config.exe} ${config.args.join(" ")}`));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
void config.cancelPromise?.finally(() => {
|
|
24
|
+
childProc.kill();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function runCommandShell(command: string, config?: { cwd?: string; maxBuffer?: number }) {
|
|
30
|
+
return new Promise<string>((resolve, reject) => {
|
|
31
|
+
child_process.exec(command, {
|
|
32
|
+
cwd: config?.cwd || path.resolve(__dirname, "../.."),
|
|
33
|
+
maxBuffer: config?.maxBuffer,
|
|
34
|
+
}, (err, stdout, stderr) => {
|
|
35
|
+
if (err) {
|
|
36
|
+
reject(err);
|
|
37
|
+
} else {
|
|
38
|
+
resolve(stdout);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// https://stackoverflow.com/questions/36575743/how-do-i-convert-probability-into-z-score
|
|
2
|
+
|
|
3
|
+
var Z_MAX = 6;
|
|
4
|
+
export function poz(z: number) {
|
|
5
|
+
|
|
6
|
+
var y, x, w;
|
|
7
|
+
|
|
8
|
+
if (z === 0.0) {
|
|
9
|
+
x = 0.0;
|
|
10
|
+
} else {
|
|
11
|
+
y = 0.5 * Math.abs(z);
|
|
12
|
+
if (y > (Z_MAX * 0.5)) {
|
|
13
|
+
x = 1.0;
|
|
14
|
+
} else if (y < 1.0) {
|
|
15
|
+
w = y * y;
|
|
16
|
+
x = ((((((((0.000124818987 * w
|
|
17
|
+
- 0.001075204047) * w + 0.005198775019) * w
|
|
18
|
+
- 0.019198292004) * w + 0.059054035642) * w
|
|
19
|
+
- 0.151968751364) * w + 0.319152932694) * w
|
|
20
|
+
- 0.531923007300) * w + 0.797884560593) * y * 2.0;
|
|
21
|
+
} else {
|
|
22
|
+
y -= 2.0;
|
|
23
|
+
x = (((((((((((((-0.000045255659 * y
|
|
24
|
+
+ 0.000152529290) * y - 0.000019538132) * y
|
|
25
|
+
- 0.000676904986) * y + 0.001390604284) * y
|
|
26
|
+
- 0.000794620820) * y - 0.002034254874) * y
|
|
27
|
+
+ 0.006549791214) * y - 0.010557625006) * y
|
|
28
|
+
+ 0.011630447319) * y - 0.009279453341) * y
|
|
29
|
+
+ 0.005353579108) * y - 0.002141268741) * y
|
|
30
|
+
+ 0.000535310849) * y + 0.999936657524;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return z > 0.0 ? ((x + 1.0) * 0.5) : ((1.0 - x) * 0.5);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/* CRITZ -- Compute critical normal z value to
|
|
38
|
+
produce given p. We just do a bisection
|
|
39
|
+
search for a value within CHI_EPSILON,
|
|
40
|
+
relying on the monotonicity of pochisq(). */
|
|
41
|
+
|
|
42
|
+
export function critz(p: number) {
|
|
43
|
+
var Z_EPSILON = 0.000001; /* Accuracy of z approximation */
|
|
44
|
+
var minz = -Z_MAX;
|
|
45
|
+
var maxz = Z_MAX;
|
|
46
|
+
var zval = 0.0;
|
|
47
|
+
var pval;
|
|
48
|
+
if (p < 0.0) p = 0.0;
|
|
49
|
+
if (p > 1.0) p = 1.0;
|
|
50
|
+
|
|
51
|
+
while ((maxz - minz) > Z_EPSILON) {
|
|
52
|
+
pval = poz(zval);
|
|
53
|
+
if (pval > p) {
|
|
54
|
+
maxz = zval;
|
|
55
|
+
} else {
|
|
56
|
+
minz = zval;
|
|
57
|
+
}
|
|
58
|
+
zval = (maxz + minz) * 0.5;
|
|
59
|
+
}
|
|
60
|
+
return (zval);
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { qreact } from "../4-dom/qreact";
|
|
3
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
4
|
+
import { css } from "typesafecss";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PAUSE_THRESHOLD = 200;
|
|
7
|
+
|
|
8
|
+
interface ThrottleState {
|
|
9
|
+
prevVNode: qreact.VNode;
|
|
10
|
+
paused: boolean;
|
|
11
|
+
threshold: number;
|
|
12
|
+
lastRenderTime: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// TODO: Support predicting if a render should be paused based on previous runs
|
|
16
|
+
// (using the target type, or maybe the render function body)?
|
|
17
|
+
// - Even just storing in memory the previous component types that were slow might be enough to be useful?
|
|
18
|
+
// TODO: Support actual render throttling as well, which delays slow renders, instead of entirely pausing them
|
|
19
|
+
// (until they get so slow we need to pause them)
|
|
20
|
+
export function throttleRerender(options: {
|
|
21
|
+
pauseThreshold?: number;
|
|
22
|
+
startPaused?: boolean;
|
|
23
|
+
}) {
|
|
24
|
+
return function (target: unknown, propertyKey: unknown, descriptor: PropertyDescriptor) {
|
|
25
|
+
const originalMethod = descriptor.value as Function;
|
|
26
|
+
descriptor.value = newRender;
|
|
27
|
+
return descriptor;
|
|
28
|
+
|
|
29
|
+
function newRender(this: {
|
|
30
|
+
throttleState?: ThrottleState;
|
|
31
|
+
} & qreact.Component, ...args: any[]) {
|
|
32
|
+
let throttleState = this.throttleState;
|
|
33
|
+
if (!throttleState) {
|
|
34
|
+
throttleState = {
|
|
35
|
+
prevVNode: undefined,
|
|
36
|
+
paused: options.startPaused ?? false,
|
|
37
|
+
threshold: options.pauseThreshold ?? DEFAULT_PAUSE_THRESHOLD,
|
|
38
|
+
lastRenderTime: 0,
|
|
39
|
+
};
|
|
40
|
+
this.throttleState = throttleState;
|
|
41
|
+
}
|
|
42
|
+
if (throttleState.paused) {
|
|
43
|
+
// NOTE: reuseLastWatches will stop the server from unsyncing values. BUT... it shouldn't unsync
|
|
44
|
+
// them immediately anyways... as that would cause dialogs, etc, to always have to resync
|
|
45
|
+
// a lot of state. So... I think it's fine to not call reuseLastWatches here. If we have
|
|
46
|
+
// a usecase that has a lot of of values being synced, which somehow renders quickly,
|
|
47
|
+
// but is only slow due to us rapidly changing from syncing nothing to everything...
|
|
48
|
+
// then we could re-enable this. But I imagine the render code will take more time
|
|
49
|
+
// than the sync overhead (not the syncing time, just the overhead to recalculate what is
|
|
50
|
+
// syncing).
|
|
51
|
+
//Querysub.reuseLastWatches();
|
|
52
|
+
return <>
|
|
53
|
+
<div
|
|
54
|
+
key="throttle-rerender-paused"
|
|
55
|
+
className={css.pad2(6, 2).hsla(0, 0, 0, 0.8).color("white").button}
|
|
56
|
+
title={`Paused, render ${formatTime(throttleState.lastRenderTime)} >= threshold ${formatTime(throttleState.threshold)}`}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
throttleState!.paused = false;
|
|
59
|
+
this.forceUpdate();
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
Slow component paused ({formatTime(throttleState.lastRenderTime)} render time). Click to unpause.
|
|
63
|
+
</div>
|
|
64
|
+
{throttleState.prevVNode}
|
|
65
|
+
</>;
|
|
66
|
+
}
|
|
67
|
+
let time = Date.now();
|
|
68
|
+
try {
|
|
69
|
+
const result = originalMethod.apply(this, args);
|
|
70
|
+
throttleState.prevVNode = result;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throttleState.prevVNode = qreact.errorHandler({ error: e, debugName: "throttleRerender" });
|
|
73
|
+
}
|
|
74
|
+
time = Date.now() - time;
|
|
75
|
+
throttleState.lastRenderTime = time;
|
|
76
|
+
throttleState.paused = time >= throttleState.threshold;
|
|
77
|
+
return throttleState.prevVNode;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
2
|
+
import { css } from "../4-dom/css";
|
|
3
|
+
import { qreact } from "../4-dom/qreact";
|
|
4
|
+
|
|
5
|
+
export class AspectSizedComponent extends qreact.Component<{
|
|
6
|
+
aspectRatio: number;
|
|
7
|
+
onNewSize?: (width: number, height: number) => void;
|
|
8
|
+
center?: { x: number; y: number };
|
|
9
|
+
className?: string;
|
|
10
|
+
innerClassName?: string;
|
|
11
|
+
defaultWidth?: number;
|
|
12
|
+
defaultHeight?: number;
|
|
13
|
+
onClick?: (e: MouseEvent) => void;
|
|
14
|
+
}> {
|
|
15
|
+
state = {
|
|
16
|
+
width: this.props.defaultWidth ?? 100,
|
|
17
|
+
height: this.props.defaultHeight ?? 100,
|
|
18
|
+
};
|
|
19
|
+
holder: HTMLDivElement | null = null;
|
|
20
|
+
getSize() {
|
|
21
|
+
let { aspectRatio } = this.props;
|
|
22
|
+
let { width, height } = this.state;
|
|
23
|
+
let leftFraction = 0;
|
|
24
|
+
let topFraction = 0;
|
|
25
|
+
let widthFraction = 1;
|
|
26
|
+
let heightFraction = 1;
|
|
27
|
+
|
|
28
|
+
let center = this.props.center ?? { x: 0.5, y: 0.5 };
|
|
29
|
+
|
|
30
|
+
if (width / height > aspectRatio) {
|
|
31
|
+
// Too wide
|
|
32
|
+
let newWidth = height * aspectRatio;
|
|
33
|
+
leftFraction = (width - newWidth) * center.x / width;
|
|
34
|
+
widthFraction = newWidth / width;
|
|
35
|
+
} else {
|
|
36
|
+
// Too tall
|
|
37
|
+
let newHeight = width / aspectRatio;
|
|
38
|
+
topFraction = (height - newHeight) * center.y / height;
|
|
39
|
+
heightFraction = newHeight / height;
|
|
40
|
+
}
|
|
41
|
+
return { leftFraction, topFraction, widthFraction, heightFraction };
|
|
42
|
+
}
|
|
43
|
+
observer: ResizeObserver = new ResizeObserver((val) => {
|
|
44
|
+
for (let t of val) {
|
|
45
|
+
if (t.target === this.holder) {
|
|
46
|
+
Querysub.serviceWriteDetached(() => {
|
|
47
|
+
let width = t.contentRect.width;
|
|
48
|
+
let height = t.contentRect.height;
|
|
49
|
+
this.state.width = width;
|
|
50
|
+
this.state.height = height;
|
|
51
|
+
let { widthFraction, heightFraction } = this.getSize();
|
|
52
|
+
let innerWidth = widthFraction * width;
|
|
53
|
+
let innerHeight = heightFraction * height;
|
|
54
|
+
this.props.onNewSize?.(innerWidth, innerHeight);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
componentWillUnmount() {
|
|
60
|
+
this.observer.disconnect();
|
|
61
|
+
}
|
|
62
|
+
render() {
|
|
63
|
+
let { leftFraction, topFraction, widthFraction, heightFraction } = this.getSize();
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
className={css.size("100%", "100%").position("relative", "soft") + " " + this.props.className}
|
|
68
|
+
key="maindiv"
|
|
69
|
+
ref2={(x) => {
|
|
70
|
+
if (this.holder) {
|
|
71
|
+
this.observer.unobserve(this.holder);
|
|
72
|
+
}
|
|
73
|
+
this.observer.observe(x);
|
|
74
|
+
this.holder = x;
|
|
75
|
+
}}
|
|
76
|
+
onClick={this.props.onClick}
|
|
77
|
+
>
|
|
78
|
+
<div className={this.props.innerClassName} style={{
|
|
79
|
+
position: "absolute",
|
|
80
|
+
left: `${leftFraction * 100}%`,
|
|
81
|
+
top: `${topFraction * 100}%`,
|
|
82
|
+
width: `${widthFraction * 100}%`,
|
|
83
|
+
height: `${heightFraction * 100}%`,
|
|
84
|
+
}}>{this.props.children}</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|