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.
Files changed (65) hide show
  1. package/package.json +6 -6
  2. package/src/-b-authorities/cloudflareHelpers.ts +11 -2
  3. package/src/3-path-functions/PathFunctionRunner.ts +168 -97
  4. package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
  5. package/src/3-path-functions/pathFunctionLoader.ts +11 -6
  6. package/src/3-path-functions/syncSchema.ts +10 -1
  7. package/src/4-deploy/edgeBootstrap.ts +10 -1
  8. package/src/4-querysub/Querysub.ts +77 -3
  9. package/src/4-querysub/QuerysubController.ts +22 -2
  10. package/src/4-querysub/permissions.ts +33 -2
  11. package/src/4-querysub/querysubPrediction.ts +52 -18
  12. package/src/archiveapps/archiveGCEntry.tsx +38 -0
  13. package/src/archiveapps/archiveJoinEntry.ts +121 -0
  14. package/src/archiveapps/archiveMergeEntry.tsx +47 -0
  15. package/src/archiveapps/compressTest.tsx +59 -0
  16. package/src/archiveapps/lockTest.ts +127 -0
  17. package/src/config.ts +5 -0
  18. package/src/diagnostics/managementPages.tsx +55 -0
  19. package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
  20. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
  21. package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
  22. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
  23. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
  24. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
  25. package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
  26. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
  27. package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
  28. package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
  29. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
  30. package/src/email/postmark.tsx +40 -0
  31. package/src/email/sendgrid.tsx +44 -0
  32. package/src/functional/UndoWatch.tsx +133 -0
  33. package/src/functional/diff.ts +858 -0
  34. package/src/functional/promiseCache.ts +67 -0
  35. package/src/functional/random.ts +9 -0
  36. package/src/functional/runCommand.ts +42 -0
  37. package/src/functional/runOnce.ts +7 -0
  38. package/src/functional/stats.ts +61 -0
  39. package/src/functional/throttleRerender.tsx +80 -0
  40. package/src/library-components/AspectSizedComponent.tsx +88 -0
  41. package/src/library-components/Histogram.tsx +338 -0
  42. package/src/library-components/InlinePopup.tsx +67 -0
  43. package/src/library-components/Notifications.tsx +153 -0
  44. package/src/library-components/RenderIfVisible.tsx +80 -0
  45. package/src/library-components/SimpleNotification.tsx +133 -0
  46. package/src/library-components/TabbedUI.tsx +39 -0
  47. package/src/library-components/animateAnyElement.tsx +65 -0
  48. package/src/library-components/errorNotifications.tsx +81 -0
  49. package/src/library-components/placeholder.ts +18 -0
  50. package/src/misc/format2.ts +48 -0
  51. package/src/misc.ts +33 -0
  52. package/src/misc2.ts +5 -0
  53. package/src/server.ts +2 -1
  54. package/src/storage/diskCache.ts +227 -0
  55. package/src/storage/diskCache2.ts +122 -0
  56. package/src/storage/fileSystemPointer.ts +72 -0
  57. package/src/user-implementation/LoginPage.tsx +78 -0
  58. package/src/user-implementation/RequireAuditPage.tsx +219 -0
  59. package/src/user-implementation/SecurityPage.tsx +212 -0
  60. package/src/user-implementation/UserPage.tsx +320 -0
  61. package/src/user-implementation/addSuperUser.ts +21 -0
  62. package/src/user-implementation/canSeeSource.ts +41 -0
  63. package/src/user-implementation/loginEmail.tsx +159 -0
  64. package/src/user-implementation/setEmailKey.ts +20 -0
  65. 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,7 @@
1
+
2
+ let runLookup: { [guid: string]: boolean } = (globalThis as any).runLookup = (globalThis as any).runLookup || {};
3
+ export function runOnce(guid: string) {
4
+ if (runLookup[guid]) return true;
5
+ runLookup[guid] = true;
6
+ return false;
7
+ }
@@ -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
+ }