querysub 0.425.0 → 0.427.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.425.0",
3
+ "version": "0.427.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",
@@ -12,7 +12,7 @@ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
12
12
  import { isClient } from "../config2";
13
13
  import { getPathStr1 } from "../path";
14
14
  import { timeoutToError } from "../errors";
15
- import { AuthoritySpec } from "./PathRouter";
15
+ import { AuthoritySpec, parsePrefixMatcher } from "./PathRouter";
16
16
  import { formatTime } from "socket-function/src/formatting/format";
17
17
  import { getAllAuthoritySpec, getEmptyAuthoritySpec } from "./PathRouterServerAuthoritySpec";
18
18
 
@@ -208,10 +208,10 @@ class AuthorityLookup {
208
208
  // - Get all node IDs should restrict our nodes to just the browser node ID. If we ever change this, then either it's redundant nodes and they all have all the same data, or we need to figure out what data they have, And as their proxies, it probably won't be their actual authority data. So that will require new API functions, etc.
209
209
  await new Promise(r => setImmediate(r));
210
210
  await delay(1);
211
- let { getPrefixesForDeploy } = await import("../3-path-functions/syncSchema");
211
+ let prefixes = await this.getPrefixesForDeploy();
212
212
  this.updatePaths(nodeId, {
213
213
  nodeId: nodeId,
214
- prefixes: await getPrefixesForDeploy(),
214
+ prefixes: prefixes.map(p => parsePrefixMatcher(p)),
215
215
  routeStart: 0,
216
216
  routeEnd: 1,
217
217
  }, true);
@@ -233,6 +233,11 @@ class AuthorityLookup {
233
233
  this.updatePaths(nodeId, otherPaths.spec, otherPaths.isReady);
234
234
  this.disconnectWatch(nodeId);
235
235
  }
236
+
237
+ private async getPrefixesForDeploy() {
238
+ let { getPrefixesForDeploy } = await import("../3-path-functions/syncSchema");
239
+ return await getPrefixesForDeploy();
240
+ }
236
241
  }
237
242
  export const authorityLookup = new AuthorityLookup();
238
243
  (globalThis as any).authorityLookup = authorityLookup;
@@ -1,5 +1,5 @@
1
1
  import { sort } from "socket-function/src/misc";
