querysub 0.404.0 → 0.406.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/bin/join.js +1 -1
- package/package.json +1 -1
- package/src/0-path-value-core/PathRouter.ts +33 -28
- package/src/0-path-value-core/PathRouterRouteOverride.ts +1 -1
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +7 -5
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +1 -1
- package/src/0-path-value-core/pathValueArchives.ts +2 -1
- package/src/2-proxy/archiveMoveHarness.ts +2 -2
- package/src/2-proxy/garbageCollection.ts +4 -3
- package/src/3-path-functions/syncSchema.ts +6 -2
- package/src/4-deploy/edgeClientWatcher.tsx +5 -0
- package/src/archiveapps/archiveJoinEntry.ts +30 -21
- package/src/functional/promiseCache.ts +5 -2
- package/src/path.ts +6 -0
- package/tempnotes.txt +0 -2
- package/test.ts +23 -17
- package/src/archiveapps/archiveMergeEntry.tsx +0 -50
- package/src/diagnostics/logs/BrowserLargeFileCache.ts +0 -64
package/bin/join.js
CHANGED
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sort } from "socket-function/src/misc";
|
|
2
|
-
import { getLastPathPart, getPathDepth, getPathIndex, getPathStr1 } from "../path";
|
|
2
|
+
import { getLastPathPart, getPathDepth, getPathIndex, getPathStr1, hack_stripPackedPath } from "../path";
|
|
3
3
|
import { AuthorityEntry, authorityLookup } from "./AuthorityLookup";
|
|
4
4
|
import { PathValue } from "./pathValueCore";
|
|
5
5
|
import { shuffle } from "../misc/random";
|
|
@@ -32,10 +32,7 @@ export type AuthoritySpec = {
|
|
|
32
32
|
routeEnd: number;
|
|
33
33
|
// If the path.startsWith(prefix), but prefix !== path, then we hash getPathIndex(path, hashIndex)
|
|
34
34
|
// - For now, let's just never add overlapping prefixes.
|
|
35
|
-
prefixes:
|
|
36
|
-
prefix: string;
|
|
37
|
-
hashIndex: number;
|
|
38
|
-
}[];
|
|
35
|
+
prefixes: string[];
|
|
39
36
|
excludeDefault?: boolean;
|
|
40
37
|
};
|
|
41
38
|
|
|
@@ -59,18 +56,21 @@ export class PathRouter {
|
|
|
59
56
|
path: string;
|
|
60
57
|
spec: AuthoritySpec;
|
|
61
58
|
}): number {
|
|
62
|
-
// NOTE:
|
|
59
|
+
// NOTE: getPathIdentifierTargets also hardcodes this logic, so it can hash many values quickly
|
|
63
60
|
let { path, spec } = config;
|
|
61
|
+
path = hack_stripPackedPath(path);
|
|
64
62
|
let override = getRoutingOverride(path);
|
|
65
63
|
if (override) {
|
|
66
64
|
if (!hasPrefixHash({ spec, prefixHash: override.prefixHash })) return -1;
|
|
67
65
|
return override.route;
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
let prefix = spec.prefixes.find(x => path.startsWith(x
|
|
68
|
+
let prefix = spec.prefixes.find(x => path.startsWith(x) && x !== path);
|
|
71
69
|
if (prefix) {
|
|
72
|
-
let key = getPathIndex(path, prefix
|
|
73
|
-
if (key === undefined)
|
|
70
|
+
let key = getPathIndex(path, getPathDepth(prefix));
|
|
71
|
+
if (key === undefined) {
|
|
72
|
+
throw new Error(`Impossible, hash index ${getPathDepth(prefix)} is out of range for path ${path}, but it matched the prefix ${prefix}`);
|
|
73
|
+
}
|
|
74
74
|
return this.getSingleKeyRoute(key);
|
|
75
75
|
}
|
|
76
76
|
if (spec.excludeDefault) return -1;
|
|
@@ -107,13 +107,21 @@ export class PathRouter {
|
|
|
107
107
|
private static encodeIdentifier(config: { prefixes: string[]; rangeStart: number; rangeEnd: number } | "remaining"): string {
|
|
108
108
|
if (config === "remaining") return "P!REMAINING";
|
|
109
109
|
let { prefixes, rangeStart, rangeEnd } = config;
|
|
110
|
-
return [
|
|
110
|
+
return [
|
|
111
|
+
"P",
|
|
112
|
+
rangeStart.toString(),
|
|
113
|
+
rangeEnd.toString(),
|
|
114
|
+
...prefixes.map(x => this.getPrefixHash(x)),
|
|
115
|
+
// Add some prefixes. They'll be interpreted as hashes, but it's fine because it's highly unlikely they'll collide with a real hash. AND... It's very useful for debugging what's in which file.
|
|
116
|
+
...prefixes.slice(0, 5).map(x => getLastPathPart(x).replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, 20)),
|
|
117
|
+
""
|
|
118
|
+
].join("!");
|
|
111
119
|
|
|
112
120
|
}
|
|
113
121
|
private static decodeIdentifier(identifier: string): { prefixHashes: string[]; rangeStart: number; rangeEnd: number } | "remaining" {
|
|
114
122
|
if (!identifier.startsWith("P")) return "remaining";
|
|
115
123
|
let parts = identifier.split("!");
|
|
116
|
-
if (parts[1]
|
|
124
|
+
if (parts[1].startsWith("REMAINING")) return "remaining";
|
|
117
125
|
return {
|
|
118
126
|
rangeStart: parseFloat(parts[1]),
|
|
119
127
|
rangeEnd: parseFloat(parts[2]),
|
|
@@ -125,7 +133,7 @@ export class PathRouter {
|
|
|
125
133
|
// NOTE: Encodes all the data, even if it matches our spec or not. If you don't want this, you have to filter first
|
|
126
134
|
// - If this becomes an issue we COULD filter, as we can do it quickly, but I don't think it is required, as all the present usecases prefilter anyways.
|
|
127
135
|
@measureFnc
|
|
128
|
-
public static
|
|
136
|
+
public static getPathIdentifierTargets(values: PathValue[], ourSpec: AuthoritySpec): Map<string, PathValue[]> {
|
|
129
137
|
// NOTE: The file size limit is 1024 bytes. But we also have our folder, etc, so we want to add enough buffer
|
|
130
138
|
// - Shorter hashes means we can store more, but there's a point when the collisions make it less useful.
|
|
131
139
|
const MAX_PREFIXES_PER_FILE = 50;
|
|
@@ -135,13 +143,11 @@ export class PathRouter {
|
|
|
135
143
|
return new Map([[this.encodeIdentifier("remaining"), values]]);
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
let ourSpec = authorityLookup.getOurSpec();
|
|
139
|
-
|
|
140
146
|
let prefixes = ourSpec.prefixes.slice();
|
|
141
|
-
sort(prefixes, x => x.
|
|
147
|
+
sort(prefixes, x => x.length);
|
|
142
148
|
function getPrefix(path: string): string | undefined {
|
|
143
149
|
for (let prefix of prefixes) {
|
|
144
|
-
if (path.startsWith(prefix
|
|
150
|
+
if (path.startsWith(prefix) && prefix !== path) return prefix;
|
|
145
151
|
}
|
|
146
152
|
return undefined;
|
|
147
153
|
}
|
|
@@ -216,7 +222,9 @@ export class PathRouter {
|
|
|
216
222
|
let hashIndex = getPathDepth(prefix);
|
|
217
223
|
for (let value of values) {
|
|
218
224
|
let key = getPathIndex(value.path, hashIndex);
|
|
219
|
-
if (key === undefined)
|
|
225
|
+
if (key === undefined) {
|
|
226
|
+
throw new Error(`Impossible, hash index ${hashIndex} is out of range for path ${value.path}, but it matched the prefix ${prefix}`);
|
|
227
|
+
}
|
|
220
228
|
let route = this.getSingleKeyRoute(key);
|
|
221
229
|
let routeIndex = Math.floor(route * splitCount);
|
|
222
230
|
let routeValues = byRouteGroup.get(routeIndex);
|
|
@@ -253,7 +261,7 @@ export class PathRouter {
|
|
|
253
261
|
// Match all remaining, as we don't store enough information to know what prefixes they excluded
|
|
254
262
|
if (decodeObj === "remaining") return true;
|
|
255
263
|
|
|
256
|
-
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x
|
|
264
|
+
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x));
|
|
257
265
|
// If it pulled off some values as prefixes, but we did full path hashes, then the hashes are going to be totally different. And so there could easily be overlap.
|
|
258
266
|
if (decodeObj.prefixHashes.some(x => !ourHashes.includes(x))) return true;
|
|
259
267
|
// However, if we hash everything the same, then the overlap is purely a case of if the route ranges overlap.
|
|
@@ -297,12 +305,12 @@ export class PathRouter {
|
|
|
297
305
|
|
|
298
306
|
let groupByPrefixHash = new Map<string, AuthoritySpec[]>();
|
|
299
307
|
for (let source of allSources) {
|
|
300
|
-
let usedPrefixes = source.authoritySpec.prefixes
|
|
308
|
+
let usedPrefixes = source.authoritySpec.prefixes;
|
|
301
309
|
|
|
302
310
|
if (target.excludeDefault) {
|
|
303
311
|
// This relaxes the restrictions, as with no default hashes, it means if a value isn't in one of our prefixes, even if it's inconsistent in the sources, it's fine.
|
|
304
312
|
// (And... Otherwise, we can't filter these at all because otherwise the default/remaining group will be different, even if it's a superset/subset relationship)
|
|
305
|
-
let targetUsedPrefixes = new Set(target.prefixes
|
|
313
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
306
314
|
usedPrefixes = usedPrefixes.filter(x => targetUsedPrefixes.has(x));
|
|
307
315
|
}
|
|
308
316
|
let prefixHash = sha256(JSON.stringify(usedPrefixes.sort()));
|
|
@@ -321,17 +329,17 @@ export class PathRouter {
|
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
function hashesSameAsTarget(spec: AuthoritySpec): boolean {
|
|
324
|
-
let targetUsedPrefixes = new Set(target.prefixes
|
|
332
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
325
333
|
if (target.excludeDefault) {
|
|
326
334
|
// If target is excluding default, then it doesn't matter if the spec is including it. We're only looking for matching values in target.
|
|
327
|
-
return spec.prefixes.every(x => targetUsedPrefixes.has(x
|
|
335
|
+
return spec.prefixes.every(x => targetUsedPrefixes.has(x));
|
|
328
336
|
}
|
|
329
337
|
// if !targetExcludeDefault, then !spec.excludeDefault, because we filtered out other cases earlier (so we don't need to handle spec.
|
|
330
338
|
|
|
331
339
|
if (spec.prefixes.length !== target.prefixes.length) return false;
|
|
332
340
|
// Otherwise, All of the prefixes have to be identical
|
|
333
341
|
for (let prefix of spec.prefixes) {
|
|
334
|
-
if (!targetUsedPrefixes.has(prefix
|
|
342
|
+
if (!targetUsedPrefixes.has(prefix)) return false;
|
|
335
343
|
}
|
|
336
344
|
return true;
|
|
337
345
|
}
|
|
@@ -432,7 +440,7 @@ export class PathRouter {
|
|
|
432
440
|
let allSources = authorityLookup.getTopologySync();
|
|
433
441
|
allSources = allSources.filter(x => !isOwnNodeId(x.nodeId));
|
|
434
442
|
let nestedMatches = allSources.filter(x =>
|
|
435
|
-
x.authoritySpec.prefixes.some(y => path.startsWith(y
|
|
443
|
+
x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path)
|
|
436
444
|
&& this.matchesAuthoritySpec(x.authoritySpec, path)
|
|
437
445
|
);
|
|
438
446
|
if (nestedMatches.length > 0) {
|
|
@@ -451,10 +459,7 @@ export class PathRouter {
|
|
|
451
459
|
let fullSources = this.getAuthoritySources({
|
|
452
460
|
target: {
|
|
453
461
|
nodeId: "",
|
|
454
|
-
prefixes: [
|
|
455
|
-
prefix: path,
|
|
456
|
-
hashIndex: getPathDepth(path),
|
|
457
|
-
}],
|
|
462
|
+
prefixes: [path],
|
|
458
463
|
routeStart: 0,
|
|
459
464
|
routeEnd: 1,
|
|
460
465
|
excludeDefault: true,
|
|
@@ -62,7 +62,7 @@ export const hasPrefixHash = cacheLimited(1000 * 10,
|
|
|
62
62
|
(config: { spec: AuthoritySpec, prefixHash: string }) => {
|
|
63
63
|
let { spec, prefixHash } = config;
|
|
64
64
|
for (let prefix of spec.prefixes) {
|
|
65
|
-
if (getPrefixHash(prefix
|
|
65
|
+
if (getPrefixHash(prefix) === prefixHash) {
|
|
66
66
|
return true;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -16,7 +16,12 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
16
16
|
|
|
17
17
|
if (!range) {
|
|
18
18
|
if (defaultToAll) {
|
|
19
|
-
return
|
|
19
|
+
return {
|
|
20
|
+
nodeId: "",
|
|
21
|
+
routeStart: 0,
|
|
22
|
+
routeEnd: 1,
|
|
23
|
+
prefixes: prefixes,
|
|
24
|
+
};
|
|
20
25
|
}
|
|
21
26
|
return undefined;
|
|
22
27
|
}
|
|
@@ -38,10 +43,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
38
43
|
nodeId: "",
|
|
39
44
|
routeStart: rangeStart,
|
|
40
45
|
routeEnd: rangeEnd,
|
|
41
|
-
prefixes: usePrefixes
|
|
42
|
-
prefix,
|
|
43
|
-
hashIndex: getPathDepth(prefix),
|
|
44
|
-
})),
|
|
46
|
+
prefixes: usePrefixes,
|
|
45
47
|
excludeDefault: excludeDefault || undefined,
|
|
46
48
|
};
|
|
47
49
|
}
|
|
@@ -20,7 +20,7 @@ let isPrefix = cache((path: string) => {
|
|
|
20
20
|
let spec = authorityLookup.getOurSpec();
|
|
21
21
|
if (!spec) return false;
|
|
22
22
|
for (let prefix of spec.prefixes) {
|
|
23
|
-
if (path.startsWith(prefix
|
|
23
|
+
if (path.startsWith(prefix)) return true;
|
|
24
24
|
}
|
|
25
25
|
return false;
|
|
26
26
|
});
|
|
@@ -13,6 +13,7 @@ import { createArchiveLocker2 } from "./archiveLocks/ArchiveLocks2";
|
|
|
13
13
|
import { devDebugbreak, isNoNetwork } from "../config";
|
|
14
14
|
import { wrapArchivesWithCache } from "../-a-archives/archiveCache";
|
|
15
15
|
import { AuthoritySpec, PathRouter } from "./PathRouter";
|
|
16
|
+
import { authorityLookup } from "./AuthorityLookup";
|
|
16
17
|
|
|
17
18
|
export const archives = lazy(() => wrapArchivesWithCache(getArchives("path-values/")));
|
|
18
19
|
export const archivesLocks = lazy(() => getArchives("path-values-locks/"));
|
|
@@ -161,7 +162,7 @@ export class PathValueArchives {
|
|
|
161
162
|
|
|
162
163
|
@measureFnc
|
|
163
164
|
public async archiveValues(values: PathValue[]) {
|
|
164
|
-
let parts = PathRouter.
|
|
165
|
+
let parts = PathRouter.getPathIdentifierTargets(values, authorityLookup.getOurSpec());
|
|
165
166
|
|
|
166
167
|
for (let [pathIdentifier, values] of parts) {
|
|
167
168
|
let encodedObj = await this.encodeValuePaths(values);
|
|
@@ -7,7 +7,7 @@ import { PathValue, VALUE_GC_THRESHOLD, FILE_VALUE_COUNT_LIMIT, FILE_SIZE_LIMIT,
|
|
|
7
7
|
import { getSingleSizeEstimate } from "../5-diagnostics/shared";
|
|
8
8
|
import { logNodeStats } from "../-0-hooks/hooks";
|
|
9
9
|
import debugbreak from "debugbreak";
|
|
10
|
-
import { AuthorityEntry } from "../0-path-value-core/AuthorityLookup";
|
|
10
|
+
import { AuthorityEntry, authorityLookup } from "../0-path-value-core/AuthorityLookup";
|
|
11
11
|
import { AuthoritySpec, PathRouter } from "../0-path-value-core/PathRouter";
|
|
12
12
|
import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
|
|
13
13
|
|
|
@@ -249,7 +249,7 @@ export async function runArchiveMover(config: {
|
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
for (let [key, values] of Object.entries(result.newValues)) {
|
|
252
|
-
let targets = PathRouter.
|
|
252
|
+
let targets = PathRouter.getPathIdentifierTargets(values, authority);
|
|
253
253
|
for (let [target, values] of targets) {
|
|
254
254
|
await addValues(target + "/", values, key);
|
|
255
255
|
}
|
|
@@ -19,6 +19,7 @@ import { getStorageDir, getSubFolder } from "../fs";
|
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import { sha256 } from "js-sha256";
|
|
21
21
|
import { AuthoritySpec, PathRouter } from "../0-path-value-core/PathRouter";
|
|
22
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
export interface AliveChecker<T = unknown> {
|
|
@@ -34,7 +35,7 @@ export interface AliveChecker<T = unknown> {
|
|
|
34
35
|
* NOTE: The value might only have a single heavily nested value, not being a valid object. If the object
|
|
35
36
|
* lookups broken, probably just return false, to clean up the straggling values.
|
|
36
37
|
*/
|
|
37
|
-
isAlive: (value: T, path: string[]) => boolean | unknown;
|
|
38
|
+
isAlive: (value: T, path: string[], valuePath: PathValue | undefined) => boolean | unknown;
|
|
38
39
|
|
|
39
40
|
/** Alias for lockBasedDelete & unsafeLiveReads */
|
|
40
41
|
lock?: boolean;
|
|
@@ -254,7 +255,7 @@ function sandboxedIsAlive(config: {
|
|
|
254
255
|
proxy = proxy[part];
|
|
255
256
|
}
|
|
256
257
|
try {
|
|
257
|
-
return !!atomicRaw(checker.isAlive(proxy, path));
|
|
258
|
+
return !!atomicRaw(checker.isAlive(proxy, path, undefined));
|
|
258
259
|
} catch {
|
|
259
260
|
config.errors.count++;
|
|
260
261
|
return true;
|
|
@@ -308,7 +309,7 @@ function sandboxedGetLocks(config: {
|
|
|
308
309
|
|
|
309
310
|
let evalResult = getWatchEvaluator()(() => {
|
|
310
311
|
let value = proxyWatcher.getCallbackPathValue(pathStr);
|
|
311
|
-
return !!atomic(checker.isAlive(value, path));
|
|
312
|
+
return !!atomic(checker.isAlive(pathValueSerializer.getPathValue(value), path, value));
|
|
312
313
|
}, { getReadLocks: true });
|
|
313
314
|
|
|
314
315
|
return evalResult.readLocks!;
|
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import { cache, lazy } from "socket-function/src/caching";
|
|
3
3
|
import { getStringKeys, sort } from "socket-function/src/misc";
|
|
4
4
|
import { Args } from "socket-function/src/types";
|
|
5
|
-
import { appendToPathStr, getPathFromStr, getPathStr, rootPathStr } from "../path";
|
|
5
|
+
import { appendToPathStr, getPathFromStr, getPathStr, joinPathStres, rootPathStr } from "../path";
|
|
6
6
|
import { writeFunctionCall } from "./PathFunctionHelpers";
|
|
7
7
|
import { CallSpec, functionSchema } from "./PathFunctionRunner";
|
|
8
8
|
import { getDomain, isLocal } from "../config";
|
|
@@ -323,6 +323,7 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
|
323
323
|
createObjectAliveChecker(data, path, gcDelay);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
|
|
326
327
|
let undoChecks = Schema2Fncs.getAllUndoChecks(schema);
|
|
327
328
|
for (let checkObj of undoChecks) {
|
|
328
329
|
// Need at least one check
|
|
@@ -368,7 +369,10 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
// Register all root lookups as prefixes.
|
|
371
|
-
let
|
|
372
|
+
let dataRoot = getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Data);
|
|
373
|
+
let paths = Schema2Fncs.getLookups(schema).map(x =>
|
|
374
|
+
joinPathStres(dataRoot, getPathStr(x))
|
|
375
|
+
);
|
|
372
376
|
// NOTE: This will break with wildcards, but... we also take only the highest level lookups, so it shouldn't be an issue
|
|
373
377
|
sort(paths, x => x.length);
|
|
374
378
|
let rootLookups = new Set<string>();
|
|
@@ -16,6 +16,11 @@ import { lazy } from "socket-function/src/caching";
|
|
|
16
16
|
import { Button } from "../library-components/Button";
|
|
17
17
|
import { updateRootDiscoveryLocation } from "../-f-node-discovery/NodeDiscovery";
|
|
18
18
|
|
|
19
|
+
setImmediate(() => {
|
|
20
|
+
// Modal
|
|
21
|
+
import("../5-diagnostics/Modal");
|
|
22
|
+
});
|
|
23
|
+
|
|
19
24
|
const SWITCH_SERVER_TIMEOUT = timeInSecond * 15;
|
|
20
25
|
const MAX_DISPLAY_INTERVAL = timeInMinute * 5;
|
|
21
26
|
const FINAL_DELAY = timeInSecond * 30;
|
|
@@ -2,23 +2,29 @@ import "../inject";
|
|
|
2
2
|
|
|
3
3
|
import { logErrors } from "../errors";
|
|
4
4
|
import { PathValueArchives, pathValueArchives } from "../0-path-value-core/pathValueArchives";
|
|
5
|
-
import {
|
|
5
|
+
import { PathValue, VALUE_GC_THRESHOLD } from "../0-path-value-core/pathValueCore";
|
|
6
6
|
import { runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
7
7
|
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
8
8
|
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
9
9
|
import { ArchiveTransaction, FileInfo } from "../0-path-value-core/archiveLocks/ArchiveLocks";
|
|
10
10
|
import { formatNumber } from "socket-function/src/formatting/format";
|
|
11
|
-
import { sort } from "socket-function/src/misc";
|
|
11
|
+
import { isNodeTrue, sort } from "socket-function/src/misc";
|
|
12
12
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
13
13
|
import { magenta } from "socket-function/src/formatting/logColors";
|
|
14
14
|
import { PathRouter } from "../0-path-value-core/PathRouter";
|
|
15
15
|
import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
|
|
16
16
|
import { getOurAuthoritySpec } from "../0-path-value-core/PathRouterServerAuthoritySpec";
|
|
17
|
+
import yargs from "yargs";
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
20
|
+
.option("watch", { type: "boolean", desc: "If true, we only join genesis values, but we join in a loop, otherwise we just join absolutely everything, and then exit." })
|
|
21
|
+
.argv || {}
|
|
22
|
+
;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async function runGenesisJoinIteration(config?: { force?: boolean }) {
|
|
19
26
|
let authoritySpec = await getOurAuthoritySpec(true);
|
|
20
27
|
let locker = await pathValueArchives.getArchiveLocker();
|
|
21
|
-
let maxAge = Date.now() - VALUE_GC_THRESHOLD;
|
|
22
28
|
|
|
23
29
|
let readCache = new Map<string, Buffer>();
|
|
24
30
|
let reread = true;
|
|
@@ -31,15 +37,16 @@ async function runGenesisJoinIteration() {
|
|
|
31
37
|
// Merge the newest first, so if we hit a big file, we can just ignore it,
|
|
32
38
|
// and next time merge the smaller files after it.
|
|
33
39
|
sort(valueFiles, x => -x.createTime);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
if (!config?.force) {
|
|
41
|
+
valueFiles = valueFiles.filter(x => {
|
|
42
|
+
let obj = pathValueArchives.decodeDataPath(x.file);
|
|
43
|
+
if (!obj.minTime) return false;
|
|
44
|
+
return obj.sourceType === "genesis";
|
|
45
|
+
});
|
|
46
|
+
}
|
|
40
47
|
let withinTimeRangeCount = valueFiles.length;
|
|
41
48
|
|
|
42
|
-
if (valueFiles.length < 3) return [];
|
|
49
|
+
if (valueFiles.length < 3 && !config?.force) return [];
|
|
43
50
|
|
|
44
51
|
await measureBlock(async function locker_readFiles() {
|
|
45
52
|
let remainingFiles = valueFiles.filter(x => !readCache.has(x.file));
|
|
@@ -65,28 +72,20 @@ async function runGenesisJoinIteration() {
|
|
|
65
72
|
let allValues: PathValue[][] = [];
|
|
66
73
|
for (let valueFile of valueFiles) {
|
|
67
74
|
let buffer = readCache.get(valueFile.file)!;
|
|
68
|
-
if (totalSize + buffer.length > FILE_SIZE_LIMIT) {
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
75
|
let newValues = await PathValueArchives.loadValuesFromBuffer({
|
|
72
76
|
path: valueFile.file,
|
|
73
77
|
data: buffer,
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
let readValues = newValues.values;
|
|
77
|
-
if (readValues.length + totalCount > FILE_VALUE_COUNT_LIMIT) {
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
81
|
allValues.push(readValues);
|
|
81
82
|
usedFiles.push(valueFile);
|
|
82
83
|
totalSize += buffer.length;
|
|
83
84
|
totalCount += readValues.length;
|
|
84
85
|
}
|
|
85
|
-
if (usedFiles.length <= 1) return [];
|
|
86
86
|
|
|
87
87
|
let allCombinedValues = allValues.flat();
|
|
88
88
|
|
|
89
|
-
|
|
90
89
|
let transaction: ArchiveTransaction = {
|
|
91
90
|
createFiles: [],
|
|
92
91
|
deleteFiles: [],
|
|
@@ -95,7 +94,7 @@ async function runGenesisJoinIteration() {
|
|
|
95
94
|
transaction.deleteFiles.push(file);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
let targets = PathRouter.
|
|
97
|
+
let targets = PathRouter.getPathIdentifierTargets(allCombinedValues, authoritySpec);
|
|
99
98
|
for (let [target, values] of targets) {
|
|
100
99
|
|
|
101
100
|
let dataObj = await pathValueArchives.encodeValuePaths(values, {
|
|
@@ -123,7 +122,17 @@ async function main() {
|
|
|
123
122
|
disablePathAuditer();
|
|
124
123
|
await Querysub.hostService("join");
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
if (yargObj.watch) {
|
|
126
|
+
await runInfinitePollCallAtStart(VALUE_GC_THRESHOLD * 0.8, runGenesisJoinIteration);
|
|
127
|
+
} else {
|
|
128
|
+
try {
|
|
129
|
+
await runGenesisJoinIteration({ force: true });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(error);
|
|
132
|
+
} finally {
|
|
133
|
+
process.exit();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
main().catch(logErrors);
|
|
@@ -34,7 +34,10 @@ export function cacheAsyncLimited<Arg, Return>(limit: number, fnc: (arg: Arg) =>
|
|
|
34
34
|
export const cacheAsyncSynced = cacheAsyncLimitedJSON;
|
|
35
35
|
export function cacheAsyncLimitedJSON<Arg, Return>(
|
|
36
36
|
limit: number,
|
|
37
|
-
fnc: (arg: Arg) => Promise<Return
|
|
37
|
+
fnc: (arg: Arg) => Promise<Return>,
|
|
38
|
+
config?: {
|
|
39
|
+
notReadyValue?: Return;
|
|
40
|
+
}
|
|
38
41
|
): ((arg: Arg) => Return | undefined) & {
|
|
39
42
|
clear(): void;
|
|
40
43
|
promise: (arg: Arg) => Promise<Return>;
|
|
@@ -71,6 +74,6 @@ export function cacheAsyncLimitedJSON<Arg, Return>(
|
|
|
71
74
|
}
|
|
72
75
|
);
|
|
73
76
|
proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: fnc.toString() });
|
|
74
|
-
return
|
|
77
|
+
return config?.notReadyValue;
|
|
75
78
|
};
|
|
76
79
|
}
|
package/src/path.ts
CHANGED
|
@@ -181,8 +181,12 @@ export function removePathLastPart(pathStr: string) {
|
|
|
181
181
|
return pathStr.slice(0, getStartOfLastPart(pathStr));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
let lastPastPath = { path: "", depth: 0 };
|
|
184
185
|
/** getPathDepth(pathStr) === getPathFromStr(pathStr).length */
|
|
185
186
|
export function getPathDepth(pathStr: string): number {
|
|
187
|
+
if (lastPastPath.path === pathStr) {
|
|
188
|
+
return lastPastPath.depth;
|
|
189
|
+
}
|
|
186
190
|
// Start at 1, to skip the first pathDelimit
|
|
187
191
|
let index = 1;
|
|
188
192
|
// Start at -1, as the last loop doesn't have pathDelimitEscaped, but is still incremented
|
|
@@ -191,6 +195,8 @@ export function getPathDepth(pathStr: string): number {
|
|
|
191
195
|
count++;
|
|
192
196
|
index = pathStr.indexOf(pathDelimitEscaped, index) + 1;
|
|
193
197
|
}
|
|
198
|
+
lastPastPath.path = pathStr;
|
|
199
|
+
lastPastPath.depth = count;
|
|
194
200
|
return count;
|
|
195
201
|
}
|
|
196
202
|
|
package/tempnotes.txt
CHANGED
package/test.ts
CHANGED
|
@@ -3,6 +3,7 @@ chdir("D:/repos/qs-cyoa/");
|
|
|
3
3
|
|
|
4
4
|
import "./inject";
|
|
5
5
|
|
|
6
|
+
import path from "path";
|
|
6
7
|
import { list, sort } from "socket-function/src/misc";
|
|
7
8
|
import { formatNumber } from "socket-function/src/formatting/format";
|
|
8
9
|
import { PathValueArchives, archives, archivesLocks, archivesRecycleBin, pathValueArchives } from "./src/0-path-value-core/pathValueArchives";
|
|
@@ -19,26 +20,29 @@ import { unique } from "./src/misc";
|
|
|
19
20
|
import { getAllAuthoritySpec, getOurAuthoritySpec } from "./src/0-path-value-core/PathRouterServerAuthoritySpec";
|
|
20
21
|
import { PathRouter } from "./src/0-path-value-core/PathRouter";
|
|
21
22
|
import { pathValueSerializer } from "./src/-h-path-value-serialize/PathValueSerializer";
|
|
23
|
+
import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
|
|
22
24
|
|
|
23
25
|
async function main() {
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
let prefixes = await getShardPrefixes();
|
|
27
|
+
console.log(prefixes);
|
|
28
|
+
// let paths = await pathValueArchives.getValuePaths(getAllAuthoritySpec());
|
|
29
|
+
// paths = paths.filter(x => x.startsWith("P!REMAINING/"));
|
|
30
|
+
// for (let path of paths) {
|
|
31
|
+
// let data = await archives().get(path);
|
|
32
|
+
// if (!data) {
|
|
33
|
+
// console.log(`Missing data file: ${path}`);
|
|
34
|
+
// continue;
|
|
35
|
+
// }
|
|
36
|
+
// let loadObj = await PathValueArchives.loadValuesFromBuffer({ path, data });
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
return;
|
|
38
|
+
// for (let value of loadObj.values) {
|
|
39
|
+
// if (!PathRouter.matchesAuthoritySpec(getAllAuthoritySpec(), value.path)) {
|
|
40
|
+
// throw new Error(`Value ${value.path} does not match authority spec`);
|
|
41
|
+
// }
|
|
42
|
+
// console.log(pathValueSerializer.getPathValue(value));
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
// return;
|
|
42
46
|
|
|
43
47
|
// let path = "P!REMAINING/data_37c5d323e0f9a083.1ed7a7340a015680.querysubtest.com:52026_1775097754671.579_3_1_337_1775097652829.5793_1775097652829.5793_genesis_.data";
|
|
44
48
|
// let value = await archives().get(path);
|
|
@@ -54,6 +58,7 @@ async function main() {
|
|
|
54
58
|
// return path.split("/").at(-1)!;
|
|
55
59
|
// }
|
|
56
60
|
|
|
61
|
+
//*
|
|
57
62
|
let allValuesObj = await pathValueArchives.loadValues({
|
|
58
63
|
nodeId: "",
|
|
59
64
|
prefixes: [],
|
|
@@ -62,6 +67,7 @@ async function main() {
|
|
|
62
67
|
});
|
|
63
68
|
let allValues = Object.values(allValuesObj.values).flat();
|
|
64
69
|
console.log(`Total values loaded: ${formatNumber(allValues.length)}`);
|
|
70
|
+
//*/
|
|
65
71
|
|
|
66
72
|
// const badDeleteTransaction = "transaction_ tSeqNum=887 tWriteTime=1774715868374.9143 thread=9dc0 cCount=14 dCount=3482 create=X,X,X,X,X,X,X,X,X,X,X,X,X,X delete=2@0977,3@0977,4@0977,1@0a2a,2@0a2a,3@0a2a,4@0a2... .transaction";
|
|
67
73
|
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import "../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
|
-
import { disablePathAuditer } from "../diagnostics/pathAuditerCallback";
|
|
9
|
-
|
|
10
|
-
const MERGE_DELAY = timeInHour;
|
|
11
|
-
|
|
12
|
-
async function mergeFiles() {
|
|
13
|
-
await runArchiveMover({
|
|
14
|
-
debugName: "merge",
|
|
15
|
-
outputType: "merge",
|
|
16
|
-
async runMover(config) {
|
|
17
|
-
let values = config.values;
|
|
18
|
-
let latestValues = new Map<string, PathValue>();
|
|
19
|
-
for (let value of values) {
|
|
20
|
-
let prev = latestValues.get(value.path);
|
|
21
|
-
if (prev && compareTime(value.time, prev.time) < 0) continue;
|
|
22
|
-
latestValues.set(value.path, value);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
values = Array.from(latestValues.values());
|
|
26
|
-
values = values.filter(x => !x.canGCValue);
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
newValues: {
|
|
30
|
-
"": values,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function main() {
|
|
38
|
-
disablePathAuditer();
|
|
39
|
-
await Querysub.hostService("merge");
|
|
40
|
-
await mergeFiles();
|
|
41
|
-
process.exit();
|
|
42
|
-
|
|
43
|
-
// while (true) {
|
|
44
|
-
// await mergeFiles();
|
|
45
|
-
// let delay = MERGE_DELAY * (1 + Math.random() * 0.1);
|
|
46
|
-
// await new Promise(resolve => setTimeout(resolve, delay));
|
|
47
|
-
// }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
main().catch(logErrors);
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { SocketFunctionClientHook } from "socket-function/SocketFunctionTypes";
|
|
2
|
-
import { cache, lazy } from "socket-function/src/caching";
|
|
3
|
-
import { decodeCborx, encodeCborx } from "../../misc/cloneHelpers";
|
|
4
|
-
import { sha256 } from "js-sha256";
|
|
5
|
-
import { errorToUndefined } from "../../errors";
|
|
6
|
-
import { isNode } from "typesafecss";
|
|
7
|
-
|
|
8
|
-
let getRootDirectory = lazy(async () => {
|
|
9
|
-
await navigator.storage.persist();
|
|
10
|
-
return navigator.storage.getDirectory();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
export const getBrowserLargeFileCache = cache((name: string) => new BrowserLargeFileCache(name));
|
|
14
|
-
|
|
15
|
-
export class BrowserLargeFileCache {
|
|
16
|
-
constructor(private name: string) { }
|
|
17
|
-
|
|
18
|
-
private getDir = lazy(async () => {
|
|
19
|
-
let root = await getRootDirectory();
|
|
20
|
-
return await root.getDirectoryHandle(this.name, { create: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
public async set(key: string, value: Buffer) {
|
|
24
|
-
let dir = await this.getDir();
|
|
25
|
-
let file = await dir.getFileHandle(key, { create: true });
|
|
26
|
-
let writable = await file.createWritable();
|
|
27
|
-
await writable.write(value);
|
|
28
|
-
await writable.close();
|
|
29
|
-
}
|
|
30
|
-
public async get(key: string): Promise<Buffer | undefined> {
|
|
31
|
-
let dir = await this.getDir();
|
|
32
|
-
try {
|
|
33
|
-
let file = await dir.getFileHandle(key, { create: false });
|
|
34
|
-
let readable = await file.getFile();
|
|
35
|
-
return Buffer.from(await readable.arrayBuffer());
|
|
36
|
-
} catch {
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Cache key = [args, functionName, classGuid]
|
|
43
|
-
* - Not nodeId, as that change so frequently, so caching based on server is not usually useful.
|
|
44
|
-
* - If you want to cache based on nodeId, pass it as an unused arg.
|
|
45
|
-
*
|
|
46
|
-
* Uses a file per key, and never cleans them up. So... basically, this sucks, but, it should
|
|
47
|
-
* work for a few specific functions with large and consistent values.
|
|
48
|
-
*/
|
|
49
|
-
export const cacheCalls: SocketFunctionClientHook = async config => {
|
|
50
|
-
if (isNode()) return;
|
|
51
|
-
|
|
52
|
-
let { args, functionName, classGuid } = config.call;
|
|
53
|
-
let bucket = sha256(JSON.stringify({ functionName, classGuid }));
|
|
54
|
-
let cache = getBrowserLargeFileCache(bucket);
|
|
55
|
-
let key = sha256(encodeCborx(args));
|
|
56
|
-
let cachedValue = await cache.get(key);
|
|
57
|
-
if (cachedValue) {
|
|
58
|
-
config.overrideResult = decodeCborx(cachedValue);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
config.onResult.push(async (result) => {
|
|
62
|
-
await errorToUndefined(cache.set(key, encodeCborx(result)));
|
|
63
|
-
});
|
|
64
|
-
};
|