querysub 0.182.0 → 0.185.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.182.0",
3
+ "version": "0.185.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",
@@ -9,7 +9,6 @@
9
9
  "url": "https://github.com/sliftist/shard.git"
10
10
  },
11
11
  "dependencies": {
12
- "@sendgrid/mail": "^7.7.0",
13
12
  "@types/fs-ext": "^2.0.3",
14
13
  "@types/node-forge": "^1.3.1",
15
14
  "@types/pako": "^2.0.3",
@@ -23,8 +22,7 @@
23
22
  "js-sha512": "^0.9.0",
24
23
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
25
24
  "pako": "^2.1.0",
26
- "preact": "^10.11.3",
27
- "socket-function": "^0.130.0",
25
+ "socket-function": "^0.132.0",
28
26
  "terser": "^5.31.0",
29
27
  "typesafecss": "^0.22.0",
30
28
  "yaml": "^2.5.0",
@@ -56,4 +54,4 @@
56
54
  "resolutions": {
57
55
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
58
56
  }
59
- }
57
+ }
@@ -116,9 +116,9 @@ export function createX509(
116
116
 
117
117
  measureBlock(function sign() {
118
118
  if (issuer === "self") {
119
- certObj.sign(keyPair.privateKey, forge.md.sha256.create());
119
+ certObj.sign(keyPair.privateKey as any, forge.md.sha256.create());
120
120
  } else {
121
- certObj.sign(privateKeyFromPem(issuer.key.toString()), forge.md.sha256.create());
121
+ certObj.sign(privateKeyFromPem(issuer.key.toString()) as any, forge.md.sha256.create());
122
122
  }
123
123
  });
124
124
 
@@ -698,7 +698,7 @@ class TransactionLocker {
698
698
  logNodeStats(`archives|TΔ Delete Old Rejected File`, formatNumber, unconfirmedOldFiles2.length);
699
699
  // At the point the file was very old when we started reading, not part of the active transaction.
700
700
  for (let file of unconfirmedOldFiles2) {
701
- await this.deleteDataFile(file.file, `old unconfirmed file (${getOwnNodeId()}, ${process.argv[1]})`);
701
+ await this.deleteDataFile(file.file, `old unconfirmed file`);
702
702
  }
703
703
  }
704
704
  }
