querysub 0.405.0 → 0.407.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/deploy-prefixes.js +7 -0
- package/package.json +2 -1
- package/src/-f-node-discovery/NodeDiscovery.ts +0 -2
- package/src/0-path-value-core/AuthorityLookup.ts +7 -2
- package/src/0-path-value-core/PathRouter.ts +170 -84
- package/src/0-path-value-core/PathRouterRouteOverride.ts +2 -2
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +2 -8
- package/src/0-path-value-core/PathValueCommitter.ts +65 -30
- package/src/0-path-value-core/PathValueController.ts +2 -4
- package/src/0-path-value-core/PathWatcher.ts +7 -4
- package/src/0-path-value-core/ShardPrefixes.ts +4 -0
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +12 -31
- package/src/0-path-value-core/pathValueArchives.ts +3 -3
- package/src/0-path-value-core/pathValueCore.ts +32 -24
- package/src/0-path-value-core/startupAuthority.ts +9 -9
- package/src/1-path-client/RemoteWatcher.ts +188 -170
- package/src/1-path-client/pathValueClientWatcher.ts +6 -11
- package/src/2-proxy/garbageCollection.ts +4 -3
- package/src/2-proxy/pathValueProxy.ts +2 -3
- package/src/3-path-functions/PathFunctionRunner.ts +3 -1
- package/src/3-path-functions/syncSchema.ts +6 -2
- package/src/4-deploy/deployGetFunctionsInner.ts +1 -1
- package/src/4-deploy/deployPrefixes.ts +14 -0
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/diagnostics/SyncTestPage.tsx +19 -8
- package/src/functional/promiseCache.ts +5 -2
- package/src/path.ts +15 -2
- package/src/rangeMath.ts +41 -0
- package/tempnotes.txt +12 -26
- package/test.ts +8 -4
- package/src/diagnostics/logs/BrowserLargeFileCache.ts +0 -64
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.407.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",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"bin": {
|
|
25
25
|
"deploy": "./bin/deploy.js",
|
|
26
|
+
"deploy-prefixes": "./bin/deploy-prefixes.js",
|
|
26
27
|
"server": "./bin/server.js",
|
|
27
28
|
"server-public": "./bin/server-public.js",
|
|
28
29
|
"function": "./bin/function.js",
|
|
@@ -46,8 +46,6 @@ let DISK_AUDIT_RATE = timeInMinute * 15;
|
|
|
46
46
|
// probably is less than that). Which is around 2.5 cents on digital ocean IF we go over
|
|
47
47
|
// our 1TB/month allowance.
|
|
48
48
|
let API_AUDIT_RATE = timeInSecond * 30;
|
|
49
|
-
// BUT, for now, poll less often... because I think it is lagging our 2 core potato digital ocean server.
|
|
50
|
-
API_AUDIT_RATE = timeInMinute * 5;
|
|
51
49
|
let API_AUDIT_COUNT = 12;
|
|
52
50
|
|
|
53
51
|
|
|
@@ -5,7 +5,7 @@ import { archiveJSONT } from "../-a-archives/archivesJSONT";
|
|
|
5
5
|
import { getDomain, isPublic } from "../config";
|
|
6
6
|
import { cache, lazy } from "socket-function/src/caching";
|
|
7
7
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
8
|
-
import { runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
8
|
+
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
9
9
|
import { getAllNodeIds, getOwnNodeId, isOwnNodeId, onNodeBroadcasted, syncNodesNow, watchDeltaNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery";
|
|
10
10
|
import { IdentityController_getCurrentReconnectNodeIdAssert } from "../-c-identity/IdentityController";
|
|
11
11
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
@@ -15,6 +15,7 @@ import { timeoutToError } from "../errors";
|
|
|
15
15
|
import { AuthoritySpec } from "./PathRouter";
|
|
16
16
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
17
17
|
import { getAllAuthoritySpec, getEmptyAuthoritySpec } from "./PathRouterServerAuthoritySpec";
|
|
18
|
+
import { getPrefixesForDeploy } from "../3-path-functions/syncSchema";
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
let NETWORK_POLL_INTERVAL = timeInMinute * 5;
|
|
@@ -201,9 +202,13 @@ class AuthorityLookup {
|
|
|
201
202
|
if (isClient()) {
|
|
202
203
|
// Doesn't matter what the node ID is, we should really only be connecting to the browser node ID, Which will have all the data for the current domain.
|
|
203
204
|
// - 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.
|
|
205
|
+
await new Promise(r => setImmediate(r));
|
|
206
|
+
await delay(1);
|
|
204
207
|
this.updatePaths(nodeId, {
|
|
205
|
-
...getAllAuthoritySpec(),
|
|
206
208
|
nodeId: nodeId,
|
|
209
|
+
prefixes: await getPrefixesForDeploy(),
|
|
210
|
+
routeStart: 0,
|
|
211
|
+
routeEnd: 1,
|
|
207
212
|
}, true);
|
|
208
213
|
return;
|
|
209
214
|
}
|
|
@@ -9,6 +9,7 @@ import { unique } from "../misc";
|
|
|
9
9
|
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
10
10
|
import { getRoutingOverride, hasPrefixHash } from "./PathRouterRouteOverride";
|
|
11
11
|
import { sha256 } from "js-sha256";
|
|
12
|
+
import { removeRange } from "../rangeMath";
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
// Cases
|
|
@@ -32,13 +33,29 @@ export type AuthoritySpec = {
|
|
|
32
33
|
routeEnd: number;
|
|
33
34
|
// If the path.startsWith(prefix), but prefix !== path, then we hash getPathIndex(path, hashIndex)
|
|
34
35
|
// - For now, let's just never add overlapping prefixes.
|
|
35
|
-
prefixes:
|
|
36
|
-
prefix: string;
|
|
37
|
-
hashIndex: number;
|
|
38
|
-
}[];
|
|
36
|
+
prefixes: string[];
|
|
39
37
|
excludeDefault?: boolean;
|
|
40
38
|
};
|
|
41
39
|
|
|
40
|
+
export function debugSpec(spec: AuthoritySpec) {
|
|
41
|
+
return {
|
|
42
|
+
info: `${spec.routeStart}-${spec.routeEnd} (${spec.prefixes.length} prefixes${spec.excludeDefault ? " excluding default" : ""})`,
|
|
43
|
+
spec: { do: { not: { expand: { spec } } } },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
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;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return longestPrefix;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
|
|
43
60
|
export class PathRouter {
|
|
44
61
|
|
|
@@ -50,6 +67,10 @@ export class PathRouter {
|
|
|
50
67
|
*/
|
|
51
68
|
@measureFnc
|
|
52
69
|
public static getRouteChildKey(path: string): number {
|
|
70
|
+
let override = getRoutingOverride(path);
|
|
71
|
+
if (override) {
|
|
72
|
+
return override.route;
|
|
73
|
+
}
|
|
53
74
|
let key = getLastPathPart(path);
|
|
54
75
|
return this.getSingleKeyRoute(key);
|
|
55
76
|
}
|
|
@@ -64,17 +85,16 @@ export class PathRouter {
|
|
|
64
85
|
path = hack_stripPackedPath(path);
|
|
65
86
|
let override = getRoutingOverride(path);
|
|
66
87
|
if (override) {
|
|
67
|
-
if (!hasPrefixHash({ spec, prefixHash: override.prefixHash })) return -1;
|
|
88
|
+
if (spec.excludeDefault && !hasPrefixHash({ spec, prefixHash: override.prefixHash })) return -1;
|
|
89
|
+
if (override.route < spec.routeStart || override.route >= spec.routeEnd) return -1;
|
|
68
90
|
return override.route;
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
let prefix = spec
|
|
93
|
+
let prefix = getMatchingPrefix(spec, path);
|
|
72
94
|
if (prefix) {
|
|
73
|
-
let key = getPathIndex(path, prefix
|
|
95
|
+
let key = getPathIndex(path, getPathDepth(prefix));
|
|
74
96
|
if (key === undefined) {
|
|
75
|
-
|
|
76
|
-
debugger;
|
|
77
|
-
throw new Error(`Impossible, hash index ${prefix.hashIndex} is out of range for path ${path}, but it matched the prefix ${prefix.prefix}`);
|
|
97
|
+
throw new Error(`Impossible, hash index ${getPathDepth(prefix)} is out of range for path ${path}, but it matched the prefix ${prefix}`);
|
|
78
98
|
}
|
|
79
99
|
return this.getSingleKeyRoute(key);
|
|
80
100
|
}
|
|
@@ -84,6 +104,17 @@ export class PathRouter {
|
|
|
84
104
|
return hash;
|
|
85
105
|
}
|
|
86
106
|
|
|
107
|
+
// Mostly for debugging
|
|
108
|
+
@measureFnc
|
|
109
|
+
public static getAllRoutes(path: string): number[] {
|
|
110
|
+
let routes: number[] = [];
|
|
111
|
+
for (let authority of authorityLookup.getTopologySync()) {
|
|
112
|
+
let route = this.getRouteFull({ path, spec: authority.authoritySpec });
|
|
113
|
+
routes.push(route);
|
|
114
|
+
}
|
|
115
|
+
return routes;
|
|
116
|
+
}
|
|
117
|
+
|
|
87
118
|
private static lastKeyRoute = {
|
|
88
119
|
key: "",
|
|
89
120
|
route: -1,
|
|
@@ -91,6 +122,12 @@ export class PathRouter {
|
|
|
91
122
|
// Takes a key which is a part of a path. Mostly used PathRouterRouteOverride, or other PathRouter helpers.
|
|
92
123
|
public static getSingleKeyRoute(key: string): number {
|
|
93
124
|
if (key && this.lastKeyRoute.key === key) return this.lastKeyRoute.route;
|
|
125
|
+
let override = getRoutingOverride(key);
|
|
126
|
+
if (override) {
|
|
127
|
+
this.lastKeyRoute.key = key;
|
|
128
|
+
this.lastKeyRoute.route = override.route;
|
|
129
|
+
return override.route;
|
|
130
|
+
}
|
|
94
131
|
let hash = fastHash(key);
|
|
95
132
|
let route = hash % (1000 * 1000 * 1000) / (1000 * 1000 * 1000);
|
|
96
133
|
this.lastKeyRoute.key = key;
|
|
@@ -112,7 +149,15 @@ export class PathRouter {
|
|
|
112
149
|
private static encodeIdentifier(config: { prefixes: string[]; rangeStart: number; rangeEnd: number } | "remaining"): string {
|
|
113
150
|
if (config === "remaining") return "P!REMAINING";
|
|
114
151
|
let { prefixes, rangeStart, rangeEnd } = config;
|
|
115
|
-
return [
|
|
152
|
+
return [
|
|
153
|
+
"P",
|
|
154
|
+
rangeStart.toString(),
|
|
155
|
+
rangeEnd.toString(),
|
|
156
|
+
...prefixes.map(x => this.getPrefixHash(x)),
|
|
157
|
+
// 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.
|
|
158
|
+
...prefixes.slice(0, 5).map(x => getLastPathPart(x).replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, 20)),
|
|
159
|
+
""
|
|
160
|
+
].join("!");
|
|
116
161
|
|
|
117
162
|
}
|
|
118
163
|
private static decodeIdentifier(identifier: string): { prefixHashes: string[]; rangeStart: number; rangeEnd: number } | "remaining" {
|
|
@@ -141,19 +186,12 @@ export class PathRouter {
|
|
|
141
186
|
}
|
|
142
187
|
|
|
143
188
|
let prefixes = ourSpec.prefixes.slice();
|
|
144
|
-
sort(prefixes, x => x.
|
|
145
|
-
function getPrefix(path: string): string | undefined {
|
|
146
|
-
for (let prefix of prefixes) {
|
|
147
|
-
if (path.startsWith(prefix.prefix) && prefix.prefix !== path) return prefix.prefix;
|
|
148
|
-
}
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
|
|
189
|
+
sort(prefixes, x => x.length);
|
|
152
190
|
|
|
153
191
|
// NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
|
|
154
192
|
let byPrefix = new Map<string | undefined, PathValue[]>();
|
|
155
193
|
for (let value of values) {
|
|
156
|
-
let prefix =
|
|
194
|
+
let prefix = getMatchingPrefix(ourSpec, value.path);
|
|
157
195
|
let values = byPrefix.get(prefix);
|
|
158
196
|
if (!values) {
|
|
159
197
|
values = [];
|
|
@@ -168,9 +206,6 @@ export class PathRouter {
|
|
|
168
206
|
}));
|
|
169
207
|
sort(prefixGroups, x => -x.values.length);
|
|
170
208
|
|
|
171
|
-
require("debugbreak")(2);
|
|
172
|
-
debugger;
|
|
173
|
-
|
|
174
209
|
let groups: {
|
|
175
210
|
prefixes: string[];
|
|
176
211
|
values: PathValue[][];
|
|
@@ -261,7 +296,7 @@ export class PathRouter {
|
|
|
261
296
|
// Match all remaining, as we don't store enough information to know what prefixes they excluded
|
|
262
297
|
if (decodeObj === "remaining") return true;
|
|
263
298
|
|
|
264
|
-
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x
|
|
299
|
+
let ourHashes = authority.prefixes.map(x => this.getPrefixHash(x));
|
|
265
300
|
// 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
301
|
if (decodeObj.prefixHashes.some(x => !ourHashes.includes(x))) return true;
|
|
267
302
|
// However, if we hash everything the same, then the overlap is purely a case of if the route ranges overlap.
|
|
@@ -283,11 +318,12 @@ export class PathRouter {
|
|
|
283
318
|
|
|
284
319
|
|
|
285
320
|
// NOTE: This might return overlapping specs. Presently, this is only used when we're loading our initial data, so it's fine. However, if we use this for another purpose in the future, it might cause problems. So we might need to implement a different function for that theoretical future purpose.
|
|
321
|
+
// NOTE: Only takes remote nodes as presently this is just used during startup.
|
|
286
322
|
@measureFnc
|
|
287
323
|
public static getAuthoritySources(config: {
|
|
288
324
|
target: AuthoritySpec;
|
|
289
325
|
preferredNodeIds?: string[];
|
|
290
|
-
}):
|
|
326
|
+
}): AuthoritySpec[] {
|
|
291
327
|
let { target } = config;
|
|
292
328
|
let allSources = authorityLookup.getTopologySync();
|
|
293
329
|
allSources = allSources.filter(x => !isOwnNodeId(x.nodeId));
|
|
@@ -305,12 +341,12 @@ export class PathRouter {
|
|
|
305
341
|
|
|
306
342
|
let groupByPrefixHash = new Map<string, AuthoritySpec[]>();
|
|
307
343
|
for (let source of allSources) {
|
|
308
|
-
let usedPrefixes = source.authoritySpec.prefixes
|
|
344
|
+
let usedPrefixes = source.authoritySpec.prefixes;
|
|
309
345
|
|
|
310
346
|
if (target.excludeDefault) {
|
|
311
347
|
// 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
348
|
// (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
|
|
349
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
314
350
|
usedPrefixes = usedPrefixes.filter(x => targetUsedPrefixes.has(x));
|
|
315
351
|
}
|
|
316
352
|
let prefixHash = sha256(JSON.stringify(usedPrefixes.sort()));
|
|
@@ -329,17 +365,17 @@ export class PathRouter {
|
|
|
329
365
|
}
|
|
330
366
|
|
|
331
367
|
function hashesSameAsTarget(spec: AuthoritySpec): boolean {
|
|
332
|
-
let targetUsedPrefixes = new Set(target.prefixes
|
|
368
|
+
let targetUsedPrefixes = new Set(target.prefixes);
|
|
333
369
|
if (target.excludeDefault) {
|
|
334
370
|
// 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
|
|
371
|
+
return spec.prefixes.every(x => targetUsedPrefixes.has(x));
|
|
336
372
|
}
|
|
337
373
|
// if !targetExcludeDefault, then !spec.excludeDefault, because we filtered out other cases earlier (so we don't need to handle spec.
|
|
338
374
|
|
|
339
375
|
if (spec.prefixes.length !== target.prefixes.length) return false;
|
|
340
376
|
// Otherwise, All of the prefixes have to be identical
|
|
341
377
|
for (let prefix of spec.prefixes) {
|
|
342
|
-
if (!targetUsedPrefixes.has(prefix
|
|
378
|
+
if (!targetUsedPrefixes.has(prefix)) return false;
|
|
343
379
|
}
|
|
344
380
|
return true;
|
|
345
381
|
}
|
|
@@ -368,41 +404,21 @@ export class PathRouter {
|
|
|
368
404
|
for (let source of group) {
|
|
369
405
|
let s = source.routeStart;
|
|
370
406
|
let e = source.routeEnd;
|
|
371
|
-
|
|
372
|
-
for (let
|
|
373
|
-
let missingRange = missingRanges[i];
|
|
374
|
-
if (s >= missingRange.end || e <= missingRange.start) continue;
|
|
375
|
-
let startTaken = Math.max(missingRange.start, s);
|
|
376
|
-
let endTaken = Math.min(missingRange.end, e);
|
|
377
|
-
// NOTE: It's not ideal that we might have to fragment one node ID between multiple requests. However, in practice, there shouldn't be much fragmentation here. The ranges that our nodes are breaking down by should be consistent, so there's actually no overlap or subsets.
|
|
407
|
+
let { removedRanges } = removeRange(missingRanges, { start: s, end: e });
|
|
408
|
+
for (let removedRange of removedRanges) {
|
|
378
409
|
usedParts.push({
|
|
379
410
|
nodeId: source.nodeId,
|
|
380
|
-
routeStart:
|
|
381
|
-
routeEnd:
|
|
411
|
+
routeStart: removedRange.start,
|
|
412
|
+
routeEnd: removedRange.end,
|
|
382
413
|
prefixes: target.prefixes,
|
|
383
414
|
excludeDefault: target.excludeDefault,
|
|
384
|
-
useFullPathHash,
|
|
385
415
|
});
|
|
386
|
-
missingRanges.splice(i, 1);
|
|
387
|
-
// Add back the parts we didn't overlap
|
|
388
|
-
if (missingRange.start < s) {
|
|
389
|
-
missingRanges.push({
|
|
390
|
-
start: missingRange.start,
|
|
391
|
-
end: s,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
if (missingRange.end > e) {
|
|
395
|
-
missingRanges.push({
|
|
396
|
-
start: e,
|
|
397
|
-
end: missingRange.end,
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
416
|
}
|
|
401
417
|
if (missingRanges.length === 0) break;
|
|
402
418
|
}
|
|
403
419
|
if (missingRanges.length === 0) {
|
|
404
420
|
if (!hashesSameAsTarget(group[0])) {
|
|
405
|
-
console.warn(`
|
|
421
|
+
console.warn(`Could only match a cohesive group that doesn't hash the same way we do. Expanding our range to be the full range of the group. This will be slower than it needs to, and might cause issues that result in only partial synchronization. This should be fixable with a deploy.`, config.target);
|
|
406
422
|
}
|
|
407
423
|
return usedParts;
|
|
408
424
|
}
|
|
@@ -418,9 +434,6 @@ export class PathRouter {
|
|
|
418
434
|
public static getChildReadNodes(path: string, config?: {
|
|
419
435
|
preferredNodeIds?: string[];
|
|
420
436
|
}): {
|
|
421
|
-
// By default we hash the key directly under the path. However, If that is not how our nodes were sharded, as in they didn't use it as a prefix, then they will be sharded by hashing the full path, and so when we receive the data, we need to keep this in mind and filter the data based on the full path hash.
|
|
422
|
-
useFullPathHash?: boolean;
|
|
423
|
-
|
|
424
437
|
// NOTE: If at all possible, we will cover all ranges. Node of the returned nodes will be redundant.
|
|
425
438
|
// - Sorted by range.start
|
|
426
439
|
nodes: {
|
|
@@ -433,20 +446,66 @@ export class PathRouter {
|
|
|
433
446
|
if (this.isSelfAuthority(path)) {
|
|
434
447
|
return { nodes: [{ nodeId: getOwnNodeId(), range: { start: 0, end: 1 } }] };
|
|
435
448
|
}
|
|
449
|
+
let preferredNodeIds = new Set(config?.preferredNodeIds ?? []);
|
|
436
450
|
|
|
437
451
|
// If a prefix is a parent of path, then it is the same as matching just the path directly
|
|
438
452
|
// (If our prefix directly equals one of the other matches, then it's more complicated, As then, the child keys of path are what is hashed, and so all the children will have different routes, so we might match multiple nodes. The same thing if we're matching the remaining case, in which case it's a full path hash, so the child key matters, and again, different routes).
|
|
439
453
|
// - The different route case is how the FuntionRunner works, and without it large databases couldn't run functions. However, most applications won't directly use it.
|
|
440
454
|
let allSources = authorityLookup.getTopologySync();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
455
|
+
// Prefer our own node
|
|
456
|
+
sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
// Direct prefixes always take priority, as almost everything is under a prefix anyways...
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
// 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
|
|
463
|
+
let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => y === path)).map(x => x.authoritySpec);
|
|
464
|
+
if (hasPrefix.length > 0) {
|
|
465
|
+
shuffle(hasPrefix, Math.random());
|
|
466
|
+
sort(hasPrefix, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
|
|
467
|
+
|
|
468
|
+
let missingRanges: { start: number; end: number }[] = [{
|
|
469
|
+
start: 0,
|
|
470
|
+
end: 1,
|
|
471
|
+
}];
|
|
472
|
+
let usedParts: {
|
|
473
|
+
nodeId: string;
|
|
474
|
+
range: { start: number; end: number };
|
|
475
|
+
}[] = [];
|
|
476
|
+
for (let source of hasPrefix) {
|
|
477
|
+
let s = source.routeStart;
|
|
478
|
+
let e = source.routeEnd;
|
|
479
|
+
let { removedRanges } = removeRange(missingRanges, { start: s, end: e });
|
|
480
|
+
// NOTE: It's not ideal that we might have to fragment one node ID between multiple requests. However, in practice, there shouldn't be much fragmentation here. The ranges that our nodes are breaking down by should be consistent, so there's actually no overlap or subsets.
|
|
481
|
+
for (let removedRange of removedRanges) {
|
|
482
|
+
usedParts.push({
|
|
483
|
+
nodeId: source.nodeId,
|
|
484
|
+
range: removedRange,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (missingRanges.length === 0) break;
|
|
488
|
+
}
|
|
489
|
+
if (missingRanges.length === 0) {
|
|
490
|
+
return { nodes: usedParts };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let nestedMatches = allSources.filter(x => {
|
|
495
|
+
// 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.
|
|
496
|
+
// - 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.
|
|
497
|
+
if (x.authoritySpec.prefixes.some(y => y === path)) return false;
|
|
498
|
+
|
|
499
|
+
// 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.
|
|
500
|
+
return (
|
|
501
|
+
x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path)
|
|
502
|
+
&& this.matchesAuthoritySpec(x.authoritySpec, path)
|
|
503
|
+
);
|
|
504
|
+
});
|
|
446
505
|
if (nestedMatches.length > 0) {
|
|
447
506
|
shuffle(nestedMatches, Math.random());
|
|
448
|
-
let preferredNodeIds = new Set(config?.preferredNodeIds ?? []);
|
|
449
507
|
sort(nestedMatches, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
|
|
508
|
+
sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
|
|
450
509
|
return {
|
|
451
510
|
nodes: nestedMatches.map(x => ({
|
|
452
511
|
nodeId: x.nodeId,
|
|
@@ -456,28 +515,53 @@ export class PathRouter {
|
|
|
456
515
|
};
|
|
457
516
|
}
|
|
458
517
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
prefixes: [{
|
|
463
|
-
prefix: path,
|
|
464
|
-
hashIndex: getPathDepth(path),
|
|
465
|
-
}],
|
|
466
|
-
routeStart: 0,
|
|
467
|
-
routeEnd: 1,
|
|
468
|
-
excludeDefault: true,
|
|
469
|
-
},
|
|
470
|
-
preferredNodeIds: config?.preferredNodeIds,
|
|
518
|
+
// If we are not under any prefixes of it, then it will be a full path hash
|
|
519
|
+
let fullPathMatches = allSources.filter(x => {
|
|
520
|
+
return !x.authoritySpec.prefixes.some(y => path.startsWith(y) && y !== path);
|
|
471
521
|
});
|
|
472
|
-
|
|
522
|
+
// 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.
|
|
523
|
+
if (fullPathMatches.length > 0) {
|
|
524
|
+
shuffle(fullPathMatches, Math.random());
|
|
525
|
+
sort(fullPathMatches, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
|
|
526
|
+
sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
|
|
527
|
+
let missingRanges: { start: number; end: number }[] = [{
|
|
528
|
+
start: 0,
|
|
529
|
+
end: 1,
|
|
530
|
+
}];
|
|
531
|
+
let usedParts: {
|
|
532
|
+
nodeId: string;
|
|
533
|
+
range: { start: number; end: number };
|
|
534
|
+
}[] = [];
|
|
535
|
+
for (let source of fullPathMatches) {
|
|
536
|
+
let s = source.authoritySpec.routeStart;
|
|
537
|
+
let e = source.authoritySpec.routeEnd;
|
|
538
|
+
let { removedRanges } = removeRange(missingRanges, { start: s, end: e });
|
|
539
|
+
// NOTE: It's not ideal that we might have to fragment one node ID between multiple requests. However, in practice, there shouldn't be much fragmentation here. The ranges that our nodes are breaking down by should be consistent, so there's actually no overlap or subsets.
|
|
540
|
+
for (let removedRange of removedRanges) {
|
|
541
|
+
usedParts.push({
|
|
542
|
+
nodeId: source.nodeId,
|
|
543
|
+
range: removedRange,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
if (missingRanges.length === 0) break;
|
|
547
|
+
}
|
|
548
|
+
if (missingRanges.length === 0) {
|
|
549
|
+
return { nodes: usedParts };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
473
552
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
// 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...
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
require("debugbreak")(2);
|
|
559
|
+
debugger;
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
// 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.
|
|
563
|
+
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 });
|
|
564
|
+
return { nodes: [] };
|
|
481
565
|
}
|
|
482
566
|
|
|
483
567
|
|
|
@@ -541,3 +625,5 @@ export class PathRouter {
|
|
|
541
625
|
|
|
542
626
|
}
|
|
543
627
|
|
|
628
|
+
|
|
629
|
+
(globalThis as any).PathRouter = PathRouter;
|
|
@@ -22,7 +22,7 @@ let keySpecialIdentifier = (
|
|
|
22
22
|
export function createRoutingOverrideKey(config: {
|
|
23
23
|
originalKey: string;
|
|
24
24
|
routeKey: string;
|
|
25
|
-
// This is
|
|
25
|
+
// This is the prefix it has the equivalent of. We need this, so if something excludes default, it doesn't automatically get every routing overridden value.
|
|
26
26
|
remappedPrefix: string;
|
|
27
27
|
}) {
|
|
28
28
|
let { originalKey, routeKey, remappedPrefix } = config;
|
|
@@ -62,7 +62,7 @@ export const hasPrefixHash = cacheLimited(1000 * 10,
|
|
|
62
62
|
(config: { spec: AuthoritySpec, prefixHash: string }) => {
|
|
63
63
|
let { spec, prefixHash } = config;
|
|
64
64
|
for (let prefix of spec.prefixes) {
|
|
65
|
-
if (getPrefixHash(prefix
|
|
65
|
+
if (getPrefixHash(prefix) === prefixHash) {
|
|
66
66
|
return true;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -20,10 +20,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
20
20
|
nodeId: "",
|
|
21
21
|
routeStart: 0,
|
|
22
22
|
routeEnd: 1,
|
|
23
|
-
prefixes: prefixes
|
|
24
|
-
prefix,
|
|
25
|
-
hashIndex: getPathDepth(prefix),
|
|
26
|
-
})),
|
|
23
|
+
prefixes: prefixes,
|
|
27
24
|
};
|
|
28
25
|
}
|
|
29
26
|
return undefined;
|
|
@@ -46,10 +43,7 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
|
|
|
46
43
|
nodeId: "",
|
|
47
44
|
routeStart: rangeStart,
|
|
48
45
|
routeEnd: rangeEnd,
|
|
49
|
-
prefixes: usePrefixes
|
|
50
|
-
prefix,
|
|
51
|
-
hashIndex: getPathDepth(prefix),
|
|
52
|
-
})),
|
|
46
|
+
prefixes: usePrefixes,
|
|
53
47
|
excludeDefault: excludeDefault || undefined,
|
|
54
48
|
};
|
|
55
49
|
}
|