querysub 0.474.0 → 0.476.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/.claude/settings.local.json +2 -1
- package/package.json +6 -4
- package/src/-c-identity/IdentityController.ts +1 -1
- package/src/-h-path-value-serialize/PathValueSerializer.ts +1 -1
- package/src/-h-path-value-serialize/stringSerializer.ts +1 -1
- package/src/0-path-value-core/PathRouter.ts +1 -1
- package/src/0-path-value-core/pathValueCore.ts +2 -10
- package/src/2-proxy/PathValueProxyWatcher.ts +6 -2
- package/src/3-path-functions/PathFunctionHelpers.ts +2 -1
- package/src/4-deploy/git.ts +40 -6
- package/src/4-querysub/Querysub.ts +1 -1
- package/src/4-querysub/querysubPrediction.ts +1 -1
- package/src/deployManager/machineApplyMainCode.ts +45 -14
- package/src/diagnostics/logs/diskLogger.ts +1 -1
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +1 -1
- package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +1 -1
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +1 -1
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +1 -1
- package/src/library-components/Histogram.tsx +1 -1
- package/src/library-components/SyncedController.ts +31 -6
- package/src/storage/diskCache.ts +1 -1
- package/src/zipThreaded.ts +1 -1
- package/src/bits.ts +0 -105
- package/src/buffers.ts +0 -69
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.476.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",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"mcp": "yarn typenode ./src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts",
|
|
24
24
|
"mc": "yarn typenode ./src/diagnostics/logs/IndexedLogs/MCPIndexedLogsEntry.ts --cwd D:/repos/qs-cyoa/",
|
|
25
25
|
"mcp2": "yarn typenode ./src/diagnostics/debugger/mcp-server.ts",
|
|
26
|
-
"mc2": "yarn typenode ./src/diagnostics/debugger/mcp-server.ts --cwd D:/repos/qs-cyoa/"
|
|
26
|
+
"mc2": "yarn typenode ./src/diagnostics/debugger/mcp-server.ts --cwd D:/repos/qs-cyoa/",
|
|
27
|
+
"ssh-a-claude": "ssh root@a.querysubtest.com"
|
|
27
28
|
},
|
|
28
29
|
"bin": {
|
|
29
30
|
"deploy": "./bin/deploy.js",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
67
68
|
"pako": "^2.1.0",
|
|
68
69
|
"peggy": "^5.0.6",
|
|
69
|
-
"socket-function": "^1.1.
|
|
70
|
+
"socket-function": "^1.1.40",
|
|
70
71
|
"terser": "^5.31.0",
|
|
71
72
|
"typesafecss": "^0.29.0",
|
|
72
73
|
"yaml": "^2.5.0",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"typedev": "^0.3.0"
|
|
82
83
|
},
|
|
83
84
|
"resolutions": {
|
|
84
|
-
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
|
|
85
|
+
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
86
|
+
"socket-function": "^1.1.40"
|
|
85
87
|
}
|
|
86
88
|
}
|
|
@@ -10,7 +10,7 @@ import { CallerContext } from "socket-function/SocketFunctionTypes";
|
|
|
10
10
|
import { cache, cacheWeak, lazy } from "socket-function/src/caching";
|
|
11
11
|
import { getClientNodeId, getNodeId, getNodeIdDomain, getNodeIdIP, getNodeIdLocation, isClientNodeId } from "socket-function/src/nodeCache";
|
|
12
12
|
import { decodeNodeId, getCommonName, getIdentityCA, getMachineId, getOwnMachineId, getPublicIdentifier, getThreadKeyCert, parseCert, sign, validateCertificate, verify } from "../-a-auth/certs";
|
|
13
|
-
import { getShortNumber } from "
|
|
13
|
+
import { getShortNumber } from "socket-function/src/bits";
|
|
14
14
|
import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
15
15
|
import { timeoutToError } from "../errors";
|
|
16
16
|
import { delay } from "socket-function/src/batching";
|
|
@@ -7,7 +7,7 @@ import { compare, recursiveFreeze } from "socket-function/src/misc";
|
|
|
7
7
|
import { MaybePromise } from "socket-function/src/types";
|
|
8
8
|
import { StringSerialize } from "./stringSerializer";
|
|
9
9
|
import { Zip } from "../zip";
|
|
10
|
-
import { asBuffer, asInt32, asUint32 } from "
|
|
10
|
+
import { asBuffer, asInt32, asUint32 } from "socket-function/src/buffers";
|
|
11
11
|
import { setFlag } from "socket-function/require/compileFlags";
|
|
12
12
|
|
|
13
13
|
import cbor from "cbor-x";
|
|
@@ -11,7 +11,7 @@ import { getRoutingOverride, hasPrefixHash } from "./PathRouterRouteOverride";
|
|
|
11
11
|
import { sha256 } from "js-sha256";
|
|
12
12
|
import { rangesOverlap, removeRange } from "../rangeMath";
|
|
13
13
|
import { decodeParentFilter } from "./hackedPackedPathParentFiltering";
|
|
14
|
-
import { getBufferInt } from "
|
|
14
|
+
import { getBufferInt } from "socket-function/src/bits";
|
|
15
15
|
|
|
16
16
|
import { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH } from "./PathRouterConstants";
|
|
17
17
|
export { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { cacheLimited, lazy } from "socket-function/src/caching";
|
|
3
|
-
import { addEpsilons, minusEpsilon } from "
|
|
3
|
+
import { addEpsilons, getTimeUnique, minusEpsilon } from "socket-function/src/bits";
|
|
4
4
|
import { logErrors } from "../errors";
|
|
5
5
|
import { appendToPathStr, getParentPathStr, getPathDepth, getPathIndexAssert, hack_getPackedPathSuffix, hack_setPackedPathSuffix, hack_stripPackedPath } from "../path";
|
|
6
6
|
import { measureFnc, measureWrap } from "socket-function/src/profiling/measure";
|
|
@@ -352,15 +352,7 @@ export function compareTime(lhs: Time, rhs: Time) {
|
|
|
352
352
|
export function getCreatorId() {
|
|
353
353
|
return IdentityController_getOwnPubKeyShort();
|
|
354
354
|
}
|
|
355
|
-
|
|
356
|
-
export function getTimeUnique() {
|
|
357
|
-
let time = Date.now();
|
|
358
|
-
if (time <= prevTime) {
|
|
359
|
-
time = addEpsilons(prevTime, 1);
|
|
360
|
-
}
|
|
361
|
-
prevTime = time;
|
|
362
|
-
return time;
|
|
363
|
-
}
|
|
355
|
+
|
|
364
356
|
|
|
365
357
|
export let debugRejections = false;
|
|
366
358
|
export function enableDebugRejections() {
|
|
@@ -7,13 +7,13 @@ import { cache, cacheLimited, lazy } from "socket-function/src/caching";
|
|
|
7
7
|
import { delay, runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
8
8
|
import { errorify, logErrors } from "../errors";
|
|
9
9
|
import { appendToPathStr, getLastPathPart, getParentPathStr, getPathDepth, getPathFromStr, getPathIndex, getPathIndexAssert, getPathPrefix, getPathStr1, getPathStr2, getPathSuffix, slicePathStrToDepth } from "../path";
|
|
10
|
-
import { addEpsilons } from "
|
|
10
|
+
import { addEpsilons, getTimeUnique } from "socket-function/src/bits";
|
|
11
11
|
import { getThreadKeyCert } from "../-a-auth/certs";
|
|
12
12
|
import { ActionsHistory } from "../diagnostics/ActionsHistory";
|
|
13
13
|
import { registerResource } from "../diagnostics/trackResources";
|
|
14
14
|
import { ClientWatcher, WatchSpecData, clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
15
15
|
import { createPathValueProxy, getPathFromProxy, getProxyPath, getProxyPathAndWatch, isValueProxy, isValueProxy2 } from "./pathValueProxy";
|
|
16
|
-
import { authorityStorage, compareTime, ReadLock, epochTime, getNextTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time, getCreatorId,
|
|
16
|
+
import { authorityStorage, compareTime, ReadLock, epochTime, getNextTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time, getCreatorId, debugTime } from "../0-path-value-core/pathValueCore";
|
|
17
17
|
import { runCodeWithDatabase, rawSchema } from "./pathDatabaseProxyBase";
|
|
18
18
|
import debugbreak from "debugbreak";
|
|
19
19
|
import { pathValueCommitter } from "../0-path-value-core/PathValueController";
|
|
@@ -1957,6 +1957,10 @@ export class PathValueProxyWatcher {
|
|
|
1957
1957
|
public runOnce<Result = void>(
|
|
1958
1958
|
options: Omit<WatcherOptions<Result>, "onResultUpdated" | "onWriteCommitted">
|
|
1959
1959
|
): Result {
|
|
1960
|
+
// TODO: This is a recent change. We maybe should apply the user pass and options. However, we definitely don't want to call create watcher as that will apply our additional options, making it temporary, where this might actually be run inside of a non-temporary watcher like a render function. Which finally will break on commit finish because it'll think it's in a temporary function and subscribe to the wrong callback.
|
|
1961
|
+
if (this.inWatcher()) {
|
|
1962
|
+
return options.watchFunction();
|
|
1963
|
+
}
|
|
1960
1964
|
let result: { result: Result } | { error: string } | undefined;
|
|
1961
1965
|
let watcher = this.createWatcher({
|
|
1962
1966
|
...options,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
2
2
|
import { atomicObjectWrite, proxyWatcher, specialObjectWriteSymbol } from "../2-proxy/PathValueProxyWatcher";
|
|
3
3
|
import { pathValueCommitter } from "../0-path-value-core/PathValueController";
|
|
4
|
-
import { getNextTime
|
|
4
|
+
import { getNextTime } from "../0-path-value-core/pathValueCore";
|
|
5
5
|
import { FunctionSpec, functionSchema, CallSpec, commitCall } from "./PathFunctionRunner";
|
|
6
6
|
import { logErrors } from "../errors";
|
|
7
7
|
import { FunctionMetadata, getCallIdOverride } from "./syncSchema";
|
|
@@ -21,6 +21,7 @@ import { PathRouter } from "../0-path-value-core/PathRouter";
|
|
|
21
21
|
import { getPathFromProxy } from "../2-proxy/pathValueProxy";
|
|
22
22
|
import { getDomain, isPublic } from "../config";
|
|
23
23
|
import { getFncFilter, isServer } from "../config2";
|
|
24
|
+
import { getTimeUnique } from "socket-function/src/bits";
|
|
24
25
|
|
|
25
26
|
// NOTE: We could deploy single functions, but... we will almost always be updating all functions at
|
|
26
27
|
// once, because keeping everything on the same git hash reduces a lot of potential bugs.
|
package/src/4-deploy/git.ts
CHANGED
|
@@ -126,18 +126,52 @@ export const setGitRef = measureWrap(async function setGitRef(config: {
|
|
|
126
126
|
gitRef: string;
|
|
127
127
|
}) {
|
|
128
128
|
await fs.promises.mkdir(config.gitFolder, { recursive: true });
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
// ssh-keyscan is a network call; a transient failure here must not abort the sync. The host key is normally already in known_hosts from machine setup, and callers only escalate to clobbering the checkout on *repo* failures — not on a momentary inability to refresh a host key.
|
|
130
|
+
try {
|
|
131
|
+
let hostKey = await runPromise(`ssh-keyscan -t rsa bitbucket.org`);
|
|
132
|
+
hostKey = hostKey.split("\n").filter(x => !x.startsWith("#")).join("\n");
|
|
133
|
+
let knownHostsPath = os.homedir() + "/.ssh/known_hosts";
|
|
134
|
+
if (hostKey && (!fs.existsSync(knownHostsPath) || !fs.readFileSync(knownHostsPath).toString().includes(hostKey))) {
|
|
135
|
+
fs.appendFileSync(knownHostsPath, "\n" + hostKey + "\n");
|
|
136
|
+
}
|
|
137
|
+
} catch (e: any) {
|
|
138
|
+
console.warn(`ssh-keyscan for bitbucket.org failed, continuing: ${e.stack ?? e}`);
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
await runPromise(`git remote update`, { cwd: config.gitFolder });
|
|
137
142
|
await runPromise(`git add --all`, { cwd: config.gitFolder });
|
|
138
|
-
|
|
143
|
+
// nothrow: on a freshly re-initialized repo (rebuildGitFolder) there is no initial commit yet, so `git stash` errors. That's harmless — the `git reset --hard` below is what actually forces the working tree to the target ref.
|
|
144
|
+
await runPromise(`git stash`, { cwd: config.gitFolder, nothrow: true });
|
|
139
145
|
await runPromise(`git fetch --all`, { cwd: config.gitFolder });
|
|
140
146
|
await runPromise(`git reset --hard ${config.gitRef}`, { cwd: config.gitFolder });
|
|
141
147
|
// Allows us to remove deleted objects from storage, ex, if we accidentally commit a 1GB, this deletes it, so we don't have to fix each server individually. Also I think the repo breaks if we go too long without pruning it?
|
|
142
148
|
await runPromise(`git prune`, { cwd: config.gitFolder });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/** Recovers a broken repo WITHOUT disturbing the working-tree files. We delete only .git, re-init, re-add the remote, then let setGitRef's `git reset --hard` reconcile the tracked files to the ref. Untracked / gitignored files (notably node_modules, which is expensive to reinstall and may be open in a running service) are left exactly as they are. When the directory is empty this produces the same result as a fresh clone. */
|
|
152
|
+
export const rebuildGitFolder = measureWrap(async function rebuildGitFolder(config: {
|
|
153
|
+
gitFolder: string;
|
|
154
|
+
repoUrl: string;
|
|
155
|
+
gitRef: string;
|
|
156
|
+
}) {
|
|
157
|
+
await fs.promises.mkdir(config.gitFolder, { recursive: true });
|
|
158
|
+
await fs.promises.rm(config.gitFolder + ".git", { recursive: true, force: true });
|
|
159
|
+
await runPromise(`git init`, { cwd: config.gitFolder });
|
|
160
|
+
await runPromise(`git remote add origin ${config.repoUrl}`, { cwd: config.gitFolder });
|
|
161
|
+
await setGitRef({ gitFolder: config.gitFolder, gitRef: config.gitRef });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** Last-resort recovery for a working tree git can't reconcile (rebuildGitFolder failed). Clones fresh into a sibling temp directory and swaps it in only after the clone and ref-set succeed, so a failed/interrupted clone can never leave the live folder empty — the failure mode that previously stranded running services with a deleted node_modules. This necessarily discards node_modules; the caller reinstalls it. */
|
|
165
|
+
export const nuclearReclone = measureWrap(async function nuclearReclone(config: {
|
|
166
|
+
gitFolder: string;
|
|
167
|
+
repoUrl: string;
|
|
168
|
+
gitRef: string;
|
|
169
|
+
}) {
|
|
170
|
+
let target = config.gitFolder.replace(/[\/\\]+$/, "");
|
|
171
|
+
let tempDir = target + ".reclone-" + Date.now();
|
|
172
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
173
|
+
await runPromise(`git clone ${config.repoUrl} ${tempDir}`);
|
|
174
|
+
await setGitRef({ gitFolder: tempDir + "/", gitRef: config.gitRef });
|
|
175
|
+
await fs.promises.rm(target, { recursive: true, force: true });
|
|
176
|
+
await fs.promises.rename(tempDir, target);
|
|
143
177
|
});
|
|
@@ -1349,7 +1349,7 @@ import "../diagnostics/watchdog";
|
|
|
1349
1349
|
import "../diagnostics/trackResources";
|
|
1350
1350
|
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
1351
1351
|
import { getCountPerPaint } from "../functional/onNextPaint";
|
|
1352
|
-
import { addEpsilons } from "
|
|
1352
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
1353
1353
|
import { blue, green, magenta } from "socket-function/src/formatting/logColors";
|
|
1354
1354
|
import { MachineController } from "../deployManager/machineController";
|
|
1355
1355
|
import { getRecords, setRecord } from "../-b-authorities/dnsAuthority";
|
|
@@ -20,7 +20,7 @@ import { PromiseObj, isNode } from "socket-function/src/misc";
|
|
|
20
20
|
import { isPublic } from "../config";
|
|
21
21
|
import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
22
22
|
import { cacheAsyncLimitedJSON } from "../functional/promiseCache";
|
|
23
|
-
import { addEpsilons } from "
|
|
23
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
24
24
|
import { delay } from "socket-function/src/batching";
|
|
25
25
|
setFlag(require, "cbor-x", "allowclient", true);
|
|
26
26
|
const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
|
|
@@ -13,12 +13,12 @@ import { formatTime } from "socket-function/src/formatting/format";
|
|
|
13
13
|
import { sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
14
14
|
import { isDefined } from "../misc";
|
|
15
15
|
import { logLoadTime } from "../logModuleLoadTimes";
|
|
16
|
-
import { delay, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
16
|
+
import { delay, retryFunctional, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
17
17
|
import os from "os";
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import { spawn, ChildProcess } from "child_process";
|
|
20
20
|
import { lazy } from "socket-function/src/caching";
|
|
21
|
-
import { getGitRefLive, getGitURLLive, setGitRef } from "../4-deploy/git";
|
|
21
|
+
import { getGitRefLive, getGitURLLive, setGitRef, rebuildGitFolder, nuclearReclone } from "../4-deploy/git";
|
|
22
22
|
import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
23
23
|
import { shutdown } from "../diagnostics/periodic";
|
|
24
24
|
import { onServiceConfigChange, triggerRollingUpdate } from "./machineController";
|
|
@@ -607,24 +607,53 @@ const killScreen = measureWrap(async function killScreen(config: {
|
|
|
607
607
|
await runPromise(`${prefix}tmux kill-session -t ${config.screenName}`);
|
|
608
608
|
await removeOldNodeId(config.screenName);
|
|
609
609
|
});
|
|
610
|
+
// When a present repo fails to sync, retry a few times with backoff before deciding it is actually broken — most failures are transient network blips (ssh-keyscan / fetch), and reacting to those by clobbering the checkout is what stranded running services (they kept running but crashed on the next lazy `require("ws")` once node_modules was gone).
|
|
611
|
+
const GIT_SYNC_MAX_RETRIES = 4;
|
|
612
|
+
const GIT_SYNC_RETRY_MIN_DELAY = timeInSecond * 2;
|
|
613
|
+
const GIT_SYNC_RETRY_MAX_DELAY = timeInSecond * 30;
|
|
614
|
+
|
|
610
615
|
const ensureGitSynced = measureWrap(async function ensureGitSynced(config: {
|
|
611
|
-
//
|
|
612
|
-
// - Maybe ask the AI the best way to forcefully switch a git repo.
|
|
616
|
+
// Forces the checkout at gitFolder to gitRef. Escalates through increasingly destructive recoveries, but only ever destroys the working tree as an absolute last resort — a network blip must never wipe node_modules out from under a running service.
|
|
613
617
|
gitFolder: string;
|
|
614
618
|
repoUrl: string;
|
|
615
619
|
gitRef: string;
|
|
616
620
|
}) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
621
|
+
let hasGit = await fsExistsAsync(config.gitFolder + ".git");
|
|
622
|
+
|
|
623
|
+
// Repo present: sync to the ref, retrying transient failures with backoff. We do NOT clobber the checkout just because a network call blipped.
|
|
624
|
+
if (hasGit) {
|
|
625
|
+
try {
|
|
626
|
+
await retryFunctional(() => setGitRef(config), {
|
|
627
|
+
maxRetries: GIT_SYNC_MAX_RETRIES,
|
|
628
|
+
minDelay: GIT_SYNC_RETRY_MIN_DELAY,
|
|
629
|
+
maxDelay: GIT_SYNC_RETRY_MAX_DELAY,
|
|
630
|
+
})();
|
|
631
|
+
return;
|
|
632
|
+
} catch (e: any) {
|
|
633
|
+
console.error(`Failed to sync git ref after ${GIT_SYNC_MAX_RETRIES} retries; rebuilding .git in place next (working tree, incl. node_modules, is preserved).`, {
|
|
634
|
+
gitFolder: config.gitFolder,
|
|
635
|
+
repoUrl: config.repoUrl,
|
|
636
|
+
gitRef: config.gitRef,
|
|
637
|
+
error: e?.stack ?? String(e),
|
|
638
|
+
});
|
|
620
639
|
}
|
|
621
|
-
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Repo missing, or present-but-unsyncable: rebuild just the .git metadata and let `git reset --hard` reconcile the tracked files. The working-tree files (node_modules) are left untouched. Equivalent to a clone when the folder is empty.
|
|
643
|
+
try {
|
|
644
|
+
await rebuildGitFolder(config);
|
|
645
|
+
return;
|
|
622
646
|
} catch (e: any) {
|
|
623
|
-
console.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
647
|
+
console.error(`Rebuilding .git in place failed; falling back to a full delete + clone (this wipes node_modules, which the caller reinstalls).`, {
|
|
648
|
+
gitFolder: config.gitFolder,
|
|
649
|
+
repoUrl: config.repoUrl,
|
|
650
|
+
gitRef: config.gitRef,
|
|
651
|
+
error: e?.stack ?? String(e),
|
|
652
|
+
});
|
|
627
653
|
}
|
|
654
|
+
|
|
655
|
+
// Nuclear: the working tree itself is unusable. Clone fresh (into a temp dir, swapped in only on success).
|
|
656
|
+
await nuclearReclone(config);
|
|
628
657
|
});
|
|
629
658
|
|
|
630
659
|
|
|
@@ -710,8 +739,10 @@ const resyncServicesBase = runInSerial(measureWrap(async function resyncServices
|
|
|
710
739
|
gitRef: config.parameters.gitRef,
|
|
711
740
|
});
|
|
712
741
|
let afterGitRef = await getGitRefLive(gitFolder);
|
|
713
|
-
|
|
714
|
-
|
|
742
|
+
// Reinstall when the ref changed OR node_modules is missing. The latter is the real fix for the "Cannot find module 'ws'" crash: a recovery re-clone can land on the same commit, so a ref-only check would skip the install and leave the service with no node_modules. Restoring node_modules also self-heals an already-running process, since a failed `require` is never cached and the next reconnect re-resolves it.
|
|
743
|
+
let nodeModulesMissing = !await fsExistsAsync(gitFolder + "node_modules");
|
|
744
|
+
if (afterGitRef !== prevGitRef || nodeModulesMissing) {
|
|
745
|
+
console.log(green(`Yarn installing for ${magenta(screenName)} (ref ${prevGitRef} -> ${afterGitRef}, nodeModulesMissing=${nodeModulesMissing})`));
|
|
715
746
|
await runPromise(`yarn install`, { cwd: gitFolder });
|
|
716
747
|
}
|
|
717
748
|
}
|
|
@@ -4,7 +4,7 @@ import { canHaveChildren } from "socket-function/src/types";
|
|
|
4
4
|
import { lazy } from "socket-function/src/caching";
|
|
5
5
|
import { timeInMinute } from "socket-function/src/misc";
|
|
6
6
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
7
|
-
import { addEpsilons } from "
|
|
7
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
8
8
|
import { getPathStr2 } from "../../path";
|
|
9
9
|
import { isPublic } from "../../config";
|
|
10
10
|
import type { IndexedLogs } from "./IndexedLogs/IndexedLogs";
|
|
@@ -17,7 +17,7 @@ import { isEmpty } from "../../misc";
|
|
|
17
17
|
import { PathValue, epochTime } from "../../0-path-value-core/pathValueCore";
|
|
18
18
|
import { pathValueSerializer } from "../../-h-path-value-serialize/PathValueSerializer";
|
|
19
19
|
import { InputLabel } from "../../library-components/InputLabel";
|
|
20
|
-
import { addEpsilons } from "
|
|
20
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
21
21
|
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
22
22
|
import { ArchiveViewerTable } from "./ArchiveViewerTable";
|
|
23
23
|
import { liveWatchPaths, skipStringsURL, skipValuesURL, startTime, endTime, addFileCount, archiveFileValue, filtersURL, viewMode, archiveViewerDiskCache } from "./archiveViewerShared";
|
|
@@ -2,7 +2,7 @@ import preact from "preact"; import { qreact } from "../../4-dom/qreact";
|
|
|
2
2
|
import { pathValueSerializer } from "../../-h-path-value-serialize/PathValueSerializer";
|
|
3
3
|
import { PathValue } from "../../0-path-value-core/pathValueCore";
|
|
4
4
|
import { atomic, atomicObjectWrite } from "../../2-proxy/PathValueProxyWatcher";
|
|
5
|
-
import { getLowUint32 } from "
|
|
5
|
+
import { getLowUint32 } from "socket-function/src/bits";
|
|
6
6
|
import { Button } from "../../library-components/Button";
|
|
7
7
|
import { InputLabel } from "../../library-components/InputLabel";
|
|
8
8
|
import { getPathFromStr, getPathStr } from "../../path";
|
|
@@ -5,7 +5,7 @@ import { css } from "../../4-dom/css";
|
|
|
5
5
|
import { TimeRangeView } from "./TimeRangeView";
|
|
6
6
|
import { URLParam } from "../../library-components/URLParam";
|
|
7
7
|
import { InputLabelURL } from "../../library-components/InputLabel";
|
|
8
|
-
import { addEpsilons } from "
|
|
8
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
9
9
|
import { formatNumber, formatPercent, formatTime } from "socket-function/src/formatting/format";
|
|
10
10
|
import { nextId, sort } from "socket-function/src/misc";
|
|
11
11
|
import { ATag } from "../../library-components/ATag";
|
|
@@ -17,7 +17,7 @@ import { throttleRerender } from "../../functional/throttleRerender";
|
|
|
17
17
|
import { InputLabel, InputLabelURL } from "../../library-components/InputLabel";
|
|
18
18
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
19
19
|
import { TimeRange, TimeRangeView, getUniqueTimeByPath, normalizeTimeRanges } from "./TimeRangeView";
|
|
20
|
-
import { addEpsilons } from "
|
|
20
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
21
21
|
import { LOCAL_DOMAIN_PATH } from "../../0-path-value-core/PathRouter";
|
|
22
22
|
|
|
23
23
|
export const watchValueType = new URLParam("valueType", "watch" as "watch" | "component");
|
|
@@ -7,7 +7,7 @@ import { performDrag2 } from "../library-components/drag";
|
|
|
7
7
|
import { atomic, atomicObjectSymbol, atomicObjectWrite } from "../2-proxy/PathValueProxyWatcher";
|
|
8
8
|
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
9
9
|
import { authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
10
|
-
import { addEpsilons } from "
|
|
10
|
+
import { addEpsilons } from "socket-function/src/bits";
|
|
11
11
|
import { Querysub } from "../4-querysub/Querysub";
|
|
12
12
|
import { MeasuredDiv } from "../library-components/MeasuredDiv";
|
|
13
13
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { atomic, atomicObjectWrite, atomicObjectWriteNoFreeze, doAtomicWrites, isTransparentValue, proxyWatcher, specialObjectWriteValue } from "../../src/2-proxy/PathValueProxyWatcher";
|
|
1
|
+
import { atomic, atomicObjectWrite, atomicObjectWriteNoFreeze, doAtomicWrites, doProxyOptions, isTransparentValue, proxyWatcher, specialObjectWriteValue } from "../../src/2-proxy/PathValueProxyWatcher";
|
|
2
2
|
import { Querysub } from "../../src/4-querysub/Querysub";
|
|
3
3
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
4
4
|
import { FullCallType, SocketRegistered } from "socket-function/SocketFunctionTypes";
|
|
@@ -91,6 +91,25 @@ const writeWatchers = new Map<string, {
|
|
|
91
91
|
fncName: string;
|
|
92
92
|
}[]>();
|
|
93
93
|
|
|
94
|
+
export function asyncCache<T>(fnc: () => Promise<T>): (...args: any[]) => T | undefined {
|
|
95
|
+
return getSyncedController({
|
|
96
|
+
nodes: {
|
|
97
|
+
"": {
|
|
98
|
+
fnc,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
})("").fnc;
|
|
102
|
+
}
|
|
103
|
+
export function asyncCacheObj<T>(fncs: T): {
|
|
104
|
+
[key in keyof T]: T[key] extends (...args: any[]) => Promise<infer Return> ? (...args: any[]) => Return | undefined : never;
|
|
105
|
+
} {
|
|
106
|
+
return getSyncedController({
|
|
107
|
+
nodes: {
|
|
108
|
+
"": fncs as any,
|
|
109
|
+
},
|
|
110
|
+
})("") as any;
|
|
111
|
+
}
|
|
112
|
+
|
|
94
113
|
export function getSyncedController<T extends {
|
|
95
114
|
nodes: {
|
|
96
115
|
[key: string]: {
|
|
@@ -274,12 +293,15 @@ export function getSyncedController<T extends {
|
|
|
274
293
|
});
|
|
275
294
|
// We have to wait until we actually commit before making the call. Otherwise, if this commit is rejected because we need to do synchronized values, we'll call the function twice.
|
|
276
295
|
let fnc = controller.nodes[nodeId][fncName] as any;
|
|
296
|
+
console.log(`Triggering call ${fncName} with args ${JSON.stringify(args)}`);
|
|
277
297
|
Querysub.onCommitFinished(() => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
console.log(`Commit finished for call ${fncName} with args ${JSON.stringify(args)}`);
|
|
299
|
+
// Doesn't on commit finished also implicitly mean we're all synced? Pretty sure that isAll synced is making things break.
|
|
300
|
+
//if (Querysub.isAllSynced()) {
|
|
301
|
+
void Promise.resolve().then(() => {
|
|
302
|
+
doPromiseCall();
|
|
303
|
+
});
|
|
304
|
+
//}
|
|
283
305
|
});
|
|
284
306
|
function doPromiseCall() {
|
|
285
307
|
let promise = fnc(...args) as Promise<unknown>;
|
|
@@ -347,7 +369,10 @@ export function getSyncedController<T extends {
|
|
|
347
369
|
}
|
|
348
370
|
}
|
|
349
371
|
}
|
|
372
|
+
// Force it to commit, so even if we aren't synced, it runs.
|
|
373
|
+
//doProxyOptions({ commitAllRuns: true }, () => {
|
|
350
374
|
call(...args);
|
|
375
|
+
//});
|
|
351
376
|
// Assign to itself, to preset the type assumptions typescript makes (otherwise we get an error below)
|
|
352
377
|
obj = obj as any;
|
|
353
378
|
if (config?.cachePromiseCalls) {
|
package/src/storage/diskCache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asBuffer } from "
|
|
1
|
+
import { asBuffer } from "socket-function/src/buffers";
|
|
2
2
|
import { assert, assertValue, logErrors } from "../errors";
|
|
3
3
|
import { batchFunction } from "socket-function/src/batching";
|
|
4
4
|
import { lazy } from "socket-function/src/caching";
|
package/src/zipThreaded.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Worker } from "worker_threads";
|
|
|
4
4
|
import { isNode } from "typesafecss";
|
|
5
5
|
import { PromiseObj } from "./promise";
|
|
6
6
|
import debugbreak from "debugbreak";
|
|
7
|
-
import { asBuffer } from "
|
|
7
|
+
import { asBuffer } from "socket-function/src/buffers";
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
let getWorkerLimit = lazy(() => {
|
package/src/bits.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/** Subtracts the smallest possible value from a number (a double). This makes it possible to convert an exclusive range end
|
|
3
|
-
* to an inclusive range end, which is sometimes required (as in, < x is the same as <= minusEpsilon(x)).
|
|
4
|
-
*/
|
|
5
|
-
export function minusEpsilon(value: number) {
|
|
6
|
-
let high = getHighUint32(value);
|
|
7
|
-
let low = getLowUint32(value);
|
|
8
|
-
|
|
9
|
-
if (low === 0) {
|
|
10
|
-
low = 2 ** 32 - 1;
|
|
11
|
-
high--;
|
|
12
|
-
} else {
|
|
13
|
-
low--;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return setLowHighUint32(low, high);
|
|
17
|
-
}
|
|
18
|
-
const maxUint32 = 2 ** 32 - 1;
|
|
19
|
-
export function addEpsilons(value: number, count: number) {
|
|
20
|
-
let high = getHighUint32(value);
|
|
21
|
-
let low = getLowUint32(value);
|
|
22
|
-
|
|
23
|
-
low += count;
|
|
24
|
-
if (low < 0) {
|
|
25
|
-
low += maxUint32;
|
|
26
|
-
high++;
|
|
27
|
-
} else if (low > maxUint32) {
|
|
28
|
-
low -= maxUint32;
|
|
29
|
-
high--;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return setLowHighUint32(low, high);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let conversionBuffer = new Float64Array(1);
|
|
36
|
-
let conversionUint8Buffer = new Uint8Array(conversionBuffer.buffer);
|
|
37
|
-
let conversionUint32Buffer = new Uint32Array(conversionBuffer.buffer);
|
|
38
|
-
export function getHighUint32(num: number): number {
|
|
39
|
-
conversionBuffer[0] = num;
|
|
40
|
-
return conversionUint32Buffer[1];
|
|
41
|
-
}
|
|
42
|
-
export function getLowUint32(num: number): number {
|
|
43
|
-
conversionBuffer[0] = num;
|
|
44
|
-
return conversionUint32Buffer[0];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** IMPORTANT! Beware of comparisons with 64 bit numbers. getFloat64_fromBytes(4294136438, 168) !== getFloat64_fromBytes(4294136438, 168).
|
|
48
|
-
* USE is64BitEqual instead, OR, ensure the 2 highest bits are always 0.
|
|
49
|
-
*/
|
|
50
|
-
export function setLowHighUint32(low: number, high: number): number {
|
|
51
|
-
conversionUint32Buffer[0] = low;
|
|
52
|
-
conversionUint32Buffer[1] = high;
|
|
53
|
-
return conversionBuffer[0];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Adds protection against NaN values, changing the result if it would be NaN. This is because NaN values will compare to be not equal even if they are equal in certain cases, and in other cases, they'll always be equal, even if their bits are not equal.
|
|
57
|
-
- This means this is not a reversible operation. However, in a lot of cases, that doesn't matter.
|
|
58
|
-
*/
|
|
59
|
-
export function setLowHighUint32Safe(low: number, high: number): number {
|
|
60
|
-
conversionUint32Buffer[0] = low;
|
|
61
|
-
// Prevent NaN by not setting all the exponent bits to 1
|
|
62
|
-
conversionUint32Buffer[1] = high & 0xBFFFFFFF;
|
|
63
|
-
return conversionBuffer[0];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// NOTE: Not reversible, see setLowHighUint32Safe
|
|
67
|
-
export function xor64BitsSafe(a: number, b: number): number {
|
|
68
|
-
let high = getHighUint32(a);
|
|
69
|
-
let low = getLowUint32(a);
|
|
70
|
-
let high2 = getHighUint32(b);
|
|
71
|
-
let low2 = getLowUint32(b);
|
|
72
|
-
return setLowHighUint32Safe(low ^ low2, high ^ high2);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Gets bits that can be stored in a number. Specifically, the first 62 bits,
|
|
76
|
-
// as 64 bits will not compare correctly when treated as a double.
|
|
77
|
-
export function getShortNumber(buffer: Buffer): number {
|
|
78
|
-
let high = buffer.readUInt32BE(0) & 0x3FFFFFFF;
|
|
79
|
-
let low = buffer.readUInt32BE(4);
|
|
80
|
-
return setLowHighUint32(low, high);
|
|
81
|
-
}
|
|
82
|
-
/* Returns a number between 0 and 2**48 */
|
|
83
|
-
export function getBufferInt(buffer: Buffer): number {
|
|
84
|
-
let num = 0;
|
|
85
|
-
for (let i = 0; i < Math.min(buffer.length, 6); i++) {
|
|
86
|
-
num = num * 256 + buffer[i];
|
|
87
|
-
}
|
|
88
|
-
return num;
|
|
89
|
-
}
|
|
90
|
-
const intMax = 2 ** 48;
|
|
91
|
-
/** Returns a number between 0 (inclusive) and 1 (exclusive) */
|
|
92
|
-
export function getBufferFraction(buffer: Buffer): number {
|
|
93
|
-
let int = getBufferInt(buffer);
|
|
94
|
-
return int / intMax;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/*
|
|
98
|
-
export function numberToBase64(num: number): string {
|
|
99
|
-
conversionBuffer[0] = num;
|
|
100
|
-
return Buffer.from(conversionBuffer.buffer).toString("base64");
|
|
101
|
-
}
|
|
102
|
-
export function numberFromBase64(base64: string) {
|
|
103
|
-
return new Float64Array(Buffer.from(base64, "base64"))[0];
|
|
104
|
-
}
|
|
105
|
-
*/
|
package/src/buffers.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { canHaveChildren } from "socket-function/src/types";
|
|
2
|
-
|
|
3
|
-
export type ArrayBufferViewTypes = Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | BigUint64Array | BigInt64Array | Float64Array | Float32Array | Uint8ClampedArray;
|
|
4
|
-
export type BufferType = ArrayBuffer | SharedArrayBuffer | ArrayBufferViewTypes;
|
|
5
|
-
|
|
6
|
-
export function cloneBuffer(data: Buffer): Buffer {
|
|
7
|
-
let newBuffer = Buffer.alloc(data.length);
|
|
8
|
-
data.copy(newBuffer);
|
|
9
|
-
return newBuffer;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export function asBuffer(data: BufferType): Buffer {
|
|
14
|
-
if (!data) return data;
|
|
15
|
-
if (data instanceof Buffer) return data;
|
|
16
|
-
if (!canHaveChildren(data)) return data as any;
|
|
17
|
-
if (!("buffer" in data) || !("byteOffset" in data) || !("byteLength" in data)) {
|
|
18
|
-
return Buffer.from(data);
|
|
19
|
-
}
|
|
20
|
-
let result = Buffer.from((data as any).buffer, (data as any).byteOffset, (data as any).byteLength);
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
export function asFloat64(data: Buffer): Float64Array {
|
|
24
|
-
if (data.length % 8 !== 0) {
|
|
25
|
-
throw new Error(`Data is not 8 count aligned, received length of ${data.length}`);
|
|
26
|
-
}
|
|
27
|
-
if (data.byteOffset % 8 !== 0) {
|
|
28
|
-
throw new Error(`Data is not 8 byte aligned, received offset of ${data.byteOffset}`);
|
|
29
|
-
}
|
|
30
|
-
return new Float64Array(data.buffer, data.byteOffset, Math.floor(data.length / 8));
|
|
31
|
-
}
|
|
32
|
-
export function asFloat32(data: Buffer): Float32Array {
|
|
33
|
-
if (data.length % 4 !== 0) {
|
|
34
|
-
throw new Error(`Data is not 4 byte aligned, received length of ${data.length}`);
|
|
35
|
-
}
|
|
36
|
-
if (data.byteOffset % 4 !== 0) {
|
|
37
|
-
throw new Error(`Data is not 4 byte aligned, received offset of ${data.byteOffset}`);
|
|
38
|
-
}
|
|
39
|
-
return new Float32Array(data.buffer, data.byteOffset, Math.floor(data.length / 4));
|
|
40
|
-
}
|
|
41
|
-
export function asUint32(data: Buffer): Uint32Array {
|
|
42
|
-
if (data.length % 4 !== 0) {
|
|
43
|
-
throw new Error(`Data is not 4 byte aligned, received length of ${data.length}`);
|
|
44
|
-
}
|
|
45
|
-
if (data.byteOffset % 4 !== 0) {
|
|
46
|
-
throw new Error(`Data is not 4 byte aligned, received offset of ${data.byteOffset}`);
|
|
47
|
-
}
|
|
48
|
-
return new Uint32Array(data.buffer, data.byteOffset, Math.floor(data.length / 4));
|
|
49
|
-
}
|
|
50
|
-
export function asInt32(data: Buffer): Int32Array {
|
|
51
|
-
if (data.length % 4 !== 0) {
|
|
52
|
-
throw new Error(`Data is not 4 byte aligned, received length of ${data.length}`);
|
|
53
|
-
}
|
|
54
|
-
if (data.byteOffset % 4 !== 0) {
|
|
55
|
-
throw new Error(`Data is not 4 byte aligned, received offset of ${data.byteOffset}`);
|
|
56
|
-
}
|
|
57
|
-
return new Int32Array(data.buffer, data.byteOffset, Math.floor(data.length / 4));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export function asFloat64MaybeCopy(data: Buffer) {
|
|
62
|
-
if (data.length % 8 !== 0) {
|
|
63
|
-
throw new Error(`Data is not 8 count aligned, received length of ${data.length}`);
|
|
64
|
-
}
|
|
65
|
-
if (data.byteOffset % 8 !== 0) {
|
|
66
|
-
return asFloat64(cloneBuffer(data));
|
|
67
|
-
}
|
|
68
|
-
return new Float64Array(data.buffer, data.byteOffset, Math.floor(data.length / 8));
|
|
69
|
-
}
|