querysub 0.152.0 → 0.154.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/package.json +6 -6
  2. package/src/-b-authorities/cloudflareHelpers.ts +11 -2
  3. package/src/3-path-functions/PathFunctionRunner.ts +168 -97
  4. package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
  5. package/src/3-path-functions/pathFunctionLoader.ts +11 -6
  6. package/src/3-path-functions/syncSchema.ts +10 -1
  7. package/src/4-deploy/edgeBootstrap.ts +10 -1
  8. package/src/4-querysub/Querysub.ts +77 -3
  9. package/src/4-querysub/QuerysubController.ts +22 -2
  10. package/src/4-querysub/permissions.ts +33 -2
  11. package/src/4-querysub/querysubPrediction.ts +52 -18
  12. package/src/archiveapps/archiveGCEntry.tsx +38 -0
  13. package/src/archiveapps/archiveJoinEntry.ts +121 -0
  14. package/src/archiveapps/archiveMergeEntry.tsx +47 -0
  15. package/src/archiveapps/compressTest.tsx +59 -0
  16. package/src/archiveapps/lockTest.ts +127 -0
  17. package/src/config.ts +5 -0
  18. package/src/diagnostics/managementPages.tsx +55 -0
  19. package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
  20. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
  21. package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
  22. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
  23. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
  24. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
  25. package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
  26. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
  27. package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
  28. package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
  29. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
  30. package/src/email/postmark.tsx +40 -0
  31. package/src/email/sendgrid.tsx +44 -0
  32. package/src/functional/UndoWatch.tsx +133 -0
  33. package/src/functional/diff.ts +858 -0
  34. package/src/functional/promiseCache.ts +67 -0
  35. package/src/functional/random.ts +9 -0
  36. package/src/functional/runCommand.ts +42 -0
  37. package/src/functional/runOnce.ts +7 -0
  38. package/src/functional/stats.ts +61 -0
  39. package/src/functional/throttleRerender.tsx +80 -0
  40. package/src/library-components/AspectSizedComponent.tsx +88 -0
  41. package/src/library-components/Histogram.tsx +338 -0
  42. package/src/library-components/InlinePopup.tsx +67 -0
  43. package/src/library-components/Notifications.tsx +153 -0
  44. package/src/library-components/RenderIfVisible.tsx +80 -0
  45. package/src/library-components/SimpleNotification.tsx +133 -0
  46. package/src/library-components/TabbedUI.tsx +39 -0
  47. package/src/library-components/animateAnyElement.tsx +65 -0
  48. package/src/library-components/errorNotifications.tsx +81 -0
  49. package/src/library-components/placeholder.ts +18 -0
  50. package/src/misc/format2.ts +48 -0
  51. package/src/misc.ts +33 -0
  52. package/src/misc2.ts +5 -0
  53. package/src/server.ts +2 -1
  54. package/src/storage/diskCache.ts +227 -0
  55. package/src/storage/diskCache2.ts +122 -0
  56. package/src/storage/fileSystemPointer.ts +72 -0
  57. package/src/user-implementation/LoginPage.tsx +78 -0
  58. package/src/user-implementation/RequireAuditPage.tsx +219 -0
  59. package/src/user-implementation/SecurityPage.tsx +212 -0
  60. package/src/user-implementation/UserPage.tsx +320 -0
  61. package/src/user-implementation/addSuperUser.ts +21 -0
  62. package/src/user-implementation/canSeeSource.ts +41 -0
  63. package/src/user-implementation/loginEmail.tsx +159 -0
  64. package/src/user-implementation/setEmailKey.ts +20 -0
  65. package/src/user-implementation/userData.ts +974 -0
@@ -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: 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 = cache((domainName: string) => {
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
- // TODO: I think our use of filePath, moduleId, etc are wrong here? We give pathFunctionLoader the filePath,
27
- // but we really should just give it the moduleId, or... even just avoid calling it altogether, as it doesn't
28
- // do too much for us if we already have the fully resolved path...
29
- // - Although using it DOES allow permissions checks to work nicely, so, eh... maybe it is fine to use pathFunctionLoader?
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 remove one.
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
- const { functionSpec } = await proxyWatcher.commitFunction({
494
- watchFunction: function getModuleConfig() {
495
- let domainObject = functionSchema()[call.DomainName];
496
- let moduleObject = domainObject.PathFunctionRunner[call.ModuleId];
497
- let functionSpec = atomicObjectRead(moduleObject.Sources[call.FunctionId]);
498
- return { functionSpec };
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
- if (!functionSpec) {
502
- throw new Error(`Function not found in database ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
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: functionSpec }, () => {
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
  }