querysub 0.405.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/package.json +1 -1
- package/src/0-path-value-core/PathRouter.ts +23 -26
- package/src/0-path-value-core/PathRouterRouteOverride.ts +1 -1
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +2 -8
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +1 -1
- package/src/2-proxy/garbageCollection.ts +4 -3
- package/src/functional/promiseCache.ts +5 -2
- package/src/path.ts +6 -0
- package/test.ts +2 -4
- package/src/diagnostics/logs/BrowserLargeFileCache.ts +0 -64
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -68,13 +65,11 @@ export class PathRouter {
|
|
|
68
65
|
return override.route;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
let prefix = spec.prefixes.find(x => path.startsWith(x
|
|
68
|
+
let prefix = spec.prefixes.find(x => path.startsWith(x) && x !== path);
|
|
72
69
|
if (prefix) {
|
|
73
|
-
let key = getPathIndex(path, prefix
|
|
70
|
+
let key = getPathIndex(path, getPathDepth(prefix));
|
|
74
71
|
if (key === undefined) {
|
|
75
|
-
|
|
76
|
-
debugger;
|
|
77
|
-
throw new Error(`Impossible, hash index ${prefix.hashIndex} is out of range for path ${path}, but it matched the prefix ${prefix.prefix}`);
|
|
72
|
+
throw new Error(`Impossible, hash index ${getPathDepth(prefix)} is out of range for path ${path}, but it matched the prefix ${prefix}`);
|
|
78
73
|
}
|
|
79
74
|
return this.getSingleKeyRoute(key);
|
|
80
75
|
}
|
|
@@ -112,7 +107,15 @@ export class PathRouter {
|
|
|
112
107
|
private static encodeIdentifier(config: { prefixes: string[]; rangeStart: number; rangeEnd: number } | "remaining"): string {
|
|
113
108
|
if (config === "remaining") return "P!REMAINING";
|
|
114
109
|
let { prefixes, rangeStart, rangeEnd } = config;
|
|
115
|
-
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("!");
|
|
116
119
|
|
|
117
120
|
}
|
|
118
121
|
private static decodeIdentifier(identifier: string): { prefixHashes: string[]; rangeStart: number; rangeEnd: number } | "remaining" {
|
|
@@ -141,10 +144,10 @@ export class PathRouter {
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
let prefixes = ourSpec.prefixes.slice();
|
|
144
|
-
sort(prefixes, x => x.
|
|
147
|
+
sort(prefixes, x => x.length);
|
|
145
148
|
function getPrefix(path: string): string | undefined {
|
|
146
149
|
for (let prefix of prefixes) {
|
|
147
|
-
if (path.startsWith(prefix
|
|
150
|
+
if (path.startsWith(prefix) && prefix !== path) return prefix;
|
|
148
151
|
}
|
|
149
152
|
return undefined;
|
|
150
153
|
}
|
|
@@ -168,9 +171,6 @@ export class PathRouter {
|
|
|
168
171
|
}));
|
|
169
172
|
sort(prefixGroups, x => -x.values.length);
|
|
170
173
|
|
|
171
|
-
require("debugbreak")(2);
|
|
172
|
-
debugger;
|
|
173
|
-
|
|
174
174
|
let groups: {
|
|
175
175
|
prefixes: string[];
|
|
176
176
|
values: PathValue[][];
|
|
@@ -261,7 +261,7 @@ export class PathRouter {
|
|
|
261
261
|
// Match all remaining, as we don't store enough information to know what prefixes they excluded
|
|
262
262
|
if (decodeObj === "remaining") return true;
|
|
263
263
|
|
|
264
|
-
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x
|
|
264
|
+
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x));
|
|
265
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.
|
|
266
266
|
if (decodeObj.prefixHashes.some(x => !ourHashes.includes(x))) return true;
|
|
267
267
|
// However, if we hash everything the same, then the overlap is purely a case of if the route ranges overlap.
|
|
@@ -305,12 +305,12 @@ export class PathRouter {
|
|
|
305
305
|
|
|
306
306
|
let groupByPrefixHash = new Map<string, AuthoritySpec[]>();
|
|
307
307
|
for (let source of allSources) {
|
|
308
|
-
let usedPrefixes = source.authoritySpec.prefixes
|
|
308
|
+
let usedPrefixes = source.authoritySpec.prefixes;
|
|
309
309
|
|
|
310
310
|
if (target.excludeDefault) {
|
|
311
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.
|
|
312
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)
|
|
313
|
-
let targetUsedPrefixes = new Set(target.prefixes
|
|
313
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
314
314
|
usedPrefixes = usedPrefixes.filter(x => targetUsedPrefixes.has(x));
|
|
315
315
|
}
|
|
316
316
|
let prefixHash = sha256(JSON.stringify(usedPrefixes.sort()));
|
|
@@ -329,17 +329,17 @@ export class PathRouter {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
function hashesSameAsTarget(spec: AuthoritySpec): boolean {
|
|
332
|
-
let targetUsedPrefixes = new Set(target.prefixes
|
|
332
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
333
333
|
if (target.excludeDefault) {
|
|
334
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.
|
|
335
|
-
return spec.prefixes.every(x => targetUsedPrefixes.has(x
|
|
335
|
+
return spec.prefixes.every(x => targetUsedPrefixes.has(x));
|
|
336
336
|
}
|
|
337
337
|
// if !targetExcludeDefault, then !spec.excludeDefault, because we filtered out other cases earlier (so we don't need to handle spec.
|
|
338
338
|
|
|
339
339
|
if (spec.prefixes.length !== target.prefixes.length) return false;
|
|
340
340
|
// Otherwise, All of the prefixes have to be identical
|
|
341
341
|
for (let prefix of spec.prefixes) {
|
|
342
|
-
if (!targetUsedPrefixes.has(prefix
|
|
342
|
+
if (!targetUsedPrefixes.has(prefix)) return false;
|
|
343
343
|
}
|
|
344
344
|
return true;
|
|
345
345
|
}
|
|
@@ -440,7 +440,7 @@ export class PathRouter {
|
|
|
440
440
|
let allSources = authorityLookup.getTopologySync();
|
|
441
441
|
allSources = allSources.filter(x => !isOwnNodeId(x.nodeId));
|
|
442
442
|
let nestedMatches = allSources.filter(x =>
|
|
443
|
-
x.authoritySpec.prefixes.some(y => path.startsWith(y
|
|
443
|
+
x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path)
|
|
444
444
|
&& this.matchesAuthoritySpec(x.authoritySpec, path)
|
|
445
445
|
);
|
|
446
446
|
if (nestedMatches.length > 0) {
|
|
@@ -459,10 +459,7 @@ export class PathRouter {
|
|
|
459
459
|
let fullSources = this.getAuthoritySources({
|
|
460
460
|
target: {
|
|
461
461
|
nodeId: "",
|
|
462
|
-
prefixes: [
|
|
463
|
-
prefix: path,
|
|
464
|
-
hashIndex: getPathDepth(path),
|
|
465
|
-
}],
|
|
462
|
+
prefixes: [path],
|
|
466
463
|
routeStart: 0,
|
|
467
464
|
routeEnd: 1,
|
|
468
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
|
}
|
|
@@ -20,10 +20,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
20
20
|
nodeId: "",
|
|
21
21
|
routeStart: 0,
|
|
22
22
|
routeEnd: 1,
|
|
23
|
-
prefixes: prefixes
|
|
24
|
-
prefix,
|
|
25
|
-
hashIndex: getPathDepth(prefix),
|
|
26
|
-
})),
|
|
23
|
+
prefixes: prefixes,
|
|
27
24
|
};
|
|
28
25
|
}
|
|
29
26
|
return undefined;
|
|
@@ -46,10 +43,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
46
43
|
nodeId: "",
|
|
47
44
|
routeStart: rangeStart,
|
|
48
45
|
routeEnd: rangeEnd,
|
|
49
|
-
prefixes: usePrefixes
|
|
50
|
-
prefix,
|
|
51
|
-
hashIndex: getPathDepth(prefix),
|
|
52
|
-
})),
|
|
46
|
+
prefixes: usePrefixes,
|
|
53
47
|
excludeDefault: excludeDefault || undefined,
|
|
54
48
|
};
|
|
55
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
|
});
|
|
@@ -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!;
|
|
@@ -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/test.ts
CHANGED
|
@@ -25,8 +25,6 @@ import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
|
|
|
25
25
|
async function main() {
|
|
26
26
|
let prefixes = await getShardPrefixes();
|
|
27
27
|
console.log(prefixes);
|
|
28
|
-
let deployPath = path.resolve("./deploy.ts");
|
|
29
|
-
await import(deployPath);
|
|
30
28
|
// let paths = await pathValueArchives.getValuePaths(getAllAuthoritySpec());
|
|
31
29
|
// paths = paths.filter(x => x.startsWith("P!REMAINING/"));
|
|
32
30
|
// for (let path of paths) {
|
|
@@ -60,7 +58,7 @@ async function main() {
|
|
|
60
58
|
// return path.split("/").at(-1)!;
|
|
61
59
|
// }
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
//*
|
|
64
62
|
let allValuesObj = await pathValueArchives.loadValues({
|
|
65
63
|
nodeId: "",
|
|
66
64
|
prefixes: [],
|
|
@@ -69,7 +67,7 @@ async function main() {
|
|
|
69
67
|
});
|
|
70
68
|
let allValues = Object.values(allValuesObj.values).flat();
|
|
71
69
|
console.log(`Total values loaded: ${formatNumber(allValues.length)}`);
|
|
72
|
-
|
|
70
|
+
//*/
|
|
73
71
|
|
|
74
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";
|
|
75
73
|
|
|
@@ -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
|
-
};
|