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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.405.0",
3
+ "version": "0.406.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -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.prefix) && x.prefix !== path);
68
+ let prefix = spec.prefixes.find(x => path.startsWith(x) && x !== path);
72
69
  if (prefix) {
73
- let key = getPathIndex(path, prefix.hashIndex);
70
+ let key = getPathIndex(path, getPathDepth(prefix));
74
71
  if (key === undefined) {
75
- require("debugbreak")(2);
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 ["P", rangeStart.toString(), rangeEnd.toString(), ...prefixes.map(x => this.getPrefixHash(x)), ""].join("!");
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.prefix.length);
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.prefix) && prefix.prefix !== path) return prefix.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.prefix));
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.map(x => x.prefix);
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.map(x => x.prefix));
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.map(x => x.prefix));
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.prefix));
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.prefix)) return false;
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.prefix) && y.prefix !== path)
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.prefix) === prefixHash) {
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.map(prefix => ({
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.map(prefix => ({
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.prefix)) return true;
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 undefined;
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
- };