@@ -181,10 +181,7 @@ export interface WatcherOptions<Result> {
181
181
  // Temporary indicates after becoming synchronizes it will immediately dispose itself
182
182
  temporary?: boolean;
183
183
 
184
- // Allow returning proxies. Usually this would make the result unusable, but if the result is intended to
185
- // be used in another synced context this is fine.
186
- // - This is also faster, as it doesn't require a deep clone, and so in some cases this makes it
187
- // better, even if you don't have any proxies.
184
+ // MUCH faster for large data (which otherwise requires a deep clone), but if it does return a proxy, and you access it in non-commit, it will throw.
188
185
  allowProxyResults?: boolean;
189
186
 
190
187
  /** Prevents us from (ever) tracking this as a wasted evaluation.
@@ -411,7 +411,10 @@ function typeDefTypeToInternalType(
411
411
  } else if (typeDef.path.includes("boolean")) {
412
412
  baseDefault = false;
413
413
  }
414
- rootResult.defaultValue = recursiveFreeze(params[params.length - 1]?.callParams?.[0] ?? baseDefault);
414
+ //rootResult.defaultValue = recursiveFreeze(params[params.length - 1]?.callParams?.[0] ?? baseDefault);
415
+ // No longer recursively freeze it. This is okay for component singletons, and common. If users otherwise set the object to a default, and then mutate it... they are on their own.
416
+ // - Basically, we often store very large objects in state, under the expectation it is never traversed. While defaults might not be large, the expectation that it isn't traversed is still there, and if we traverse it and freeze it, we are likely to break things.
417
+ rootResult.defaultValue = params[params.length - 1]?.callParams?.[0] ?? baseDefault;
415
418
  }
416
419
  return results;
417
420
  }
@@ -1,5 +1,5 @@
1
1
  import type preact from "preact";
2
- import { compareArray, isNode, sort } from "socket-function/src/misc";
2
+ import { compareArray, isNode, sort, timeInSecond } from "socket-function/src/misc";
3
3
  import { isDeploy } from "../4-deploy/deployCheck";
4
4
  import { PermissionsCheck } from "../4-querysub/permissions";
5
5
  import { ClientWatcher, clientWatcher } from "../1-path-client/pathValueClientWatcher";
@@ -531,7 +531,11 @@ class QRenderClass {
531
531
  let state = instance.state;
532
532
  registerSchemaPrefix({ schema: state, prefixPathStr: statePath });
533
533
  watchDispose(this, () => {
534
- unregisterSchemaPrefix({ schema: state, prefixPathStr: statePath });
534
+ // Wait a bit to unregister, as there is no rush, and we want to allow any cleanup to occur that might still reference this state (which might break if we remove the schema).
535
+ // - Don't wait too long though, as if we wait an hour, then it could cause REALLY hard to debug bugs.
536
+ setTimeout(() => {
537
+ unregisterSchemaPrefix({ schema: state, prefixPathStr: statePath });
538
+ }, timeInSecond * 15);
535
539
  });
536
540
  } else {
537
541
  initialState = instance.state || {};
@@ -1107,6 +1111,22 @@ class QRenderClass {
1107
1111
  }
1108
1112
  }
1109
1113
 
1114
+ const contextCommit = (callback: () => void) => {
1115
+ void Promise.resolve().finally(() => {
1116
+ logErrors(proxyWatcher.commitFunction({
1117
+ canWrite: true,
1118
+ watchFunction() {
1119
+ QRenderClass.renderingComponentId = self.id;
1120
+ try {
1121
+ callback();
1122
+ } finally {
1123
+ QRenderClass.renderingComponentId = undefined;
1124
+ }
1125
+ },
1126
+ }));
1127
+ });
1128
+ };
1129
+
1110
1130
  // NOTE: This is a bit inefficient as it requires running twice, because the first run our child
1111
1131
  // components will have no rootDOMNodes. But... it's probably fine...
1112
1132
  QRenderClass.diffVNodes({
@@ -1205,7 +1225,8 @@ class QRenderClass {
1205
1225
  }
1206
1226
  if (isVNode(vNode)) {
1207
1227
  if (typeof vNode.ref === "function") {
1208
- vNode.ref(null);
1228
+ let ref = vNode.ref;
1229
+ contextCommit(() => ref(null));
1209
1230
  }
1210
1231
  }
1211
1232
  },
@@ -1315,22 +1336,6 @@ class QRenderClass {
1315
1336
  );
1316
1337
  }
1317
1338
 
1318
- const contextCommit = (callback: () => void) => {
1319
- void Promise.resolve().finally(() => {
1320
- logErrors(proxyWatcher.commitFunction({
1321
- canWrite: true,
1322
- watchFunction() {
1323
- QRenderClass.renderingComponentId = self.id;
1324
- try {
1325
- callback();
1326
- } finally {
1327
- QRenderClass.renderingComponentId = undefined;
1328
- }
1329
- },
1330
- }));
1331
- });
1332
- };
1333
-
1334
1339
  let props = self.instance.props as qreact.JSX.IntrinsicClassAttributes<any>;
1335
1340
  if (isFirstMount) {
1336
1341
  isFirstMount = false;
@@ -1672,7 +1677,7 @@ class QRenderClass {
1672
1677
  disposeWatchers.delete(this);
1673
1678
  for (let disposer of disposers) {
1674
1679
  try {
1675
- disposer();
1680
+ Querysub.commit(disposer);
1676
1681
  } catch (e: any) {
1677
1682
  console.error(`Error in dispose callback for ${this.debugName}, ${disposer.name}, ${e.stack}`);
1678
1683
  }
@@ -201,6 +201,8 @@ export class Querysub {
201
201
 
202
202
  public static now = getSyncedTime;
203
203
  public static time = getSyncedTime;
204
+ public static timeUnique = getSyncedTimeUnique;
205
+
204
206
  public static getCallTime = getSyncedTime;
205
207
  public static getFunctionCallTime = getSyncedTime;
206
208
  public static getCallId = () => Querysub.getCallerMachineId() + "_" + Querysub.getCallTime();
@@ -349,6 +351,13 @@ export class Querysub {
349
351
  }
350
352
  public static localCommit = Querysub.localRead;
351
353
  public static commitLocal = Querysub.localRead;
354
+ public static fastRead<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
355
+ return proxyWatcher.runOnce({
356
+ watchFunction: fnc,
357
+ ...options,
358
+ allowProxyResults: true,
359
+ });
360
+ }
352
361
  public static localRead<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
353
362
  return proxyWatcher.runOnce({
354
363
  watchFunction: fnc,
@@ -613,17 +622,20 @@ export class Querysub {
613
622
  paintTime = performance.now() - paintTime;
614
623
  // ALWAYS finish the profile, or we leak a lot of memory!
615
624
  let profile = measureObj.finish();
616
- if (paintTime > threshold && Object.keys(profile.entries).length > 0) {
625
+ let profiledTime = Object.values(profile.entries).reduce((a, b) => a + b.ownTime.sum, 0);
626
+ if (paintTime > threshold && profiledTime > threshold && Object.keys(profile.entries).length > 0) {
617
627
  console.log(`Slow paint time: ${formatTime(paintTime)} (${formatNumber(1000 / paintTime)}fps), profile is:`);
618
628
  logMeasureTable(profile, {
619
629
  minTimeToLog: 0,
620
630
  maxTableEntries: 100,
621
631
  mergeDepth: 2,
632
+ //thresholdInTable: 0.001,
622
633
  });
623
634
  logMeasureTable(profile, {
624
635
  minTimeToLog: 0,
625
636
  maxTableEntries: 100,
626
637
  mergeDepth: 3,
638
+ //thresholdInTable: 0.001,
627
639
  });
628
640
  }
629
641
  }
@@ -1045,6 +1057,17 @@ let initInterval = cache((interval: number) => {
1045
1057
  }
1046
1058
  }, interval);
1047
1059
  });
1060
+
1061
+ let lastTime = 0;
1062
+ function getSyncedTimeUnique() {
1063
+ let time = getSyncedTime();
1064
+ if (time <= lastTime) {
1065
+ time = addEpsilons(time, 1);
1066
+ }
1067
+ lastTime = time;
1068
+ return time;
1069
+ }
1070
+
1048
1071
  function getSyncedTime() {
1049
1072
  let call = getCurrentCallAllowUndefined();
1050
1073
  if (call) {
@@ -1136,4 +1159,5 @@ import { getEdgeBootstrapScript } from "../4-deploy/edgeBootstrap";
1136
1159
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
1137
1160
  import { css } from "../4-dom/css";
1138
1161
  import { getCountPerPaint } from "../functional/onNextPaint";
1162
+ import { addEpsilons } from "../bits";
1139
1163
 
@@ -12,6 +12,10 @@ export type ATagProps = (
12
12
  Omit<preact.JSX.HTMLAttributes<HTMLAnchorElement>, "href">
13
13
  & {
14
14
  values?: URLOverride[];
15
+
16
+ // Also selected if any of these combinations of values are visible
17
+ showAdditionalValues?: URLOverride[][];
18
+
15
19
  // TODO:
16
20
  /** For example: ["ArrowLeft", "ctrl+z", "shift+toggle"] */
17
21
  hotkeys?: string[];
@@ -34,6 +38,13 @@ export class ATag extends qreact.Component<ATagProps> {
34
38
  return <div {...props as any}>{children}</div>;
35
39
  }
36
40
  let isCurrent = values?.every(value => value.param.value === value.value);
41
+ if (this.props.showAdditionalValues) {
42
+ for (let additionalValues of this.props.showAdditionalValues) {
43
+ if (additionalValues.every(value => value.param.value === value.value)) {
44
+ isCurrent = true;
45
+ }
46
+ }
47
+ }
37
48
  let lightness = props.lightMode ? 40 : 80;
38
49
  return (
39
50
  <a
@@ -48,7 +48,6 @@ export type InputProps = (
48
48
  export class Input extends qreact.Component<InputProps> {
49
49
  state = {};
50
50
  onFocusText = "";
51
- firstFocus = true;
52
51
 
53
52
  elem: HTMLInputElement | null = null;
54
53
  lastValue: unknown = null;
@@ -97,22 +96,21 @@ export class Input extends qreact.Component<InputProps> {
97
96
  if (x) {
98
97
  this.elem = x;
99
98
  }
100
- if (x && props.focusOnMount && this.firstFocus) {
101
- this.firstFocus = false;
102
- setTimeout(() => {
103
- x.focus();
104
- // I'm not sure why, but this initial focus isn't trigger onFocus, so we have to
105
- // trigger it ourselves
106
- if (!props.noFocusSelect) {
107
- x.select();
108
- }
109
- }, 0);
110
- }
99
+
111
100
  let ref = props.inputRef || props.ref;
112
101
  if (typeof ref === "function") {
113
102
  ref(x);
114
103
  }
115
104
  },
105
+ ["ref2" as any]: (x: HTMLInputElement) => {
106
+ if (props.focusOnMount) {
107
+ x.focus();
108
+ if (!props.noFocusSelect) {
109
+ x.select();
110
+ }
111
+ }
112
+ (props as any).ref2?.(x);
113
+ },
116
114
  class: undefined,
117
115
  className: (
118
116
  (props.className || props.class || " ")
@@ -1,57 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import { lazy } from "socket-function/src/caching";
4
- import { isNode } from "socket-function/src/misc";
5
- import sendGrid from "@sendgrid/mail";
6
- import { addRecord } from "./dnsAuthority";
7
- import debugbreak from "debugbreak";
8
- import { getStorageDir } from "../fs";
9
- import { red } from "socket-function/src/formatting/logColors";
10
-
11
- let sendGridPath = lazy(() => {
12
- return getStorageDir() + "sendgrid.txt";
13
- });
14
-
15
- export const hasEmailSendPermissions = lazy(() => {
16
- if (!isNode()) return undefined;
17
- let path = sendGridPath();
18
- if (!fs.existsSync(path)) return undefined;
19
- let sendGridKey = fs.readFileSync(path, "utf8");
20
- sendGrid.setApiKey(sendGridKey);
21
- return sendGrid;
22
- });
23
-
24
- export async function sendEmail(config: {
25
- from: string;
26
- to: string[];
27
- subject: string;
28
- html: string;
29
- }) {
30
- console.log(red(`!!! Sending email to ${config.to.join(", ")}, from ${config.from}, subject: ${JSON.stringify(config.subject)}`));
31
- const sendGrid = hasEmailSendPermissions();
32
- if (!sendGrid) {
33
- throw new Error(`Sendgrid get not found at ${sendGridPath()}`);
34
- }
35
- await sendGrid.send(config);
36
- }
37
-
38
- // NOTE: Sending SMTP yourself is actually easy, but... the port will be blocked by all ISPs, and many
39
- // hosting providing, including digital ocean. So... we're just going to have to use a service to do it...
40
- // SPF
41
- // - Just adding a DNS entry for our IP?
42
- // Ah, so... add a TXT record to point to a subdomain
43
- // - Ex: "v=spf1 redirect=_spf.google.com"
44
- // - With the subdomain record looking like: "v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21 ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 ip4:216.239.32.0/19 -all"
45
- // - Maybe ~all, but, -all seems safer?
46
- // - This just makes it easier to update the record, as the redirect will be constant,
47
- // and then the subdomain value can be clobbered every time (as it will only have 1 TXT record)
48
- // DKIM
49
- // - Public key published in DNS
50
- // - Under [selector]._domainkey.[domain]
51
- // - Where selector is specified in the email
52
- // - And then we sign the email, and pass the signature with it (somehow)
53
- // DMARC
54
- // - A DNS entry specifying that we are using SPF and DKIM
55
- // - Fairly simple, even constant, but I'm not sure why I can't see it in gmail.com or perspectanalytics.com
56
- // TXT records. Oh well, we can probably just publish it ourselves
57
- // - https://www.cloudflare.com/learning/dns/dns-records/dns-dmarc-record/