2
- import { getLastPathPart, getPathDepth, getPathIndex, getPathStr1, hack_stripPackedPath } from "../path";
2
+ import { getLastPathPart, getPathDepth, getPathFromStr, getPathIndex, getPathStr, 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";
@@ -33,10 +33,65 @@ export type AuthoritySpec = {
33
33
  routeEnd: number;
34
34
  // If the path.startsWith(prefix), but prefix !== path, then we hash getPathIndex(path, hashIndex)
35
35
  // - For now, let's just never add overlapping prefixes.
36
- prefixes: string[];
36
+ prefixes: PrefixMatcher[];
37
37
  excludeDefault?: boolean;
38
38
  };
39
39
 
40
+
41
+ export type PrefixMatcher = {
42
+ prefix: string;
43
+ additionalMatches: {
44
+ value: string;
45
+ index: number;
46
+ }[];
47
+ childKeyIndex: number;
48
+ originalPrefix: string;
49
+ prefixPartLength: number;
50
+ };
51
+ function matchesPrefix(matcher: PrefixMatcher, path: string): boolean {
52
+ if (!path.startsWith(matcher.prefix)) return false;
53
+ if (matcher.prefix === path) return false;
54
+ if (getPathIndex(path, matcher.childKeyIndex) === undefined) return false;
55
+ for (let additionalMatch of matcher.additionalMatches) {
56
+ if (getPathIndex(path, additionalMatch.index) !== additionalMatch.value) {
57
+ return false;
58
+ }
59
+ }
60
+ return true;
61
+ }
62
+ /** Empty string path part becomes a wildcard */
63
+ export function parsePrefixMatcher(prefixPath: string): PrefixMatcher {
64
+ // So up until the first wild card, that's a prefix. But then after that, we then need to skip all the wild cards, of course. And then for the non-wild cards, add them as additional matches. And then obviously set the child key index to just be the part's length.
65
+ let parts = getPathFromStr(prefixPath);
66
+ let wildcardIndex = parts.indexOf("");
67
+ if (wildcardIndex === -1) {
68
+ return {
69
+ prefix: prefixPath,
70
+ additionalMatches: [],
71
+ childKeyIndex: parts.length,
72
+ originalPrefix: prefixPath,
73
+ prefixPartLength: parts.length,
74
+ };
75
+ }
76
+ let prefix = getPathStr(parts.slice(0, wildcardIndex));
77
+ let additionalMatches: { value: string; index: number }[] = [];
78
+ for (let i = wildcardIndex + 1; i < parts.length; i++) {
79
+ let part = parts[i];
80
+ if (part === "") continue;
81
+ additionalMatches.push({
82
+ value: part,
83
+ index: i,
84
+ });
85
+ }
86
+ return {
87
+ prefix,
88
+ additionalMatches,
89
+ childKeyIndex: wildcardIndex,
90
+ originalPrefix: prefixPath,
91
+ prefixPartLength: parts.length,
92
+ };
93
+ }
94
+
40
95
  export function debugSpec(spec: AuthoritySpec) {
41
96
  return {
42
97
  info: `${spec.routeStart}-${spec.routeEnd} (${spec.prefixes.length} prefixes${spec.excludeDefault ? " excluding default" : ""})`,
@@ -44,16 +99,16 @@ export function debugSpec(spec: AuthoritySpec) {
44
99
  };
45
100
  }
46
101
 
47
- function getMatchingPrefix(spec: AuthoritySpec, path: string): string | undefined {
48
- let longestPrefix: string | undefined;
49
- for (let prefix of spec.prefixes) {
50
- if (path.startsWith(prefix) && prefix !== path) {
51
- if (!longestPrefix || longestPrefix.length < prefix.length) {
52
- longestPrefix = prefix;
102
+ function getMatchingPrefix(spec: AuthoritySpec, path: string): PrefixMatcher | undefined {
103
+ let longestMatcher: PrefixMatcher | undefined;
104
+ for (let matcher of spec.prefixes) {
105
+ if (matchesPrefix(matcher, path)) {
106
+ if (!longestMatcher || longestMatcher.prefixPartLength < matcher.prefixPartLength) {
107
+ longestMatcher = matcher;
53
108
  }
54
109
  }
55
110
  }
56
- return longestPrefix;
111
+ return longestMatcher;
57
112
  }
58
113
 
59
114
 
@@ -81,11 +136,11 @@ export class PathRouter {
81
136
 
82
137
  let prefix = getMatchingPrefix(spec, path);
83
138
  if (prefix) {
84
- let key = getPathIndex(path, getPathDepth(prefix));
139
+ let key = getPathIndex(path, prefix.childKeyIndex);
85
140
  if (key === undefined) {
86
141
  require("debugbreak")(2);
87
142
  debugger;
88
- throw new Error(`Impossible, hash index ${getPathDepth(prefix)} is out of range for path ${path}, but it matched the prefix ${prefix}`);
143
+ throw new Error(`Impossible, hash index ${prefix.childKeyIndex} is out of range for path ${path}, but it matched the prefix ${prefix.originalPrefix}`);
89
144
  }
90
145
  let route = this.getSingleKeyRoute(key);
91
146
  if (route < spec.routeStart || route >= spec.routeEnd) return -1;
@@ -136,13 +191,13 @@ export class PathRouter {
136
191
  }
137
192
 
138
193
 
139
- private static getPrefixHash(prefix: string): string {
140
- return Buffer.from(sha256(prefix), "hex").toString("base64").slice(0, 6);
194
+ private static getPrefixHash(prefix: PrefixMatcher): string {
195
+ return Buffer.from(sha256(prefix.originalPrefix), "hex").toString("base64").slice(0, 6);
141
196
  }
142
197
  private static isPrefixHash(hash: string): boolean {
143
198
  return hash.length === 6 && /^[a-zA-Z0-9]+$/.test(hash);
144
199
  }
145
- private static encodeIdentifier(config: { prefixes: string[]; rangeStart: number; rangeEnd: number } | "remaining"): string {
200
+ private static encodeIdentifier(config: { prefixes: PrefixMatcher[]; rangeStart: number; rangeEnd: number } | "remaining"): string {
146
201
  if (config === "remaining") return "P!REMAINING";
147
202
  let { prefixes, rangeStart, rangeEnd } = config;
148
203
  return [
@@ -151,7 +206,7 @@ export class PathRouter {
151
206
  rangeEnd.toString(),
152
207
  ...prefixes.map(x => this.getPrefixHash(x)),
153
208
  // 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.
154
- ...prefixes.slice(0, 5).map(x => getLastPathPart(x).replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, 20)),
209
+ ...prefixes.slice(0, 5).map(x => getLastPathPart(x.originalPrefix).replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, 20)),
155
210
  ""
156
211
  ].join("!");
157
212
 
@@ -182,10 +237,10 @@ export class PathRouter {
182
237
  const SHARD_THRESHOLD = 1000;
183
238
 
184
239
  let prefixes = ourSpec.prefixes.slice();
185
- sort(prefixes, x => x.length);
240
+ sort(prefixes, x => x.prefixPartLength);
186
241
 
187
242
  // NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
188
- let byPrefix = new Map<string | undefined, PathValue[]>();
243
+ let byPrefix = new Map<PrefixMatcher | undefined, PathValue[]>();
189
244
  for (let value of values) {
190
245
  let prefix = getMatchingPrefix(ourSpec, value.path);
191
246
  let values = byPrefix.get(prefix);
@@ -203,7 +258,7 @@ export class PathRouter {
203
258
  sort(prefixGroups, x => -x.values.length);
204
259
 
205
260
  let groups: {
206
- prefixes: string[];
261
+ prefixes: PrefixMatcher[];
207
262
  values: PathValue[][];
208
263
  count: number;
209
264
  }[] = [];
@@ -305,34 +360,30 @@ export class PathRouter {
305
360
  public static overlapsAuthority(authority1: AuthoritySpec, authority2: AuthoritySpec): boolean {
306
361
  // TODO: This becomes complicated because of exclude default, although I feel like there has to be a way to simplify it? Eh... whatever.
307
362
 
308
- // Normalize it so if only one excludes default, it's always going to be the second one.
309
- if (authority1.excludeDefault && !authority2.excludeDefault) return this.overlapsAuthority(authority2, authority1);
310
-
311
363
  let doRangesOverlap = rangesOverlap({ start: authority1.routeStart, end: authority1.routeEnd }, { start: authority2.routeStart, end: authority2.routeEnd });
312
364
 
313
365
  // If their prefixes are identical, then it's purely a range check
314
- if (authority1.prefixes.length === authority2.prefixes.length && authority1.prefixes.every(x => authority2.prefixes.includes(x))) {
366
+ if (authority1.prefixes.length === authority2.prefixes.length && authority1.prefixes.every(x => authority2.prefixes.some(y => x.originalPrefix === y.originalPrefix))) {
315
367
  return doRangesOverlap;
316
368
  }
317
369
 
318
- // If they have any prefixes which are identical and the ranges overlap, then they overlap.
319
- if (doRangesOverlap) {
320
- if (authority1.prefixes.some(x => authority2.prefixes.includes(x))) {
321
- return true;
370
+ // If one has excludeDefault and is a subset of the other's prefixes, then it's a range check
371
+ if (authority1.excludeDefault) {
372
+ let authority2Prefixes = new Set(authority2.prefixes.map(x => x.originalPrefix));
373
+ let isSubset = authority1.prefixes.every(x => authority2Prefixes.has(x.originalPrefix));
374
+ if (isSubset) {
375
+ return doRangesOverlap;
322
376
  }
323
377
  }
324
- // If any of their prefixes are under the prefix and match it, then that's a match.
325
- if (authority1.prefixes.some(x => this.matchesAuthoritySpec(authority2, x))) {
326
- return true;
327
- }
328
- if (authority2.prefixes.some(x => this.matchesAuthoritySpec(authority1, x))) {
329
- return true;
330
- }
331
- if (authority1.excludeDefault && authority2.excludeDefault) {
332
- // No shared prefixes, and none of them are nested under each other, and we don't include defaults, so neither match.
333
- return false;
378
+ if (authority2.excludeDefault) {
379
+ let authority1Prefixes = new Set(authority1.prefixes.map(x => x.originalPrefix));
380
+ let isSubset = authority2.prefixes.every(x => authority1Prefixes.has(x.originalPrefix));
381
+ if (isSubset) {
382
+ return doRangesOverlap;
383
+ }
334
384
  }
335
- // If their prefixes are entirely unrelated, it means they're going to hash differently, so they do overlap.
385
+
386
+ // Otherwise, they hash differently, so they likely have overlap.
336
387
  return true;
337
388
  }
338
389
  @measureFnc
@@ -384,10 +435,10 @@ export class PathRouter {
384
435
  if (target.excludeDefault) {
385
436
  // 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.
386
437
  // (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)
387
- let targetUsedPrefixes = new Set(target.prefixes);
388
- usedPrefixes = usedPrefixes.filter(x => targetUsedPrefixes.has(x));
438
+ let targetUsedPrefixes = new Set(target.prefixes.map(x => x.originalPrefix));
439
+ usedPrefixes = usedPrefixes.filter(x => targetUsedPrefixes.has(x.originalPrefix));
389
440
  }
390
- let prefixHash = sha256(JSON.stringify(usedPrefixes.sort()));
441
+ let prefixHash = sha256(JSON.stringify(usedPrefixes.map(x => x.originalPrefix).sort()));
391
442
  let group = groupByPrefixHash.get(prefixHash);
392
443
  if (!group) {
393
444
  group = [];
@@ -403,17 +454,17 @@ export class PathRouter {
403
454
  }
404
455
 
405
456
  function hashesSameAsTarget(spec: AuthoritySpec): boolean {
406
- let targetUsedPrefixes = new Set(target.prefixes);
457
+ let targetUsedPrefixes = new Set(target.prefixes.map(x => x.originalPrefix));
407
458
  if (target.excludeDefault) {
408
459
  // 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.
409
- return spec.prefixes.every(x => targetUsedPrefixes.has(x));
460
+ return spec.prefixes.every(x => targetUsedPrefixes.has(x.originalPrefix));
410
461
  }
411
462
  // if !targetExcludeDefault, then !spec.excludeDefault, because we filtered out other cases earlier (so we don't need to handle spec.
412
463
 
413
464
  if (spec.prefixes.length !== target.prefixes.length) return false;
414
465
  // Otherwise, All of the prefixes have to be identical
415
466
  for (let prefix of spec.prefixes) {
416
- if (!targetUsedPrefixes.has(prefix)) return false;
467
+ if (!targetUsedPrefixes.has(prefix.originalPrefix)) return false;
417
468
  }
418
469
  return true;
419
470
  }
@@ -498,7 +549,7 @@ export class PathRouter {
498
549
 
499
550
 
500
551
  // Direct prefix. This happens for things like calls and functions, it requires more advanced routing as it means we're going to route between multiple servers, but... it is important
501
- let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => y === path)).map(x => x.authoritySpec);
552
+ let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => y.prefix === path)).map(x => x.authoritySpec);
502
553
  if (hasPrefix.length > 0) {
503
554
  shuffle(hasPrefix, Math.random());
504
555
  sort(hasPrefix, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
@@ -534,11 +585,11 @@ export class PathRouter {
534
585
  let nestedMatches = allSources.filter(x => {
535
586
  // There's nested prefixes, so if we match any prefix explicitly, we can't just take one of the previous prefixes because that isn't how the hashing will work.
536
587
  // - This happens if it's a direct match, but one of the shards is down, in which case we can't get a full match.
537
- if (x.authoritySpec.prefixes.some(y => y === path)) return false;
588
+ if (x.authoritySpec.prefixes.some(y => y.prefix === path)) return false;
538
589
 
539
590
  // If our path, which we're going to read the children of, is the child of another path, then it means in that other path, the child key will be known to us constant, and so we're going to match exactly one authority.
540
591
  return (
541
- x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path)
592
+ x.authoritySpec.prefixes.some(y => matchesPrefix(y, path))
542
593
  && this.matchesAuthoritySpec(x.authoritySpec, path)
543
594
  );
544
595
  });
@@ -558,7 +609,7 @@ export class PathRouter {
558
609
 
559
610
  // If we are not under any prefixes of it, then it will be a full path hash
560
611
  let fullPathMatches = allSources.filter(x => {
561
- return !x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path) && !x.authoritySpec.excludeDefault;
612
+ return !x.authoritySpec.prefixes.some(y => matchesPrefix(y, path)) && !x.authoritySpec.excludeDefault;
562
613
  });
563
614
  // Same as prefix matches. Not preferred, and not preferred over being under a prefix, but required for some root data, or data with no prefixes.
564
615
  if (fullPathMatches.length > 0) {
@@ -600,7 +651,7 @@ export class PathRouter {
600
651
  // TODO: We could maybe match a partial match. However, even that is suspect. The site being partially broken is almost worse than it being completely broken. We should just get ALL the shards running again...
601
652
 
602
653
  // NOTE: We *could* actually synchronize it even if it doesn't have a prefix shard as we can fall back to just the full path sharding. However, it becomes very complicated if we want a specific range, and then it becomes complicated if it then switches to prefix hashing (With the nodes that were using the full path hashing slowly going away). AND... key synchronization IS slow, so it's good to discourage it in general.
603
- console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). Path: ${JSON.stringify(path)}`, { path, allSources });
654
+ console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). Path: ${JSON.stringify(path)}`, { path, allSources: allSources.map(x => x.authoritySpec) });
604
655
  }
605
656
  return { nodes: [] };
606
657
  }
@@ -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) === prefixHash) {
65
+ if (getPrefixHash(prefix.originalPrefix) === prefixHash) {
66
66
  return true;
67
67
  }
68
68
  }
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import { AuthoritySpec } from "./PathRouter";
2
+ import { AuthoritySpec, parsePrefixMatcher } from "./PathRouter";
3
3
  import { getPathDepth } from "../path";
4
4
  import { getAuthorityRange, getAuthorityExcludeDefault, getAuthorityPrefix } from "../config";
5
5
  import { getShardPrefixes } from "./ShardPrefixes";
@@ -9,6 +9,7 @@ export async function getOurAuthoritySpec(defaultToAll: true): Promise<Authority
9
9
  export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<AuthoritySpec | undefined>;
10
10
  export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<AuthoritySpec | undefined> {
11
11
  let prefixes = await getShardPrefixes();
12
+ let prefixMatchers = prefixes.map(p => parsePrefixMatcher(p));
12
13
 
13
14
  const range = getAuthorityRange();
14
15
  const excludeDefault = getAuthorityExcludeDefault();
@@ -20,7 +21,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
20
21
  nodeId: "",
21
22
  routeStart: 0,
22
23
  routeEnd: 1,
23
- prefixes: prefixes,
24
+ prefixes: prefixMatchers,
24
25
  };
25
26
  }
26
27
  return undefined;
@@ -34,9 +35,9 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
34
35
  throw new Error(`Invalid authority range, should be in the format "0.5-1.0": ${range}`);
35
36
  }
36
37
 
37
- let usePrefixes = prefixes;
38
+ let usePrefixes = prefixMatchers;
38
39
  if (cmdPrefixes && cmdPrefixes.length > 0) {
39
- usePrefixes = cmdPrefixes;
40
+ usePrefixes = cmdPrefixes.map(p => parsePrefixMatcher(p));
40
41
  }
41
42
 
42
43
  return {
@@ -60,10 +61,11 @@ export function getEmptyAuthoritySpec(): AuthoritySpec {
60
61
  }
61
62
  export async function getAllAuthoritySpec(): Promise<AuthoritySpec> {
62
63
  let prefixes = await getShardPrefixes();
64
+ let prefixMatchers = prefixes.map(p => parsePrefixMatcher(p));
63
65
  return {
64
66
  nodeId: "",
65
67
  routeStart: 0,
66
68
  routeEnd: 1,
67
- prefixes: prefixes,
69
+ prefixes: prefixMatchers,
68
70
  };
69
71
  }
@@ -369,8 +369,10 @@ class PathWatcher {
369
369
 
370
370
  // initialTriggers.parentPaths => triggerPaths
371
371
  for (let parentPath of initialTriggers.parentPaths) {
372
- let valuePaths = authorityStorage.getPathsFromParent(parentPath);
372
+ // NOTE: We have to trigger all of the values without the hacked path restriction. This should be safe, if a little bit inefficient, because we're still not going to send the values to watchers who aren't watching them. But we need to trigger all of it because it might be the case that we had synchronized 99% of the path and someone was watching 100% of the path and we just finished synchronizing the last 1%. So the parent path trigger that we received only has 1%, but we need to send everything. And it's too complicated to determine exactly what we need to send as in the intersection of all the watchers. So it's easier to just trigger all the value paths and what they're watching will naturally be sent.
373
+ let valuePaths = authorityStorage.getPathsFromParent(hack_stripPackedPath(parentPath));
373
374
  for (let valuePath of valuePaths || []) {
375
+ if (!authorityStorage.isSynced(valuePath)) continue;
374
376
  // IMPORTANT! Add all the values to the initial triggers so then later we not only trigger them but know their initial trigger so we can make sure that the is initial trigger logic for each value runs as well.
375
377
  initialTriggers.values.add(valuePath);
376
378
  triggerPaths.add(valuePath);
@@ -378,7 +380,9 @@ class PathWatcher {
378
380
 
379
381
  let latestParentWatches = this.parentWatchers.get(hack_stripPackedPath(parentPath));
380
382
  if (!latestParentWatches) continue;
381
- for (let { watchers } of latestParentWatches.values()) {
383
+ for (let [packedPath, { watchers }] of latestParentWatches) {
384
+ // ONLY trigger, if packedPath is fully synchronized
385
+ if (!authorityStorage.isParentSynced(packedPath)) continue;
382
386
  for (let watcher of watchers) {
383
387
  if (onlyTriggerNodeId && watcher !== onlyTriggerNodeId) continue;
384
388
  let changes = changedPerCallbacks.get(watcher);
@@ -386,7 +390,7 @@ class PathWatcher {
386
390
  changes = { values: new Set(), initialTriggers: { values: new Set(), parentPaths: new Set() } };
387
391
  changedPerCallbacks.set(watcher, changes);
388
392
  }
389
- changes.initialTriggers.parentPaths.add(parentPath);
393
+ changes.initialTriggers.parentPaths.add(packedPath);
390
394
  }
391
395
  }
392
396
  }
@@ -14,6 +14,9 @@ import { nestArchives } from "../-a-archives/archives";
14
14
  import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
15
15
  import { archiveJSONT } from "../-a-archives/archivesJSONT";
16
16
  import { getDomain } from "../config";
17
+ import { sort } from "socket-function/src/misc";
18
+ import { getPathFromStr, getPathStr } from "../path";
19
+ import { green, red } from "socket-function/src/formatting/logColors";
17
20
 
18
21
  type PrefixObj = {
19
22
  prefixes: string[];
@@ -30,8 +33,25 @@ export async function getShardPrefixes(): Promise<string[]> {
30
33
 
31
34
  export async function setShardPrefixes(_prefixes: string[]) {
32
35
  console.log("Setting shard prefixes");
36
+ _prefixes = _prefixes.slice();
37
+
38
+ const oldPrefixes = await getShardPrefixes();
39
+ const oldSet = new Set(oldPrefixes);
40
+ const newSet = new Set(_prefixes);
41
+
33
42
  for (let prefix of _prefixes) {
34
- console.log("Prefix:", prefix);
43
+ if (!oldSet.has(prefix)) {
44
+ console.log(green(`Prefix: ${prefix}`));
45
+ } else {
46
+ console.log(`Prefix: ${prefix}`);
47
+ }
35
48
  }
49
+
50
+ for (let prefix of oldPrefixes) {
51
+ if (!newSet.has(prefix)) {
52
+ console.log(red(`Prefix: ${prefix}`));
53
+ }
54
+ }
55
+
36
56
  await prefixes.set(key, { prefixes: _prefixes });
37
57
  }
@@ -50,18 +50,12 @@ class ValidStateComputer {
50
50
  let parentPath = getParentPathStr(value.path);
51
51
  // We check for the parent trigger late because getting the parent path is a little bit expensive
52
52
  if (!config.initialTriggers.parentPaths.has(parentPath)) {
53
- if (PathRouter.isLocalPath(value.path)) {
54
- require("debugbreak")(2);
55
- debugger;
56
- }
57
-
58
- console.log(`Ignoring duplicate value.`, {
53
+ console.info(`Ignoring duplicate value.`, {
59
54
  path: value.path,
60
55
  timeId: value.time.time,
61
56
  timeIdVersion: value.time.version,
62
57
  timeIdFull: value.time,
63
- existingValid: existingValue?.valid,
64
- newValid: value.valid,
58
+ valid: value.valid,
65
59
  });
66
60
  // NOTE: This should be safe. If this causes any problems, it will probably be if we're not the authority on it. In which case, we could move this check so it only happens if we are the authority on it. However, I think even if we aren't the authority, if we already have the exact value and the valid state hasn't changed, we really shouldn't have to ingest it. Watchers should be triggering when they start watching, so they shouldn't need us to trigger them again by re-ingesting it.
67
61
  return false;
@@ -150,6 +150,19 @@ type TypeDefType<T = never, Optional = false> = {
150
150
  schema: NewT
151
151
  ): TypeDefType<{ [key: string | number]: TypeDefToT<NewT> }>;
152
152
 
153
+ /** Lookup with GC enabled, but excluded from root lookup routing.
154
+ * Behaves like a regular lookup but won't be used as a prefix for sharding.
155
+ */
156
+ lookupNoRoot<NewT>(
157
+ schema: NewT
158
+ ): TypeDefType<{ [key: string]: TypeDefToT<NewT> }>;
159
+ lookupNumberNoRoot<NewT>(
160
+ schema: NewT
161
+ ): TypeDefType<{ [key: number]: TypeDefToT<NewT> }>;
162
+ lookupEitherNoRoot<NewT>(
163
+ schema: NewT
164
+ ): TypeDefType<{ [key: string | number]: TypeDefToT<NewT> }>;
165
+
153
166
 
154
167
 
155
168
  // TODO: Support .union, which just spreads the inputs together, making an object which
@@ -247,22 +260,42 @@ export class Schema2Fncs {
247
260
  return schema2ToTypeString(schema);
248
261
  }
249
262
 
250
- /** Uses * for wildcards */
251
- public static getLookups(schema: Schema2): string[][] {
252
- let internalType = typeDefCached(schema);
253
- let lookupPaths = new Set<string>();
254
-
255
- for (let def of internalType.defs) {
256
- for (let i = 0; i < def.path.length; i++) {
257
- if (def.path[i] === WildCard) {
258
- let lookupPath = def.path.slice(0, i);
259
- let stringPath = lookupPath.map(p => p === WildCard ? "*" : p as string);
260
- lookupPaths.add(JSON.stringify(stringPath));
263
+ public static getRootLookups(schema: Schema2): string[][] {
264
+ let defs = typeDefCached(schema).defs.slice();
265
+
266
+ sort(defs, x => x.path.length);
267
+
268
+ let rootLookups = new Set<string>();
269
+ for (let def of defs) {
270
+ if (def.isLookupNoRoot) continue;
271
+ if (!def.isObjectGC) continue;
272
+ let path = def.path;
273
+ if (path.at(-1) !== WildCard) continue;
274
+ path = path.slice(0, -1);
275
+
276
+ let pathParts = path.map(p => p === WildCard ? "" : p);
277
+ let pathKey = JSON.stringify(pathParts);
278
+
279
+ let hasAncestor = false;
280
+ for (let i = 0; i < path.length - 1; i++) {
281
+ let ancestorPath = path.slice(0, i + 1);
282
+ let ancestorParts = ancestorPath.map(p => p === WildCard ? "" : p);
283
+ let ancestorKey = JSON.stringify(ancestorParts);
284
+ if (rootLookups.has(ancestorKey)) {
285
+ hasAncestor = true;
286
+ break;
261
287
  }
262
288
  }
289
+
290
+ if (!hasAncestor) {
291
+ rootLookups.add(pathKey);
292
+ }
263
293
  }
264
294
 
265
- return Array.from(lookupPaths).map(p => JSON.parse(p));
295
+ return Array.from(rootLookups).map(p => {
296
+ let parts = JSON.parse(p) as string[];
297
+ return parts;
298
+ });
266
299
  }
267
300
  }
268
301
 
@@ -518,6 +551,8 @@ type InternalTypeDef = {
518
551
  isBoolean?: boolean;
519
552
  isNull?: boolean;
520
553
 
554
+ isLookupNoRoot?: boolean;
555
+
521
556
  gcDelay?: number;
522
557
 
523
558
  defaultValue?: unknown;
@@ -544,6 +579,7 @@ function typeDefTypeToInternalType(
544
579
  }
545
580
  let isLookup = false;
546
581
  let isLookupGC = false;
582
+ let isLookupNoRoot = false;
547
583
  if (typeDef.path.includes("lookup") || typeDef.path.includes("lookupNumber") || typeDef.path.includes("lookupEither")) {
548
584
  isLookup = true;
549
585
  isLookupGC = true;
@@ -552,6 +588,11 @@ function typeDefTypeToInternalType(
552
588
  isLookup = true;
553
589
  isLookupGC = false;
554
590
  }
591
+ if (typeDef.path.includes("lookupNoRoot") || typeDef.path.includes("lookupNumberNoRoot") || typeDef.path.includes("lookupEitherNoRoot")) {
592
+ isLookup = true;
593
+ isLookupGC = true;
594
+ isLookupNoRoot = true;
595
+ }
555
596
 
556
597
  let isObject = false;
557
598
  let isObjectGC = false;
@@ -594,6 +635,7 @@ function typeDefTypeToInternalType(
594
635
  // NOTE: Gets reset if the nestedValues are used with a primitive
595
636
  nestedValues.isNotAtomic = true;
596
637
  nestedValues.isObjectGC = isObjectGC || isLookupGC;
638
+ nestedValues.isLookupNoRoot = isLookupNoRoot;
597
639
  nestedValues.gcDelay = Number(typeDef.pathWithCalls.find(x => x.key === "gcDelay")?.callParams?.[0]) || undefined;
598
640
 
599
641
 
@@ -372,27 +372,11 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
372
372
  };
373
373
  }
374
374
 
375
- // Register all root lookups as prefixes.
376
375
  let dataRoot = getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Data);
377
- let paths = Schema2Fncs.getLookups(schema).map(x =>
378
- joinPathStres(dataRoot, getPathStr(x))
379
- );
380
- // NOTE: This will break with wildcards, but... we also take only the highest level lookups, so it shouldn't be an issue
381
- sort(paths, x => x.length);
382
- let rootLookups = new Set<string>();
383
- for (let path of paths) {
384
- let parts = getPathFromStr(path);
385
- // Don't add if any of our ancestors are added
386
- function hasAncestor() {
387
- for (let i = 0; i < parts.length - 1; i++) {
388
- let ancestorPath = getPathStr(parts.slice(0, i));
389
- if (rootLookups.has(ancestorPath)) return true;
390
- }
391
- }
392
- if (!hasAncestor()) {
393
- rootLookups.add(path);
394
- prefixes.push(path);
395
- }
376
+ let rootLookups = Schema2Fncs.getRootLookups(schema);
377
+ for (let lookup of rootLookups) {
378
+ let path = joinPathStres(dataRoot, getPathStr(lookup));
379
+ prefixes.push(path);
396
380
  }
397
381
  }
398
382
 
@@ -7,6 +7,9 @@ async function main() {
7
7
  await import(deployPath);
8
8
 
9
9
  let prefixes = await getPrefixesForDeploy();
10
+ for (let prefix of prefixes) {
11
+ console.log(prefix);
12
+ }
10
13
  await setShardPrefixes(prefixes);
11
14
  }
12
15
 
@@ -16,6 +16,8 @@ import { sort } from "socket-function/src/misc";
16
16
  import { PathAuditerController } from "./pathAuditer";
17
17
  import { t } from "../2-proxy/schema2";
18
18
 
19
+ //todonext
20
+ // So... We need to double write, to write to multiple keys at once, in something that's not a lookup, or no, is a lookup, but before we deploy, so it doesn't have the proper or no, a nested lookup, even better.
19
21
 
20
22
  let { data, functions } = Querysub.createSchema({
21
23
  values: t.lookup(t.number)
package/test.ts CHANGED
@@ -16,6 +16,7 @@ import { ClientWatcher } from "./src/1-path-client/pathValueClientWatcher";
16
16
  import { RemoteWatcher } from "./src/1-path-client/RemoteWatcher";
17
17
  import { PathRouter } from "./src/0-path-value-core/PathRouter";
18
18
  import { shutdown } from "./src/diagnostics/periodic";
19
+ import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
19
20
 
20
21
  let tempTestSchema = Querysub.createSchema({
21
22
  value: t.number,
@@ -27,7 +28,12 @@ let tempTestSchema = Querysub.createSchema({
27
28
  });
28
29
 
29
30
  async function main() {
30
- await Querysub.hostService("test");
31
+
32
+ let prefixes = await getShardPrefixes();
33
+ for (let prefix of prefixes) {
34
+ console.log(prefix);
35
+ }
36
+ // await Querysub.hostService("test");
31
37
 
32
38
  // let path = getProxyPath(() => tempTestSchema.data().value);
33
39
  // console.log({ path });
@@ -40,14 +46,14 @@ async function main() {
40
46
  // ClientWatcher.DEBUG_WRITES = true;
41
47
  // RemoteWatcher.DEBUG = true;
42
48
 
43
- let value = await Querysub.commitAsync(() => tempTestSchema.data().value);
44
- console.log({ value });
45
- await Querysub.commitAsync(() => tempTestSchema.data().value++, { doNotStoreWritesAsPredictions: true });
46
- await delay(3000);
47
- let value2 = await Querysub.commitAsync(() => tempTestSchema.data().value);
48
- console.log({ value2 });
49
+ // let value = await Querysub.commitAsync(() => tempTestSchema.data().value);
50
+ // console.log({ value });
51
+ // await Querysub.commitAsync(() => tempTestSchema.data().value++, { doNotStoreWritesAsPredictions: true });
52
+ // await delay(3000);
53
+ // let value2 = await Querysub.commitAsync(() => tempTestSchema.data().value);
54
+ // console.log({ value2 });
49
55
 
50
- await shutdown();
56
+ // await shutdown();
51
57
 
52
58
  // let test = await Querysub.commitAsync(() => {
53
59
  // let live = deploySchema()[getDomain()].deploy.live.hash;