querysub 0.424.0 → 0.426.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/AuthorityLookup.ts +8 -3
- package/src/0-path-value-core/PathRouter.ts +98 -47
- 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/PathValueCommitter.ts +53 -32
- package/src/0-path-value-core/PathValueController.ts +2 -1
- package/src/0-path-value-core/ShardPrefixes.ts +21 -1
- package/src/0-path-value-core/ValidStateComputer.ts +26 -4
- package/src/0-path-value-core/pathValueCore.ts +1 -1
- package/src/2-proxy/schema2.ts +54 -12
- package/src/3-path-functions/syncSchema.ts +4 -20
- package/src/4-deploy/deployPrefixes.ts +3 -0
- package/src/4-querysub/Querysub.ts +0 -2
- package/src/diagnostics/SyncTestPage.tsx +2 -0
- package/src/diagnostics/logs/IndexedLogs/BufferIndexLogsOptimizationConstants.ts +2 -1
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +1 -2
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +5 -5
- package/src/diagnostics/logs/ObjectDisplay.tsx +1 -1
- package/test.ts +14 -8
package/package.json
CHANGED
|
@@ -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
|
|
211
|
+
let prefixes = await this.getPrefixesForDeploy();
|
|
212
212
|
this.updatePaths(nodeId, {
|
|
213
213
|
nodeId: nodeId,
|
|
214
|
-
prefixes:
|
|
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:
|
|
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):
|
|
48
|
-
let
|
|
49
|
-
for (let
|
|
50
|
-
if (
|
|
51
|
-
if (!
|
|
52
|
-
|
|
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
|
|
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,
|
|
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 ${
|
|
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:
|
|
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:
|
|
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.
|
|
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<
|
|
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:
|
|
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.
|
|
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
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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 =>
|
|
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 =>
|
|
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) {
|
|
@@ -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:
|
|
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 =
|
|
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:
|
|
69
|
+
prefixes: prefixMatchers,
|
|
68
70
|
};
|
|
69
71
|
}
|
|
@@ -15,7 +15,7 @@ import debugbreak from "debugbreak";
|
|
|
15
15
|
import { red } from "socket-function/src/formatting/logColors";
|
|
16
16
|
import { isClient } from "../config2";
|
|
17
17
|
import { auditLog, isDebugLogEnabled } from "./auditLogs";
|
|
18
|
-
import { authorityLookup } from "./AuthorityLookup";
|
|
18
|
+
import { AuthorityEntry, authorityLookup } from "./AuthorityLookup";
|
|
19
19
|
import { debugNodeId } from "../-c-identity/IdentityController";
|
|
20
20
|
import { decodeNodeId } from "../-a-auth/certs";
|
|
21
21
|
import { decodeParentFilter, encodeParentFilter } from "./hackedPackedPathParentFiltering";
|
|
@@ -28,6 +28,10 @@ setImmediate(() => import("../4-querysub/Querysub"));
|
|
|
28
28
|
|
|
29
29
|
const MAX_SEND_TRY_COUNT = 3;
|
|
30
30
|
|
|
31
|
+
// NOTE: This isn't very efficient, but it is safer. It's quadratic depending on the number of authorities for the server. Which shouldn't be so bad. I mean, why would we have four times redundancy? That's a lot. And even then, we're sending sixteen times the amount of traffic. That's fine, I guess. I think it's more reasonable for us to have two times redundancy, maybe even three times redundancy. With two times redundancy, it's only sending the value four times, which is reasonable. And the trade-off is there's basically no way that we can lose the data.
|
|
32
|
+
// - If we only send to one authority and then they rebroadcast it, it's possible that they die after receiving the value. If we send it to both value servers but they don't re-broadcast, it's possible that we die while broadcasting the values after having only sent a few. However, by sending it to both servers and having both servers send it to the other servers, it means that if we die while broadcasting it, it's fine. And if the server dies that we want to send it to, it's fine because we send it to both servers.
|
|
33
|
+
const BROADCAST_TO_ALL_AUTHORITIES = true;
|
|
34
|
+
|
|
31
35
|
export type BatchValues = {
|
|
32
36
|
pathValues: PathValue[],
|
|
33
37
|
parentsSynced?: string[];
|
|
@@ -207,8 +211,9 @@ class PathValueCommitter {
|
|
|
207
211
|
source: pathValue.source,
|
|
208
212
|
otherAuthorities,
|
|
209
213
|
});
|
|
214
|
+
continue;
|
|
210
215
|
}
|
|
211
|
-
|
|
216
|
+
function sendToAuthority(otherAuthority: AuthorityEntry) {
|
|
212
217
|
let values = valuesPerOtherAuthority.get(otherAuthority.nodeId);
|
|
213
218
|
if (!values) {
|
|
214
219
|
values = [];
|
|
@@ -216,6 +221,15 @@ class PathValueCommitter {
|
|
|
216
221
|
}
|
|
217
222
|
values.push(pathValue);
|
|
218
223
|
}
|
|
224
|
+
if (BROADCAST_TO_ALL_AUTHORITIES) {
|
|
225
|
+
for (let otherAuthority of otherAuthorities) {
|
|
226
|
+
sendToAuthority(otherAuthority);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// NOTE: Path routing is pretty strict. Once we disconnect, it'll remove it from the list of authorities. And if we can't talk to it, the retry logic will call us again.
|
|
230
|
+
let otherAuthority = otherAuthorities[~~(Math.random() * otherAuthorities.length)];
|
|
231
|
+
sendToAuthority(otherAuthority);
|
|
232
|
+
}
|
|
219
233
|
}
|
|
220
234
|
|
|
221
235
|
// Don't send to bad nodes for 60 seconds
|
|
@@ -268,30 +282,41 @@ class PathValueCommitter {
|
|
|
268
282
|
});
|
|
269
283
|
}
|
|
270
284
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
let
|
|
275
|
-
let
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
// If we broadcast to all authorities, we can't also retry, as this means if one authority is down, we end up infinitely looping. And it breaks the servers and the logs.
|
|
286
|
+
if (!BROADCAST_TO_ALL_AUTHORITIES) {
|
|
287
|
+
void forwardPromise.catch(async error => {
|
|
288
|
+
let byTryCount = new Map<number, PathValue[]>();
|
|
289
|
+
for (let value of values) {
|
|
290
|
+
let tryCount = tryCountPerValue.get(value) ?? 0;
|
|
291
|
+
let arr = byTryCount.get(tryCount) ?? [];
|
|
292
|
+
arr.push(value);
|
|
293
|
+
byTryCount.set(tryCount, arr);
|
|
294
|
+
}
|
|
295
|
+
for (let [tryCount, values] of byTryCount.entries()) {
|
|
296
|
+
tryCount++;
|
|
297
|
+
if (tryCount > MAX_SEND_TRY_COUNT) {
|
|
298
|
+
console.error(`Failed to send values after ${MAX_SEND_TRY_COUNT} tries. Giving up.`, {
|
|
299
|
+
error: error.message,
|
|
300
|
+
otherAuthority,
|
|
301
|
+
count: values.length,
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
for (let value of values) {
|
|
306
|
+
console.error(`Retrying to send values after ${tryCount} tries.`, {
|
|
307
|
+
error: error.message,
|
|
308
|
+
otherAuthority,
|
|
309
|
+
path: value.path,
|
|
310
|
+
timeId: value.time.time,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
void pathValueCommitter.broadcastValues({
|
|
314
|
+
values: new Set(values),
|
|
315
|
+
tryCount: tryCount,
|
|
286
316
|
});
|
|
287
|
-
continue;
|
|
288
317
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
tryCount: tryCount,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
295
320
|
|
|
296
321
|
pathValueCommitter.addCommitPromise(forwardPromise);
|
|
297
322
|
});
|
|
@@ -352,9 +377,10 @@ class PathValueCommitter {
|
|
|
352
377
|
return true;
|
|
353
378
|
});
|
|
354
379
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
380
|
+
// NOTE: If we are the authority on it, it means we're not going to have a remote watch node ID. And if we did, it would be ourselves. So this also implicitly filters out any initial triggers for values that we are the authority on.
|
|
381
|
+
for (let path of Array.from(batch.initialTriggers.values)) {
|
|
382
|
+
if (isWrongAuthority(path, undefined, "initialTrigger")) {
|
|
383
|
+
batch.initialTriggers.values.delete(path);
|
|
358
384
|
}
|
|
359
385
|
}
|
|
360
386
|
for (let parentPath of Array.from(batch.initialTriggers.parentPaths)) {
|
|
@@ -362,11 +388,6 @@ class PathValueCommitter {
|
|
|
362
388
|
continue;
|
|
363
389
|
}
|
|
364
390
|
|
|
365
|
-
// TODO: Remove this breakpoint eventually. This can happen naturally when servers go down?
|
|
366
|
-
require("debugbreak")(2);
|
|
367
|
-
debugger;
|
|
368
|
-
remoteWatcher.isFinalRemoteWatchPath({ parentPath, nodeId: batch.sourceNodeId });
|
|
369
|
-
|
|
370
391
|
console.warn(`Ignoring parent path which we aren't watching. From ${debugNodeId(batch.sourceNodeId)}.`, {
|
|
371
392
|
parentPath,
|
|
372
393
|
sourceNodeId: debugNodeId(batch.sourceNodeId),
|
|
@@ -310,8 +310,9 @@ export class PathValueControllerBase {
|
|
|
310
310
|
public async getValuesByPathAndTime(entries: AuditSnapshotEntry[]): Promise<Buffer[]> {
|
|
311
311
|
let values: PathValue[] = [];
|
|
312
312
|
for (let entry of entries) {
|
|
313
|
-
let value = authorityStorage.
|
|
313
|
+
let value = authorityStorage.getValueExact(entry.path, entry.time);
|
|
314
314
|
if (!value) continue;
|
|
315
|
+
if (!value.valid) continue;
|
|
315
316
|
if (value.isTransparent) continue;
|
|
316
317
|
if (value.canGCValue) continue;
|
|
317
318
|
values.push(value);
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -2,7 +2,7 @@ import { keyByArray, binarySearchIndex } from "socket-function/src/misc";
|
|
|
2
2
|
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
3
3
|
import { isNode } from "typesafecss";
|
|
4
4
|
import { isDiskAudit } from "../config";
|
|
5
|
-
import { getPathFromStr } from "../path";
|
|
5
|
+
import { getParentPathStr, getPathFromStr } from "../path";
|
|
6
6
|
import { auditLog } from "./auditLogs";
|
|
7
7
|
import { PathRouter } from "./PathRouter";
|
|
8
8
|
import { PathValue, authorityStorage, compareTime, debugPathValuePath, ReadLock, byLockGroup, isCoreQuiet, debugRejections, debugTime, debugPathValue, MAX_CHANGE_AGE } from "./pathValueCore";
|
|
@@ -40,13 +40,36 @@ class ValidStateComputer {
|
|
|
40
40
|
|
|
41
41
|
let ourValues: PathValue[] = [];
|
|
42
42
|
let notOurValues: PathValue[] = [];
|
|
43
|
-
|
|
43
|
+
pathValues = pathValues.filter(value => {
|
|
44
|
+
// NOTE: I think if it's initial trigger we need to process it anyways?
|
|
45
|
+
if (!config.initialTriggers.values.has(value.path)) {
|
|
46
|
+
// Ignore values we already have. We receive duplicates, often due to values being broadcast multiple times in order to prevent data loss.
|
|
47
|
+
let existingValue = authorityStorage.getValueExact(value.path, value.time);
|
|
48
|
+
// If we already have the value and the valid state is the exact same, then remove it from path values and don't even add it to our values. Pretend like we never received it.
|
|
49
|
+
if (existingValue && !!existingValue.valid === !!value.valid) {
|
|
50
|
+
let parentPath = getParentPathStr(value.path);
|
|
51
|
+
// We check for the parent trigger late because getting the parent path is a little bit expensive
|
|
52
|
+
if (!config.initialTriggers.parentPaths.has(parentPath)) {
|
|
53
|
+
console.info(`Ignoring duplicate value.`, {
|
|
54
|
+
path: value.path,
|
|
55
|
+
timeId: value.time.time,
|
|
56
|
+
timeIdVersion: value.time.version,
|
|
57
|
+
timeIdFull: value.time,
|
|
58
|
+
valid: value.valid,
|
|
59
|
+
});
|
|
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.
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
44
66
|
if (!PathRouter.isSelfAuthority(value.path)) {
|
|
45
67
|
notOurValues.push(value);
|
|
46
68
|
} else {
|
|
47
69
|
ourValues.push(value);
|
|
48
70
|
}
|
|
49
|
-
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
50
73
|
authorityStorage.ingestValues(notOurValues, { doNotArchive });
|
|
51
74
|
|
|
52
75
|
|
|
@@ -93,7 +116,6 @@ class ValidStateComputer {
|
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
|
|
96
|
-
|
|
97
119
|
let initialPrevValidStates: Map<PathValue, boolean | undefined> | undefined = this.capturePrevValidStates(ourValues);
|
|
98
120
|
authorityStorage.ingestValues(ourValues, { doNotArchive });
|
|
99
121
|
|
|
@@ -431,7 +431,7 @@ class AuthorityPathValueStorage {
|
|
|
431
431
|
public getValue(path: string): unknown {
|
|
432
432
|
return pathValueSerializer.getPathValue(this.getValueAtTime(path));
|
|
433
433
|
}
|
|
434
|
-
public
|
|
434
|
+
public getValueExact(path: string, time: Time): PathValue | undefined {
|
|
435
435
|
let value = this.getValueAtTime(path, time, true);
|
|
436
436
|
if (value && compareTime(value.time, time) === 0) {
|
|
437
437
|
return value;
|
package/src/2-proxy/schema2.ts
CHANGED
|
@@ -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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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(
|
|
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
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
|
|
@@ -1026,8 +1026,6 @@ export class Querysub {
|
|
|
1026
1026
|
RequireController.addMapGetModules(async (result, args) => {
|
|
1027
1027
|
let configObj = args[2] as { signedIdentity: SignedIdentity | undefined } | undefined;
|
|
1028
1028
|
if (!await isAllowedToSeeSource(configObj?.signedIdentity)) {
|
|
1029
|
-
require("debugbreak")(2);
|
|
1030
|
-
debugger;
|
|
1031
1029
|
await isAllowedToSeeSource(configObj?.signedIdentity);
|
|
1032
1030
|
//console.log(red(`Not allowed to see source`));
|
|
1033
1031
|
for (let [key, value] of Object.entries(result.modules)) {
|
|
@@ -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)
|
|
@@ -13,7 +13,8 @@ export const STREAMING_BLOCK_THRESHOLD = 1024 * 1024;
|
|
|
13
13
|
|
|
14
14
|
export const PUBLIC_MOVE_THRESHOLD = timeInHour * 3;
|
|
15
15
|
// Max uncompressed data size
|
|
16
|
-
|
|
16
|
+
// NOTE: This used to be 512MB, but searching was really slow. I think making this smaller will make searching faster?
|
|
17
|
+
export const MAX_SINGLE_FILE_DATA = 1024 * 1024 * 64;
|
|
17
18
|
|
|
18
19
|
export const MOVING_TIMEOUT = timeInMinute * 5;
|
|
19
20
|
|
|
@@ -479,8 +479,7 @@ export class IndexedLogs<T> {
|
|
|
479
479
|
if (onResultsCallback) {
|
|
480
480
|
interval = setInterval(async () => {
|
|
481
481
|
updateResultsStats();
|
|
482
|
-
let
|
|
483
|
-
let shouldContinue = await onResultsCallback(results);
|
|
482
|
+
let shouldContinue = await onResultsCallback(getFinalResults());
|
|
484
483
|
if (!shouldContinue) {
|
|
485
484
|
if (!results.cancel) {
|
|
486
485
|
console.log(blue(`Cancelled search on ${this.config.name}`));
|
|
@@ -6,7 +6,7 @@ import { BufferIndex } from "./BufferIndex";
|
|
|
6
6
|
import { LogStreamer } from "./LogStreamer";
|
|
7
7
|
import { TimeFileTree } from "./TimeFileTree";
|
|
8
8
|
import { blue, green, magenta } from "socket-function/src/formatting/logColors";
|
|
9
|
-
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
9
|
+
import { formatDateTime, formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
@@ -150,15 +150,15 @@ export async function moveLogsToPublic(config: {
|
|
|
150
150
|
let groups: typeof initialGroups = [];
|
|
151
151
|
for (let group of initialGroups) {
|
|
152
152
|
let currentSize = 0;
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
for (let path of group) {
|
|
155
155
|
let size = sizeMap.get(path) || 0;
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
if (groups.length === 0 || currentSize + size > sizeThreshold) {
|
|
158
158
|
groups.push([]);
|
|
159
159
|
currentSize = 0;
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
|
|
162
162
|
groups[groups.length - 1].push(path);
|
|
163
163
|
currentSize += size;
|
|
164
164
|
}
|
|
@@ -256,7 +256,7 @@ export async function moveLogsToPublic(config: {
|
|
|
256
256
|
await localLogs.del(path.fullPath + indexExtension);
|
|
257
257
|
}));
|
|
258
258
|
|
|
259
|
-
console.log(green(`(${i + 1}/${groups.length}) Wrote ${encoded.length} log files to public (${formatNumber(encoded.reduce((acc, x) => acc + x.uncompressedSize, 0))}B compressed to ${formatNumber(encoded.reduce((acc, x) => acc + x.compressedSize, 0))}B + ${formatNumber(encoded.reduce((acc, x) => acc + x.index.length, 0))}B index) in ${formatTime(Date.now() - time)}`) + ` (${blue(config.loggerName)})`);
|
|
259
|
+
console.log(green(`(${i + 1}/${groups.length}) Wrote ${encoded.length} log files to public from ${group.length} input files, ${formatDateTime(startTime)} to ${formatDateTime(endTime)} (${formatNumber(encoded.reduce((acc, x) => acc + x.uncompressedSize, 0))}B compressed to ${formatNumber(encoded.reduce((acc, x) => acc + x.compressedSize, 0))}B + ${formatNumber(encoded.reduce((acc, x) => acc + x.index.length, 0))}B index) in ${formatTime(Date.now() - time)}`) + ` (${blue(config.loggerName)})`);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
// Clean up orphaned index files (index files without corresponding data files)
|
|
@@ -99,7 +99,7 @@ export class ObjectDisplay extends qreact.Component<{
|
|
|
99
99
|
<span className={css.maxHeight("70vh").overflowAuto} onClick={log}>
|
|
100
100
|
<div class={css.relative}>
|
|
101
101
|
{renderValue(value, [])}
|
|
102
|
-
<MeasureHeightCSS />
|
|
102
|
+
{/* <MeasureHeightCSS /> */}
|
|
103
103
|
</div>
|
|
104
104
|
</span>
|
|
105
105
|
</ShowMore>;
|
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
|
-
|
|
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;
|