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
|
@@ -5,7 +5,7 @@ import { CHILD_CHECK_PREFIX, FunctionMetadata } from "../3-path-functions/syncSc
|
|
|
5
5
|
import { RemoteWatcher, remoteWatcher } from "../1-path-client/RemoteWatcher";
|
|
6
6
|
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
7
7
|
import { atomic, atomicObjectWrite, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
8
|
-
import { CallSpec, DEPTH_TO_DATA, FunctionSpec, commitCall, debugCallSpec, functionSchema } from "../3-path-functions/PathFunctionRunner";
|
|
8
|
+
import { CallSpec, DEPTH_TO_DATA, FunctionSpec, commitCall, debugCallSpec, functionSchema, getDevFunctionSpecFromCall } from "../3-path-functions/PathFunctionRunner";
|
|
9
9
|
import { PathValueController } from "../0-path-value-core/PathValueController";
|
|
10
10
|
import { epochTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, pathWatcher, Time, WatchConfig, compareTime, debugTime, getNextTime, MAX_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
11
11
|
import { IdentityController_getMachineId, IdentityController_getPubKeyShort, IdentityController_getSecureIP } from "../-c-identity/IdentityController";
|
|
@@ -43,6 +43,7 @@ setFlag(require, "preact", "allowclient", true);
|
|
|
43
43
|
import yargs from "yargs";
|
|
44
44
|
import { mergeFilterables, parseFilterable, serializeFilterable } from "../misc/filterable";
|
|
45
45
|
import { isManagementUser } from "../-0-hooks/hooks";
|
|
46
|
+
import { isLocal } from "../config";
|
|
46
47
|
|
|
47
48
|
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
48
49
|
.option("fncfilter", { type: "string", default: "", desc: `Sets the filterable state for function calls, causing them to target specific FunctionRunners. If no FunctionRunner matches, all functions will fail to run. For example: "devtestserver" will match a FunctionRunner that uses the "devtestserver" filter. Merges with the existing filterable state if a client sets it explicitly.` })
|
|
@@ -580,10 +581,15 @@ export class QuerysubControllerBase {
|
|
|
580
581
|
}
|
|
581
582
|
|
|
582
583
|
public async getModulePath(config: {
|
|
583
|
-
functionSpec:
|
|
584
|
+
functionSpec: {
|
|
585
|
+
DomainName: string;
|
|
586
|
+
ModuleId: string;
|
|
587
|
+
FunctionId: string;
|
|
588
|
+
};
|
|
584
589
|
}): Promise<string> {
|
|
585
590
|
let spec = config.functionSpec;
|
|
586
591
|
Querysub.assertDomainAllowed(getPathStr1(spec.DomainName));
|
|
592
|
+
|
|
587
593
|
let moduleConfig = await proxyWatcher.commitFunction({
|
|
588
594
|
watchFunction: function getModuleConfig() {
|
|
589
595
|
return functionSchema()[spec.DomainName].PathFunctionRunner[spec.ModuleId].Sources[spec.FunctionId];
|
|
@@ -596,6 +602,19 @@ export class QuerysubControllerBase {
|
|
|
596
602
|
let module = await getModuleFromConfig(moduleConfig);
|
|
597
603
|
return module.filename;
|
|
598
604
|
}
|
|
605
|
+
public async getDevFunctionSpecFromCall(call: {
|
|
606
|
+
DomainName: string;
|
|
607
|
+
ModuleId: string;
|
|
608
|
+
FunctionId: string;
|
|
609
|
+
}) {
|
|
610
|
+
let functionSpec = getDevFunctionSpecFromCall(call);
|
|
611
|
+
if (!functionSpec) return undefined;
|
|
612
|
+
let modulePath = (await getModuleFromConfig(functionSpec)).filename;
|
|
613
|
+
return {
|
|
614
|
+
functionSpec,
|
|
615
|
+
modulePath,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
599
618
|
|
|
600
619
|
|
|
601
620
|
// NOTE: Maybe remove this? It's very useful for debugging though...
|
|
@@ -619,6 +638,7 @@ export const QuerysubController = SocketFunction.register(
|
|
|
619
638
|
debugGetSingleReadNode: {},
|
|
620
639
|
debugGetPathAuthorities: {},
|
|
621
640
|
getModulePath: {},
|
|
641
|
+
getDevFunctionSpecFromCall: {},
|
|
622
642
|
}),
|
|
623
643
|
() => ({
|
|
624
644
|
|
|
@@ -2,7 +2,7 @@ import { cache, cacheArgsEqual, cacheLimited } from "socket-function/src/caching
|
|
|
2
2
|
import { measureFnc, measureWrap, nameFunction } from "socket-function/src/profiling/measure";
|
|
3
3
|
import { getPathSuffix, getPathDepth, trimPathStrToDepth, getPathFromStr, rootPathStr, getPathIndex, getPathStr1, joinPathStres, appendToPathStr, getPathStr } from "../path";
|
|
4
4
|
import { atomic, atomicObjectRead, isSynced, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
5
|
-
import { SchemaObject, getSchemaObject, hasWildcardMatch, getWildcardMatches, PERMISSIONS_FUNCTION_ID, PermissionsCheckResult } from "../3-path-functions/syncSchema";
|
|
5
|
+
import { SchemaObject, getSchemaObject, hasWildcardMatch, getWildcardMatches, PERMISSIONS_FUNCTION_ID, PermissionsCheckResult, getDevelopmentModule } from "../3-path-functions/syncSchema";
|
|
6
6
|
import { getModuleFromConfig } from "../3-path-functions/pathFunctionLoader";
|
|
7
7
|
import { getSchemaPartsFromPath, functionSchema, DEPTH_TO_DATA, overrideCurrentCall, CallSpec, FunctionSpec, DOMAIN_INDEX, MODULE_INDEX } from "../3-path-functions/PathFunctionRunner";
|
|
8
8
|
import debugbreak from "debugbreak";
|
|
@@ -13,6 +13,7 @@ import { isClient } from "../config2";
|
|
|
13
13
|
import { sort } from "socket-function/src/misc";
|
|
14
14
|
import { Querysub } from "./QuerysubController";
|
|
15
15
|
import { CALL_PERMISSIONS_KEY } from "./permissionsShared";
|
|
16
|
+
import { getDomain, isLocal } from "../config";
|
|
16
17
|
|
|
17
18
|
function watchModule(config: FunctionSpec): NodeJS.Module | undefined {
|
|
18
19
|
let module = getModuleFromConfig(config);
|
|
@@ -50,7 +51,37 @@ export class PermissionsCheck {
|
|
|
50
51
|
);
|
|
51
52
|
});
|
|
52
53
|
|
|
53
|
-
private getModulePermissions
|
|
54
|
+
private getModulePermissions(domainName: string) {
|
|
55
|
+
if (isLocal() && domainName === getDomain()) {
|
|
56
|
+
return function (moduleId: string): {
|
|
57
|
+
schema: SchemaObject;
|
|
58
|
+
fnc: FunctionSpec;
|
|
59
|
+
} | undefined {
|
|
60
|
+
let fnc: FunctionSpec = {
|
|
61
|
+
DomainName: domainName,
|
|
62
|
+
ModuleId: moduleId,
|
|
63
|
+
FunctionId: PERMISSIONS_FUNCTION_ID,
|
|
64
|
+
exportPathStr: callPermissionsPath,
|
|
65
|
+
// NOTE: These SHOULDN'T be required, as we don't commit this function to the FunctionRunner,
|
|
66
|
+
// we just use this to run the function on the local machine.
|
|
67
|
+
FilePath: "LOCAL_PERMISSIONS_HACK",
|
|
68
|
+
gitURL: "LOCAL_PERMISSIONS_HACK",
|
|
69
|
+
gitRef: "LOCAL_PERMISSIONS_HACK",
|
|
70
|
+
};
|
|
71
|
+
let modulePermissions = getDevelopmentModule(moduleId);
|
|
72
|
+
if (!modulePermissions) throw new Error(`No development module found for ${moduleId}. Was it imported? Try restarting the edge server.`);
|
|
73
|
+
let schema = getSchemaObject(modulePermissions);
|
|
74
|
+
if (!schema) throw new Error(`Module does not export a schema: ${moduleId}`);
|
|
75
|
+
return {
|
|
76
|
+
schema,
|
|
77
|
+
fnc,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return this.getModulePermissionsBase(domainName);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private getModulePermissionsBase = cache((domainName: string) => {
|
|
54
85
|
return cache((moduleId: string) => {
|
|
55
86
|
let functionConfigProxy = PermissionsCheck.skipPermissionsChecks(() =>
|
|
56
87
|
functionSchema()[domainName].PathFunctionRunner[moduleId].Sources[PERMISSIONS_FUNCTION_ID],
|
|
@@ -20,29 +20,46 @@ import cbor from "cbor-x";
|
|
|
20
20
|
import { FunctionMetadata } from "../3-path-functions/syncSchema";
|
|
21
21
|
import { isNode, nextId, sort } from "socket-function/src/misc";
|
|
22
22
|
import { getBrowserUrlNode } from "../-f-node-discovery/NodeDiscovery";
|
|
23
|
+
import { isLocal } from "../config";
|
|
23
24
|
setFlag(require, "cbor-x", "allowclient", true);
|
|
24
25
|
const cborEncoder = lazy(() => new cbor.Encoder({ structuredClone: true }));
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const addModuleToLoader = cacheJSONArgsEqual(async (spec: FunctionSpec): Promise<void> => {
|
|
27
|
+
|
|
28
|
+
// NOTE: Most functions won't use this, as they should use regular api calls. However,
|
|
29
|
+
// as we are using paths for RequireJS, we explicitly need our local (when serverside).
|
|
30
|
+
async function getPredictController() {
|
|
31
31
|
let controller: QuerysubControllerBase;
|
|
32
32
|
if (isNode()) {
|
|
33
33
|
// NOTE: If we are on node, then the require WON'T run over the network, so we need to use
|
|
34
|
-
// our local module path, not the
|
|
34
|
+
// our local module path, not the remote one.
|
|
35
35
|
controller = new QuerysubControllerBase();
|
|
36
36
|
} else {
|
|
37
37
|
let nodeId = await querysubNodeId();
|
|
38
38
|
if (!nodeId) throw new Error("No querysub node found");
|
|
39
39
|
controller = QuerysubController.nodes[nodeId] as any;
|
|
40
40
|
}
|
|
41
|
+
return controller;
|
|
42
|
+
}
|
|
43
|
+
// TODO: I think our use of filePath, moduleId, etc are wrong here? We give pathFunctionLoader the filePath,
|
|
44
|
+
// but we really should just give it the moduleId, or... even just avoid calling it altogether, as it doesn't
|
|
45
|
+
// do too much for us if we already have the fully resolved path...
|
|
46
|
+
// - Although using it DOES allow permissions checks to work nicely, so, eh... maybe it is fine to use pathFunctionLoader?
|
|
47
|
+
const addModuleToLoader = cacheJSONArgsEqual(async (spec: FunctionSpec): Promise<void> => {
|
|
48
|
+
let controller = await getPredictController();
|
|
41
49
|
|
|
42
50
|
let path = await controller.getModulePath({ functionSpec: spec });
|
|
43
51
|
setGitURLMapping({ spec, resolvedPath: path });
|
|
44
52
|
});
|
|
45
53
|
|
|
54
|
+
const getDevFunctionSpecFromCall = cacheJSONArgsEqual(async (call: {
|
|
55
|
+
DomainName: string;
|
|
56
|
+
ModuleId: string;
|
|
57
|
+
FunctionId: string;
|
|
58
|
+
}) => {
|
|
59
|
+
let controller = await getPredictController();
|
|
60
|
+
return controller.getDevFunctionSpecFromCall(call);
|
|
61
|
+
});
|
|
62
|
+
|
|
46
63
|
|
|
47
64
|
export function getCallResultPath(call: CallSpec) {
|
|
48
65
|
return getProxyPath(() => functionSchema()[call.DomainName].PathFunctionRunner[call.ModuleId].Results[call.CallId]);
|
|
@@ -490,20 +507,36 @@ export async function getCallWrites(config: {
|
|
|
490
507
|
overrides?: PathValue[];
|
|
491
508
|
}) {
|
|
492
509
|
let { call, debugName } = config;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
510
|
+
|
|
511
|
+
let functionSpec: FunctionSpec | undefined;
|
|
512
|
+
if (isLocal()) {
|
|
513
|
+
let obj = await getDevFunctionSpecFromCall({
|
|
514
|
+
DomainName: call.DomainName,
|
|
515
|
+
ModuleId: call.ModuleId,
|
|
516
|
+
FunctionId: call.FunctionId,
|
|
517
|
+
});
|
|
518
|
+
if (!obj) throw new Error(`Function not referenced in deploy.ts ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
|
|
519
|
+
functionSpec = obj.functionSpec;
|
|
520
|
+
setGitURLMapping({ spec: functionSpec, resolvedPath: obj.modulePath });
|
|
521
|
+
} else {
|
|
522
|
+
const obj = await proxyWatcher.commitFunction({
|
|
523
|
+
watchFunction: function getModuleConfig() {
|
|
524
|
+
let domainObject = functionSchema()[call.DomainName];
|
|
525
|
+
let moduleObject = domainObject.PathFunctionRunner[call.ModuleId];
|
|
526
|
+
let functionSpec = atomicObjectRead(moduleObject.Sources[call.FunctionId]);
|
|
527
|
+
return { functionSpec };
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
functionSpec = obj.functionSpec;
|
|
531
|
+
|
|
532
|
+
if (!functionSpec) {
|
|
533
|
+
throw new Error(`Function not found in database ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
|
|
499
534
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
535
|
+
// Add the module to the loader via asking the server the exact url for this call. The loader will
|
|
536
|
+
// then load the code from that url when it ends up running it.
|
|
537
|
+
await addModuleToLoader(functionSpec);
|
|
503
538
|
}
|
|
504
539
|
|
|
505
|
-
await addModuleToLoader(functionSpec);
|
|
506
|
-
|
|
507
540
|
let module = await getModuleFromConfig(functionSpec);
|
|
508
541
|
let exportPath = getPathFromStr(functionSpec.exportPathStr);
|
|
509
542
|
let exportObj = module.exports;
|
|
@@ -515,6 +548,7 @@ export async function getCallWrites(config: {
|
|
|
515
548
|
}
|
|
516
549
|
let baseFunction = exportObj as Function;
|
|
517
550
|
|
|
551
|
+
let specTyped = functionSpec;
|
|
518
552
|
return await proxyWatcher.dryRunFull({
|
|
519
553
|
debugName,
|
|
520
554
|
runAtTime: call.runAtTime,
|
|
@@ -524,7 +558,7 @@ export async function getCallWrites(config: {
|
|
|
524
558
|
overrides: config.overrides,
|
|
525
559
|
nestedCalls: "inline",
|
|
526
560
|
watchFunction() {
|
|
527
|
-
return overrideCurrentCall({ spec: call, fnc:
|
|
561
|
+
return overrideCurrentCall({ spec: call, fnc: specTyped }, () => {
|
|
528
562
|
let args = parseArgs(call);
|
|
529
563
|
return baseFunction(...args);
|
|
530
564
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "querysub/inject";
|
|
2
|
+
|
|
3
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
4
|
+
import { logErrors } from "../errors";
|
|
5
|
+
import { isNodeTrue, timeInDay, timeInHour } from "socket-function/src/misc";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
8
|
+
|
|
9
|
+
import yargs from "yargs";
|
|
10
|
+
import { runAliveCheckerIteration } from "../2-proxy/garbageCollection";
|
|
11
|
+
|
|
12
|
+
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
13
|
+
.option("watch", { type: "boolean", desc: "Watch, and GC every N time (every day as of writing this)" })
|
|
14
|
+
.argv || {}
|
|
15
|
+
;
|
|
16
|
+
|
|
17
|
+
// yarn gc --authority testshard.json
|
|
18
|
+
async function main() {
|
|
19
|
+
await Querysub.hostService("gc");
|
|
20
|
+
|
|
21
|
+
let folderRoot = path.resolve(".");
|
|
22
|
+
folderRoot = folderRoot.replaceAll(/\\/g, "/");
|
|
23
|
+
const deployPath = path.resolve("./deploy.ts");
|
|
24
|
+
require(deployPath);
|
|
25
|
+
|
|
26
|
+
if (yargObj.watch) {
|
|
27
|
+
await runInfinitePollCallAtStart(timeInDay, runAliveCheckerIteration);
|
|
28
|
+
} else {
|
|
29
|
+
try {
|
|
30
|
+
// Force, as they are running this manually, and so they probably want to see something happen...
|
|
31
|
+
await runAliveCheckerIteration({ force: true });
|
|
32
|
+
} finally {
|
|
33
|
+
process.exit();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main().catch(logErrors);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import "querysub/inject";
|
|
2
|
+
|
|
3
|
+
import { logErrors } from "../errors";
|
|
4
|
+
import { getOurAuthorities } from "../config2";
|
|
5
|
+
import { pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
6
|
+
import { PathValueArchives, pathValueArchives } from "../0-path-value-core/pathValueArchives";
|
|
7
|
+
import { FILE_SIZE_LIMIT, FILE_VALUE_COUNT_LIMIT, PathValue, VALUE_GC_THRESHOLD } from "../0-path-value-core/pathValueCore";
|
|
8
|
+
import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
|
+
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
10
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
11
|
+
import { ArchiveTransaction, FileInfo } from "../0-path-value-core/archiveLocks/ArchiveLocks";
|
|
12
|
+
import { formatNumber } from "socket-function/src/formatting/format";
|
|
13
|
+
import { sort } from "socket-function/src/misc";
|
|
14
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
15
|
+
import { magenta } from "socket-function/src/formatting/logColors";
|
|
16
|
+
|
|
17
|
+
async function runGenesisJoinIteration() {
|
|
18
|
+
let authorities = getOurAuthorities();
|
|
19
|
+
for (let authority of authorities) {
|
|
20
|
+
let locker = await pathValueArchives.getArchiveLocker();
|
|
21
|
+
let matchedDirs = await pathValueArchives.getAuthorityDirs(authority);
|
|
22
|
+
let authorityDir = pathValueAuthority2.getArchiveDirectory(authority);
|
|
23
|
+
let maxAge = Date.now() - VALUE_GC_THRESHOLD;
|
|
24
|
+
|
|
25
|
+
let readCache = new Map<string, Buffer>();
|
|
26
|
+
let reread = true;
|
|
27
|
+
while (reread) {
|
|
28
|
+
reread = false;
|
|
29
|
+
let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
|
|
30
|
+
valueFiles = valueFiles.filter(x => matchedDirs.some(y => x.file.startsWith(y)));
|
|
31
|
+
|
|
32
|
+
// Merge the newest first, so if we hit a big file, we can just ignore it,
|
|
33
|
+
// and next time merge the smaller files after it.
|
|
34
|
+
sort(valueFiles, x => -x.createTime);
|
|
35
|
+
valueFiles = valueFiles.filter(x => {
|
|
36
|
+
let obj = pathValueArchives.decodeDataPath(x.file);
|
|
37
|
+
if (!obj.minTime) return false;
|
|
38
|
+
if (obj.sourceType !== "genesis") return false;
|
|
39
|
+
return x.createTime >= maxAge;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (valueFiles.length < 3) return [];
|
|
43
|
+
|
|
44
|
+
await measureBlock(async function locker_readFiles() {
|
|
45
|
+
let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
|
|
46
|
+
let buffers = await readFiles(remainingFiles);
|
|
47
|
+
for (let i = 0; i < remainingFiles.length; i++) {
|
|
48
|
+
let buffer = buffers[i];
|
|
49
|
+
if (!buffer) {
|
|
50
|
+
console.log(`File missing, re-reading`, remainingFiles[i].file);
|
|
51
|
+
reread = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
readCache.set(remainingFiles[i].file, buffer);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (reread) {
|
|
58
|
+
console.log(`Files changed, re-reading`);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let usedFiles: FileInfo[] = [];
|
|
63
|
+
let totalSize = 0;
|
|
64
|
+
let totalCount = 0;
|
|
65
|
+
let allValues: PathValue[][] = [];
|
|
66
|
+
for (let valueFile of valueFiles) {
|
|
67
|
+
let buffer = readCache.get(valueFile.file)!;
|
|
68
|
+
if (totalSize + buffer.length > FILE_SIZE_LIMIT) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
let newValues = await PathValueArchives.loadValuesFromBuffer({
|
|
72
|
+
path: valueFile.file,
|
|
73
|
+
data: buffer,
|
|
74
|
+
});
|
|
75
|
+
let readValues = newValues.values;
|
|
76
|
+
if (readValues.length + totalCount > FILE_VALUE_COUNT_LIMIT) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
allValues.push(readValues);
|
|
80
|
+
usedFiles.push(valueFile);
|
|
81
|
+
totalSize += buffer.length;
|
|
82
|
+
totalCount += readValues.length;
|
|
83
|
+
}
|
|
84
|
+
if (usedFiles.length <= 1) return [];
|
|
85
|
+
|
|
86
|
+
let allCombinedValues = allValues.flat();
|
|
87
|
+
|
|
88
|
+
console.log(magenta(`Joining ${formatNumber(usedFiles.length)} files with ${formatNumber(allCombinedValues.length)} values in ${formatNumber(totalSize)} bytes`));
|
|
89
|
+
|
|
90
|
+
let dataObj = await pathValueArchives.encodeValuePaths(allCombinedValues, {
|
|
91
|
+
pathOverrides: {
|
|
92
|
+
sourceType: "join",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (!dataObj) return [];
|
|
96
|
+
let transaction: ArchiveTransaction = {
|
|
97
|
+
createFiles: [],
|
|
98
|
+
deleteFiles: [],
|
|
99
|
+
};
|
|
100
|
+
for (let file of usedFiles) {
|
|
101
|
+
transaction.deleteFiles.push(file);
|
|
102
|
+
}
|
|
103
|
+
transaction.createFiles.push({
|
|
104
|
+
file: authorityDir + dataObj?.file,
|
|
105
|
+
data: dataObj.data,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return [transaction];
|
|
109
|
+
});
|
|
110
|
+
console.log(`Join result: ${result}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function main() {
|
|
116
|
+
await Querysub.hostService("join");
|
|
117
|
+
|
|
118
|
+
await runInfinitePollCallAtStart(VALUE_GC_THRESHOLD * 0.8, runGenesisJoinIteration);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main().catch(logErrors);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "querysub/inject";
|
|
2
|
+
|
|
3
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
4
|
+
import { logErrors } from "../errors";
|
|
5
|
+
import { timeInDay, timeInHour } from "socket-function/src/misc";
|
|
6
|
+
import { runArchiveMover } from "../2-proxy/archiveMoveHarness";
|
|
7
|
+
import { PathValue, compareTime } from "../0-path-value-core/pathValueCore";
|
|
8
|
+
|
|
9
|
+
const MERGE_DELAY = timeInHour;
|
|
10
|
+
|
|
11
|
+
async function mergeFiles() {
|
|
12
|
+
await runArchiveMover({
|
|
13
|
+
outputType: "merge",
|
|
14
|
+
async runMover(config) {
|
|
15
|
+
let values = config.values;
|
|
16
|
+
let latestValues = new Map<string, PathValue>();
|
|
17
|
+
for (let value of values) {
|
|
18
|
+
let prev = latestValues.get(value.path);
|
|
19
|
+
if (prev && compareTime(value.time, prev.time) < 0) continue;
|
|
20
|
+
latestValues.set(value.path, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
values = Array.from(latestValues.values());
|
|
24
|
+
values = values.filter(x => !x.canGCValue);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
newValues: {
|
|
28
|
+
"": values,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
await Querysub.hostService("merge");
|
|
37
|
+
await mergeFiles();
|
|
38
|
+
process.exit();
|
|
39
|
+
|
|
40
|
+
// while (true) {
|
|
41
|
+
// await mergeFiles();
|
|
42
|
+
// let delay = MERGE_DELAY * (1 + Math.random() * 0.1);
|
|
43
|
+
// await new Promise(resolve => setTimeout(resolve, delay));
|
|
44
|
+
// }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch(logErrors);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { measureBlock, measureCode } from "socket-function/src/profiling/measure";
|
|
3
|
+
import zlib from "zlib";
|
|
4
|
+
const brotli = require("brotli-wasm");
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
let path = "D:/repos/qs-aipaint/model4/planremapped_unet.cyberrealistic_v33.1_lora.epi_noiseoffset2.1unet_71975fc6d0215e536a96ba7eb7a44e3a503bd265ae00cfdcfebd9fe5b92f54cb.plan0.plan";
|
|
9
|
+
let data = fs.readFileSync(path);
|
|
10
|
+
|
|
11
|
+
// None of the zstd libraries work. It is about 10X faster at encoding, but... only
|
|
12
|
+
// about 2X faster at decoding, so... whatever... it's fine for now.
|
|
13
|
+
// It's not THAT hard to add support for other libraries later on...
|
|
14
|
+
// (We can even have all endpoints use zip, and only have the disk read/writing use
|
|
15
|
+
// a new format, that way only the PathValueServers have to update).
|
|
16
|
+
// brotli was similar, except brotli-wasm, which just hung forever
|
|
17
|
+
// I think a lot of these libraries use emscripten with bad configuration that doesn't
|
|
18
|
+
// let it allocate more memory.
|
|
19
|
+
await measureCode(async () => {
|
|
20
|
+
/*
|
|
21
|
+
{
|
|
22
|
+
let compressed = await measureBlock(async function compressZip() {
|
|
23
|
+
return zlib.gzipSync(data);
|
|
24
|
+
});
|
|
25
|
+
let decompressed = await measureBlock(async function decompressZip() {
|
|
26
|
+
return zlib.gunzipSync(compressed);
|
|
27
|
+
});
|
|
28
|
+
console.log("Zip", compressed.length, decompressed.length);
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
31
|
+
// {
|
|
32
|
+
|
|
33
|
+
// let compressed = await measureBlock(async function compressZStd() {
|
|
34
|
+
// return zstd2.ZstdSimple.compress(data);
|
|
35
|
+
// });
|
|
36
|
+
// let decompressed = await measureBlock(async function decompressZStd() {
|
|
37
|
+
// return zstd2.ZstdSimple.decompress(compressed);
|
|
38
|
+
// });
|
|
39
|
+
// // let decompressed2 = await measureBlock(async function decompressFZStd() {
|
|
40
|
+
// // return fzstd.decompress(compressed);
|
|
41
|
+
// // });
|
|
42
|
+
// console.log("ZStd", compressed.length, decompressed.length);
|
|
43
|
+
// }
|
|
44
|
+
{
|
|
45
|
+
|
|
46
|
+
let compressed = await measureBlock(async function compressZStd() {
|
|
47
|
+
return brotli.compress(data);
|
|
48
|
+
});
|
|
49
|
+
let decompressed = await measureBlock(async function decompressZStd() {
|
|
50
|
+
return brotli.decompress(compressed);
|
|
51
|
+
});
|
|
52
|
+
// let decompressed2 = await measureBlock(async function decompressFZStd() {
|
|
53
|
+
// return fzstd.decompress(compressed);
|
|
54
|
+
// });
|
|
55
|
+
console.log("ZStd", compressed.length, decompressed.length);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
main().catch(e => console.error(e)).finally(() => process.exit());
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { pathValueArchives } from "../0-path-value-core/pathValueArchives";
|
|
2
|
+
import { getPathStr, rootPathStr } from "../path";
|
|
3
|
+
import { ArchiveLocker, ArchiveTransaction } from "../0-path-value-core/archiveLocks/ArchiveLocks";
|
|
4
|
+
import { getDomain } from "../config";
|
|
5
|
+
import { getOurAuthorities } from "../config2";
|
|
6
|
+
import { getNodeId } from "socket-function/src/nodeCache";
|
|
7
|
+
import { getOurNodeIdAssert, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
|
|
8
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
9
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
10
|
+
import { AuthorityPath, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
11
|
+
import { PathValue, getNextTime } from "../0-path-value-core/pathValueCore";
|
|
12
|
+
import debugbreak from "debugbreak";
|
|
13
|
+
import { delay } from "socket-function/src/batching";
|
|
14
|
+
import { isDefined } from "../misc";
|
|
15
|
+
import { sort, timeInMinute } from "socket-function/src/misc";
|
|
16
|
+
import { green, red } from "socket-function/src/formatting/logColors";
|
|
17
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
18
|
+
|
|
19
|
+
// yarn typenode src\framework-beta\archivemerge\lockTest.ts --nonetwork
|
|
20
|
+
async function main() {
|
|
21
|
+
await Querysub.hostService("lockTest");
|
|
22
|
+
/*
|
|
23
|
+
let allLockers: ArchiveLocker[] = [];
|
|
24
|
+
for (let authority of getOurAuthorities()) {
|
|
25
|
+
let lockers = await pathValueArchives.getRawArchives(authority);
|
|
26
|
+
allLockers.push(...lockers);
|
|
27
|
+
}
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const START = 5;
|
|
31
|
+
const ITERATION_COUNT = 5;
|
|
32
|
+
const COUNT_LIMIT = ITERATION_COUNT * 5;
|
|
33
|
+
|
|
34
|
+
// D:\repos\qs-aipaint\database-storage\archives\path-values-recycle-bin\=-.,querysub.__com.,
|
|
35
|
+
|
|
36
|
+
let authority: AuthorityPath = getOurAuthorities()[0];
|
|
37
|
+
let locker = await pathValueArchives.getArchiveLocker();
|
|
38
|
+
let matchedDirs = await pathValueArchives.getAuthorityDirs(authority);
|
|
39
|
+
const authorityDir = pathValueAuthority2.getArchiveDirectory(authority);
|
|
40
|
+
while (true) {
|
|
41
|
+
let result = await locker.atomicSwapFiles({}, async (valueFiles, readFiles) => {
|
|
42
|
+
valueFiles = valueFiles.filter(x => matchedDirs.some(y => x.file.startsWith(y)));
|
|
43
|
+
let prevFiles = valueFiles.filter(x => pathValueArchives.decodeDataPath(x.file).sourceType === "test");
|
|
44
|
+
let prevData = (await readFiles(prevFiles)).filter(isDefined);
|
|
45
|
+
|
|
46
|
+
let pathValues: number[][] = [];
|
|
47
|
+
for (let prevBuffer of prevData) {
|
|
48
|
+
let prevValue = await pathValueSerializer.deserialize([prevBuffer], { singleBuffer: true });
|
|
49
|
+
pathValues.push(prevValue.map(x => pathValueSerializer.getPathValue(x) as number));
|
|
50
|
+
}
|
|
51
|
+
console.log(`Values [${pathValues.map(x => "[" + x.join(", ") + "]").join(", ")}]`);
|
|
52
|
+
|
|
53
|
+
let flatValues = pathValues.flat();
|
|
54
|
+
sort(flatValues, x => x);
|
|
55
|
+
let minValue = flatValues[0] || START;
|
|
56
|
+
if (flatValues.length > 0) {
|
|
57
|
+
let expectedValues = Array.from({ length: flatValues.length }, (_, i) => i + minValue);
|
|
58
|
+
if (flatValues.join(",") !== expectedValues.join(",")) {
|
|
59
|
+
const errorMessage = `Values should be [${expectedValues.join(", ")}], was [${flatValues.join(", ")}]`;
|
|
60
|
+
console.error(errorMessage);
|
|
61
|
+
debugbreak(2);
|
|
62
|
+
debugger;
|
|
63
|
+
throw new Error(errorMessage);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const transaction: ArchiveTransaction = {
|
|
68
|
+
createFiles: [],
|
|
69
|
+
deleteFiles: [],
|
|
70
|
+
};
|
|
71
|
+
function removeValue(value: number) {
|
|
72
|
+
console.log(`Deleting file with value ${value}`);
|
|
73
|
+
let index = pathValues.findIndex(x => x[0] === value);
|
|
74
|
+
let file = prevFiles[index];
|
|
75
|
+
transaction.deleteFiles.push(file);
|
|
76
|
+
}
|
|
77
|
+
async function addValue(newValue: number) {
|
|
78
|
+
console.log(`Adding file with value ${newValue}`);
|
|
79
|
+
let newValues: PathValue[] = [];
|
|
80
|
+
newValues.push({
|
|
81
|
+
path: getPathStr([getDomain(), "test"]),
|
|
82
|
+
value: newValue,
|
|
83
|
+
time: getNextTime(),
|
|
84
|
+
locks: [],
|
|
85
|
+
lockCount: 0,
|
|
86
|
+
valid: true,
|
|
87
|
+
});
|
|
88
|
+
const dataObj = (await pathValueArchives.encodeValuePaths(
|
|
89
|
+
newValues,
|
|
90
|
+
{
|
|
91
|
+
pathOverrides: {
|
|
92
|
+
sourceType: "test",
|
|
93
|
+
misc: newValue + ""
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
))!;
|
|
97
|
+
transaction.createFiles.push({
|
|
98
|
+
data: dataObj.data,
|
|
99
|
+
file: authorityDir + dataObj.file,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let newCount = flatValues.length + ITERATION_COUNT;
|
|
104
|
+
let extraCount = newCount - COUNT_LIMIT;
|
|
105
|
+
for (let i = 0; i < extraCount; i++) {
|
|
106
|
+
removeValue(flatValues[i]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let lastValue = flatValues[flatValues.length - 1] ?? START;
|
|
110
|
+
for (let i = 0; i < ITERATION_COUNT; i++) {
|
|
111
|
+
await addValue(lastValue + i + 1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let timeWaitTime = Math.random() * 5 * 1000;
|
|
115
|
+
console.log(`Test wait ${formatTime(timeWaitTime)}`);
|
|
116
|
+
await delay(timeWaitTime);
|
|
117
|
+
|
|
118
|
+
return [transaction];
|
|
119
|
+
});
|
|
120
|
+
if (result === "rejected") {
|
|
121
|
+
console.warn(red("Rejected transaction"));
|
|
122
|
+
} else {
|
|
123
|
+
console.log(green("Transaction committed"));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
main().catch(console.error).finally(() => process.exit());
|
package/src/config.ts
CHANGED
|
@@ -17,6 +17,11 @@ let yargObj = isNodeTrue() && yargs(process.argv)
|
|
|
17
17
|
.argv || {}
|
|
18
18
|
;
|
|
19
19
|
|
|
20
|
+
if (!isNode()) {
|
|
21
|
+
// TODO: Get yargs running in the browser, instead of this hack processing
|
|
22
|
+
yargObj.local = process.argv.includes("--local");
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export function isNoNetwork() {
|
|
21
26
|
return yargObj.nonetwork;
|
|
22
27
|
}
|