querysub 0.696.0 → 0.698.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.696.0",
3
+ "version": "0.698.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",
@@ -80,7 +80,7 @@
80
80
  "pako": "^2.1.0",
81
81
  "peggy": "^5.0.6",
82
82
  "sliftutils": "^1.7.149",
83
- "socket-function": "^1.2.36",
83
+ "socket-function": "^1.3.0",
84
84
  "terser": "^5.31.0",
85
85
  "typenode": "^6.6.1",
86
86
  "typesafecss": "^0.32.0",
@@ -492,7 +492,6 @@ export class PathRouter {
492
492
  public static isLocalPath(path: string): boolean {
493
493
  return path.startsWith(LOCAL_DOMAIN_PATH);
494
494
  }
495
- @measureFnc
496
495
  public static isSelfAuthority(path: string): boolean {
497
496
  if (this.isLocalPath(path)) return true;
498
497
  let ourSpec = authorityLookup.getOurSpec();
@@ -15,7 +15,6 @@ import { isDefined } from "../misc";
15
15
 
16
16
  class ValidStateComputer {
17
17
 
18
- @measureFnc
19
18
  public ingestValuesAndValidStates(config: {
20
19
  pathValues: PathValue[];
21
20
  parentSyncs: { parentPath: string; sourceNodeId: string }[];
@@ -557,7 +557,6 @@ export class ClientWatcher {
557
557
  // - You can just fragment your writes into different components to isolate any slow loading parts, so partial
558
558
  // loading of data really isn't needed.
559
559
  // NOTE: Takes ownership of paths and parentPaths, so... don't mutate them after calling this!
560
- @measureFnc
561
560
  public setWatches(watchSpec: WatchSpec) {
562
561
  // NOTE: Yes, setWatches shows up as being slow in the profiler. I added measureBlock to every single section, and none of them showed up as being slow.
563
562
  // - My best guess is that after we clobber values in various lookups, but BEFORE we return, the destructor for those old resources runs. So their destruct time gets put in our profiler?
@@ -663,7 +662,6 @@ export class ClientWatcher {
663
662
  // NOTE: This synchronously predicts the values, so you don't need to wait. When the promise resolves
664
663
  // the values will be committed and they won't belost (although it is still possible for them to become
665
664
  // rejected).
666
- @measureFnc
667
665
  public setValues(
668
666
  config: {
669
667
  values: Map<string, Value>;
@@ -581,8 +581,7 @@ export class PathValueProxyWatcher {
581
581
  }
582
582
  };
583
583
 
584
- // TEMPORARY measure, remove once the catalog import's slowness is found.
585
- public getCallbackPathValue = measureWrap((pathStr: string, syncParentKeys?: "parentKeys"): PathValue | undefined => {
584
+ public getCallbackPathValue = ((pathStr: string, syncParentKeys?: "parentKeys"): PathValue | undefined => {
586
585
  const watcher = this.runningWatcher;
587
586
  if (!watcher) {
588
587
  debugger;
@@ -671,9 +670,8 @@ export class PathValueProxyWatcher {
671
670
  }
672
671
 
673
672
  return pathValue;
674
- }, "PathValueProxyWatcher|getCallbackPathValue");
675
- // TEMPORARY measure, remove once the catalog import's slowness is found.
676
- public getCallback = measureWrap((pathStr: string, syncParentKeys?: "parentKeys", readTransparent?: "readTransparent"): { value: unknown } | undefined => {
673
+ });
674
+ public getCallback = ((pathStr: string, syncParentKeys?: "parentKeys", readTransparent?: "readTransparent"): { value: unknown } | undefined => {
677
675
  if (this.runningWatcher && this.runningWatcher.evaluationGetCallbacks.length > 0) {
678
676
  for (let callback of this.runningWatcher.evaluationGetCallbacks) callback(pathStr);
679
677
  }
@@ -752,7 +750,7 @@ export class PathValueProxyWatcher {
752
750
  }
753
751
  return { value: readValue };
754
752
  }
755
- }, "PathValueProxyWatcher|getCallback");
753
+ });
756
754
 
757
755
  // We exclude undefined, BUT, we include "null", etc.
758
756
  // - This differs from javascript, but... due to how deletions work, we can't/don't differentiate between
@@ -762,8 +760,7 @@ export class PathValueProxyWatcher {
762
760
  return value?.value !== undefined;
763
761
  };
764
762
 
765
- // TEMPORARY measure, remove once the catalog import's slowness is found.
766
- public setCallback = measureWrap((pathStr: string, value: unknown, inRecursion = false, allowSpecial = false): void => {
763
+ public setCallback = ((pathStr: string, value: unknown, inRecursion = false, allowSpecial = false): void => {
767
764
  if (this.runningWatcher && this.runningWatcher.evaluationSetCallbacks.length > 0) {
768
765
  for (let callback of this.runningWatcher.evaluationSetCallbacks) callback(pathStr);
769
766
  }
@@ -939,10 +936,9 @@ export class PathValueProxyWatcher {
939
936
  }
940
937
  watcher.pendingWrites.set(pathStr, value);
941
938
  }
942
- }, "PathValueProxyWatcher|setCallback");
939
+ });
943
940
  /** Syncs keys AND values (as we won't return a key for a value that is undefined). */
944
- // TEMPORARY measure, remove once the catalog import's slowness is found.
945
- public getKeys = measureWrap((pathStr: string): string[] => {
941
+ public getKeys = ((pathStr: string): string[] => {
946
942
  if (this.runningWatcher && this.runningWatcher.evaluationGetKeysCallbacks.length > 0) {
947
943
  for (let callback of this.runningWatcher.evaluationGetKeysCallbacks) callback(pathStr);
948
944
  }
@@ -1026,7 +1022,7 @@ export class PathValueProxyWatcher {
1026
1022
  keysArray.sort();
1027
1023
 
1028
1024
  return keysArray;
1029
- }, "PathValueProxyWatcher|getKeys");
1025
+ });
1030
1026
 
1031
1027
  private getSymbol = (pathStr: string, symbol: symbol): { value: unknown } | undefined => {
1032
1028
  if (symbol === Symbol.toPrimitive) return {
@@ -144,6 +144,10 @@ export async function getGitDiff(gitDir = "."): Promise<string> {
144
144
  return diff;
145
145
  }
146
146
 
147
+ export async function gitPull(gitDir = ".") {
148
+ await runGitCommand(`git pull`, { cwd: gitDir });
149
+ }
150
+
147
151
  export async function getLatestRefOnUpstreamBranch(gitDir = ".") {
148
152
  await runGitCommand(`git fetch`, { cwd: gitDir });
149
153
  return (await runGitCommand(`git rev-parse @{upstream}`, { cwd: gitDir })).trim();
@@ -108,7 +108,6 @@ export function confirmNoOverlapDeploy(actionLabel: string, onConfirm: () => voi
108
108
 
109
109
  const CONFIRM_LIST_MAX_HEIGHT = 220;
110
110
 
111
- /** Deploying every outdated service restarts all of them at once, which is most of the cluster — worth naming what is about to restart before it happens. */
112
111
  class DeployAllConfirmModal extends qreact.Component<{ titles: string[]; latestRef: string; onConfirm: () => void }> {
113
112
  render() {
114
113
  let count = this.props.titles.length;
@@ -149,16 +148,14 @@ export class UpdateButtons extends qreact.Component<{
149
148
  state = t.state({
150
149
  isDeploying: t.type(false),
151
150
  });
152
- // Deploys all outdated services to latestRef, always immediately. The overlap is what keeps the service up - the old instances keep running for it, so connections have somewhere to go while clients move across.
153
- private deployAll(outdatedServices: ServiceConfig[], latestRef: string) {
151
+ private deployAll(outdatedServices: ServiceConfig[], latestRef: string, noOverlap: boolean) {
154
152
  this.state.isDeploying = true;
155
153
  // Props are synchronized state, so everything the async work needs is cloned into plain locals HERE, in the synced part
156
154
  let toDeploy = outdatedServices.map(service => {
157
155
  let updated = deepCloneJSON(service);
158
156
  updated.parameters.gitRef = latestRef;
159
157
  updated.parameters.releaseTime = Date.now();
160
- // Always set, never inherited: the overlap belongs to the deploy being made, and leaving the previous deploy's value in place means one no-overlap deploy silently makes every later deploy a no-overlap one.
161
- updated.parameters.overlapTime = DEFAULT_OVERLAP_TIME;
158
+ updated.parameters.overlapTime = noOverlap ? 0 : DEFAULT_OVERLAP_TIME;
162
159
  return updated;
163
160
  });
164
161
  let controller = MachineServiceController(SocketFunction.browserNodeId());
@@ -232,7 +229,7 @@ export class UpdateButtons extends qreact.Component<{
232
229
  confirmDeployAll({
233
230
  titles: outdatedServices.map(service => service.info.title),
234
231
  latestRef: gitInfo.latestRef,
235
- onConfirm: () => this.deployAll(outdatedServices, gitInfo.latestRef),
232
+ onConfirm: () => this.deployAll(outdatedServices, gitInfo.latestRef, false),
236
233
  });
237
234
  }}
238
235
  >
@@ -248,6 +245,20 @@ export class UpdateButtons extends qreact.Component<{
248
245
  <RenderGitRefInfo gitRef={ref} />
249
246
  </div>)}
250
247
  </button>
248
+ <button
249
+ className={buttonStyle.hsl(0, 85, 70)}
250
+ disabled={this.state.isDeploying}
251
+ title="Deploys now AND shuts the old instances down as soon as the new ones are up (overlap of 0)"
252
+ onClick={() => {
253
+ confirmNoOverlapDeploy(`Deploy All (${outdatedServices.length}), No Overlap`, () => {
254
+ this.deployAll(outdatedServices, gitInfo.latestRef, true);
255
+ });
256
+ }}
257
+ >
258
+ <div>
259
+ {bigEmoji("⚡")} <span>{this.state.isDeploying && "⏳ Deploying..." || `Deploy All (${outdatedServices.length}), No Overlap`}</span>
260
+ </div>
261
+ </button>
251
262
  </>}
252
263
  </>;
253
264
  }
package/src/errors.ts CHANGED
@@ -121,6 +121,24 @@ export function errorify(error: any, messageOverride?: string) {
121
121
  return errorObj;
122
122
  }
123
123
 
124
+ const STACK_FRAME_PREFIX = /^(\s*)at\s+/;
125
+ const STACK_FRAME_POSITION = /:(\d+):(\d+)\)?\s*$/;
126
+
127
+ export function filterErrorStack(error: unknown): string {
128
+ let stack = errorify(error).stack || "";
129
+ return stack.split("\n").map(filterStackFrame).join("\n");
130
+ }
131
+
132
+ function filterStackFrame(line: string): string {
133
+ let prefix = STACK_FRAME_PREFIX.exec(line);
134
+ if (!prefix) return line;
135
+ let name = line.slice(prefix[0].length).split(" (")[0].trim();
136
+ if (name.includes("://")) name = "";
137
+ let position = STACK_FRAME_POSITION.exec(line);
138
+ if (!position) return `${prefix[1]}at ${name}`;
139
+ return `${prefix[1]}at ${name && name + " " || ""}(${position[1]}:${position[2]})`;
140
+ }
141
+
124
142
  export function assertValue<T>(value: T | undefined | null, message?: string): T {
125
143
  if (value === undefined || value === null) {
126
144
  throw new Error(`Value is ${value === undefined ? "undefined" : "null"}, ${message}`);
@@ -1,7 +1,7 @@
1
1
  import { css } from "typesafecss";
2
2
  import { qreact } from "../4-dom/qreact";
3
3
  import { Querysub } from "../4-querysub/Querysub";
4
- import { sort, timeInSecond } from "socket-function/src/misc";
4
+ import { sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
5
5
  import { formatTime } from "socket-function/src/formatting/format";
6
6
  import { isCurrentUserSuperUser } from "../user-implementation/userData";
7
7
  import type { EdgeNodeConfig, EdgeNodeStat } from "../4-deploy/edgeNodes";
@@ -12,8 +12,7 @@ const EDGE_NODE_URL_PARAM = "edgenode";
12
12
  export class EdgeNodeSelector extends qreact.Component<{}> {
13
13
  render() {
14
14
  if (!(isCurrentUserSuperUser() || location.hostname.startsWith("127-0-0-1"))) return undefined;
15
- // The stats are a plain global the bootstrapper updates as its probes finish, so we re-read them every second
16
- Querysub.nowDelayed(timeInSecond);
15
+ Querysub.nowDelayed(timeInMinute);
17
16
  let stats = globalThis.EDGE_NODE_STATS;
18
17
  const booted = (globalThis as any).BOOTED_EDGE_NODE as EdgeNodeConfig | undefined;
19
18
  if (!booted) return undefined;
@@ -50,7 +50,6 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
50
50
  render() {
51
51
  let { ...props } = { ...this.props };
52
52
  let label = props.label || this.props.children;
53
- (this.props as any).title = this.props.tooltip;
54
53
  if (props.fontSize !== undefined) {
55
54
  props.style = { ...props.style as any, fontSize: props.fontSize };
56
55
  }
@@ -152,7 +151,7 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
152
151
  </span>;
153
152
  }
154
153
  return (
155
- <label onClick={onClick} class={
154
+ <label onClick={onClick} title={props.tooltip} class={
156
155
  css.hbox(props.checkbox ? CHECKBOX_LABEL_GAP : LABEL_GAP).button.relative
157
156
  + " trigger-hover "
158
157
  + props.outerClass
@@ -29,6 +29,7 @@ export class SimpleNotification extends qreact.Component<{
29
29
  seqNum = nextSeqNum++;
30
30
  render() {
31
31
  let { type, title, children, usesClickEvents } = this.props;
32
+
32
33
  let color = (
33
34
  type === "info" && { h: 188, s: 52 }
34
35
  || type === "warn" && { h: 50, s: 100 }
@@ -51,7 +52,10 @@ export class SimpleNotification extends qreact.Component<{
51
52
  + " " + animClassName
52
53
  }
53
54
  onClick={() => {
54
- void navigator.clipboard.writeText(title);
55
+ let copyText = this.props.tooltip || title;
56
+ Querysub.onCommitFinished(() => {
57
+ void navigator.clipboard.writeText(copyText);
58
+ });
55
59
  if (!usesClickEvents) {
56
60
  closeCurrentNotification();
57
61
  }
@@ -235,7 +235,7 @@ function loadParamsFromURL() {
235
235
  }
236
236
 
237
237
  function syncStateToStorage() {
238
- Querysub.createWriteWatcher(function syncStateToURL() {
238
+ Querysub.createWriteWatcher(function syncStateToStorage() {
239
239
  for (let [key, value] of Object.entries(data().params)) {
240
240
  if (localStorageKeys.has(key)) {
241
241
  localStorage.setItem(key, JSON.stringify(value));
@@ -4,6 +4,7 @@ import { SimpleNotification } from "./SimpleNotification";
4
4
  import { qreact } from "../4-dom/qreact";
5
5
  import { Querysub } from "../4-querysub/Querysub";
6
6
  import { css } from "../4-dom/css";
7
+ import { filterErrorStack } from "../errors";
7
8
 
8
9
  function onUncaught(...args: unknown[]) {
9
10
  let error = args[4] as Error;
@@ -66,7 +67,7 @@ function onMessage(config: {
66
67
  notification: <SimpleNotification
67
68
  type={config.type}
68
69
  title={title}
69
- tooltip={config.stack}
70
+ tooltip={filterErrorStack(config.stack)}
70
71
  >
71
72
  {/* NOTE: I've stopping showing this info. It's annoying, as they can just look in the console
72
73
  (as most of these come from console.error anyways). */}