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.
Files changed (31) hide show
  1. package/bin/deploy-prefixes.js +7 -0
  2. package/package.json +2 -1
  3. package/src/-f-node-discovery/NodeDiscovery.ts +0 -2
  4. package/src/0-path-value-core/AuthorityLookup.ts +7 -2
  5. package/src/0-path-value-core/PathRouter.ts +170 -84
  6. package/src/0-path-value-core/PathRouterRouteOverride.ts +2 -2
  7. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +2 -8
  8. package/src/0-path-value-core/PathValueCommitter.ts +65 -30
  9. package/src/0-path-value-core/PathValueController.ts +2 -4
  10. package/src/0-path-value-core/PathWatcher.ts +7 -4
  11. package/src/0-path-value-core/ShardPrefixes.ts +4 -0
  12. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +12 -31
  13. package/src/0-path-value-core/pathValueArchives.ts +3 -3
  14. package/src/0-path-value-core/pathValueCore.ts +32 -24
  15. package/src/0-path-value-core/startupAuthority.ts +9 -9
  16. package/src/1-path-client/RemoteWatcher.ts +188 -170
  17. package/src/1-path-client/pathValueClientWatcher.ts +6 -11
  18. package/src/2-proxy/garbageCollection.ts +4 -3
  19. package/src/2-proxy/pathValueProxy.ts +2 -3
  20. package/src/3-path-functions/PathFunctionRunner.ts +3 -1
  21. package/src/3-path-functions/syncSchema.ts +6 -2
  22. package/src/4-deploy/deployGetFunctionsInner.ts +1 -1
  23. package/src/4-deploy/deployPrefixes.ts +14 -0
  24. package/src/4-deploy/edgeNodes.ts +1 -1
  25. package/src/diagnostics/SyncTestPage.tsx +19 -8
  26. package/src/functional/promiseCache.ts +5 -2
  27. package/src/path.ts +15 -2
  28. package/src/rangeMath.ts +41 -0
  29. package/tempnotes.txt +12 -26
  30. package/test.ts +8 -4
  31. package/src/diagnostics/logs/BrowserLargeFileCache.ts +0 -64
@@ -21,6 +21,7 @@ import { getSpecFromModule } from "./pathFunctionLoader";
21
21
  import { isNode } from "typesafecss";
22
22
  import { LOCAL_DOMAIN } from "../0-path-value-core/PathRouter";
23
23
  import { createRoutingOverrideKey } from "../0-path-value-core/PathRouterRouteOverride";
24
+ import { delay } from "socket-function/src/batching";
24
25
 
25
26
  // This is the the function id which should be used when creating the FunctionSpec (in order to load the module),
26
27
  // to access the permissions in the schema.
@@ -286,7 +287,9 @@ type SyncSchemaResult<Schema> = {
286
287
 
287
288
  let prefixes: string[] = [];
288
289
  // ONLY used by the deploy process. Path value server is pretty much the only one who needs this, and they aren't including client-side code, and they definitely aren't updating client-side code, so it doesn't make sense to use this directly.
289
- export function getPrefixesForDeploy(): string[] {
290
+ export async function getPrefixesForDeploy(): Promise<string[]> {
291
+ await new Promise(r => setImmediate(r));
292
+ await new Promise(r => setImmediate(r));
290
293
  return prefixes;
291
294
  }
292
295
  // Used for some old non syncSchema schema cases
@@ -302,7 +305,8 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
302
305
  const domainName = config.domainName ?? getDomain();
303
306
 
304
307
  const data = () => functionSchema()[domainName].PathFunctionRunner[moduleId].Data;
305
- let dataCached = lazy(() => data()) as () => Schema;
308
+ // NOTE: We used to cache this, but then they would cache the current database that's inside of the schema, which is dynamically set. So I don't think caching this really makes sense. We might be able to do some kind of intelligent cache where we check what the current database is and only reuse the cache if that's the same.
309
+ let dataCached = (() => data()) as () => Schema;
306
310
 
307
311
  let fncs = { ...functions };
308
312
 
@@ -94,7 +94,7 @@ async function main() {
94
94
  }
95
95
  }
96
96
 
97
- for (let prefix of getPrefixesForDeploy()) {
97
+ for (let prefix of await getPrefixesForDeploy()) {
98
98
  console.log(`PREFIX:${prefix}`);
99
99
  }
100
100
  }
@@ -0,0 +1,14 @@
1
+ import { setShardPrefixes } from "../0-path-value-core/ShardPrefixes";
2
+ import { getPrefixesForDeploy } from "../3-path-functions/syncSchema";
3
+ import path from "path";
4
+
5
+ async function main() {
6
+ let deployPath = path.resolve("./deploy.ts");
7
+ await import(deployPath);
8
+
9
+ let prefixes = await getPrefixesForDeploy();
10
+ await setShardPrefixes(prefixes);
11
+ }
12
+
13
+ main().catch(console.error).finally(() => process.exit());
14
+
@@ -273,7 +273,7 @@ async function updateEdgeNodesFile() {
273
273
  let liveHash = "";
274
274
  if (!noSyncing()) {
275
275
  liveHash = await Querysub.commitSynced(() => {
276
- return deploySchema()[getDomain()].deploy.live.hash;
276
+ return String(deploySchema()[getDomain()].deploy.live.hash);
277
277
  });
278
278
  }
279
279
 
@@ -17,11 +17,9 @@ import { PathAuditerController } from "./pathAuditer";
17
17
  import { t } from "../2-proxy/schema2";
18
18
 
19
19
 
20
- let { data, functions } = Querysub.createSchema<{
21
- values: {
22
- [key: string]: number;
23
- };
24
- }>()({
20
+ let { data, functions } = Querysub.createSchema({
21
+ values: t.lookup(t.number)
22
+ })({
25
23
  functions: {
26
24
  setValue(key: string, value: number) {
27
25
  data().values[key] = value;
@@ -90,6 +88,14 @@ export class SyncTestPage extends qreact.Component {
90
88
  allThreads = allThreads.slice();
91
89
  sort(allThreads, x => x.entrypoint);
92
90
 
91
+ function refreshDelayed() {
92
+ Querysub.onCommitFinished(() => {
93
+ setTimeout(() => {
94
+ syncTestController.refreshAll();
95
+ }, 500);
96
+ });
97
+ }
98
+
93
99
  return (
94
100
  <div className={css.pad2(20).vbox(40)}>
95
101
  <h1>Sync Test Page</h1>
@@ -125,8 +131,11 @@ export class SyncTestPage extends qreact.Component {
125
131
  <div className={css.hbox(10)}>
126
132
  <span class={css.boldStyle}>{key}</span>
127
133
  <span>{value}</span>
128
- <Button onClick={() => functions.incrementValue(key)}>Increment</Button>
129
- <InputLabel value={value} number onChangeValue={value => functions.setValue(key, +value)} />
134
+ <Button onClick={() => { functions.incrementValue(key); refreshDelayed(); }}>Increment</Button>
135
+ <InputLabel value={value} number onChangeValue={value => {
136
+ functions.setValue(key, +value);
137
+ refreshDelayed();
138
+ }} />
130
139
  </div>
131
140
  <div className={css.hbox(10, 2).wrap.marginLeft(20)}>
132
141
  {allThreads.map(thread => {
@@ -149,6 +158,7 @@ export class SyncTestPage extends qreact.Component {
149
158
  path: path,
150
159
  nodeId: thread.nodeId
151
160
  });
161
+ refreshDelayed();
152
162
  }}>
153
163
  Delete Value
154
164
  </Button>
@@ -168,6 +178,7 @@ export class SyncTestPage extends qreact.Component {
168
178
  label="Insert new key"
169
179
  onChangeValue={value => {
170
180
  functions.setValue(value, 0);
181
+ refreshDelayed();
171
182
  }}
172
183
  />
173
184
  <Button onClick={() => {
@@ -205,7 +216,7 @@ class SyncTestControllerBase {
205
216
  }
206
217
 
207
218
  public async getValueAndTime(path: string) {
208
- let pathValue = authorityStorage.getValueAtTime(path);
219
+ let pathValue = authorityStorage.getValueAtTime(path, undefined, false, "noAudit");
209
220
  let value = pathValueSerializer.getPathValue(pathValue);
210
221
  let time = pathValue?.time.time;
211
222
  return { value, time };
@@ -34,7 +34,10 @@ export function cacheAsyncLimited<Arg, Return>(limit: number, fnc: (arg: Arg) =>
34
34
  export const cacheAsyncSynced = cacheAsyncLimitedJSON;
35
35
  export function cacheAsyncLimitedJSON<Arg, Return>(
36
36
  limit: number,
37
- fnc: (arg: Arg) => Promise<Return>
37
+ fnc: (arg: Arg) => Promise<Return>,
38
+ config?: {
39
+ notReadyValue?: Return;
40
+ }
38
41
  ): ((arg: Arg) => Return | undefined) & {
39
42
  clear(): void;
40
43
  promise: (arg: Arg) => Promise<Return>;
@@ -71,6 +74,6 @@ export function cacheAsyncLimitedJSON<Arg, Return>(
71
74
  }
72
75
  );
73
76
  proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: fnc.toString() });
74
- return undefined;
77
+ return config?.notReadyValue;
75
78
  };
76
79
  }
package/src/path.ts CHANGED
@@ -171,9 +171,16 @@ export function getParentPathStr(pathStr: string) {
171
171
  return pathStr.slice(0, getStartOfLastPart(pathStr));
172
172
  }
173
173
 
174
- /** === getPathFromStr(pathStr).slice(-1)[0] */
174
+ /** === getPathFromStr(pathStr).slice(-1)[0] || "" */
175
175
  export function getLastPathPart(pathStr: string) {
176
- return unescapePathPart(pathStr.slice(getStartOfLastPart(pathStr) - pathDelimitEscaped.length, -pathDelimitEscaped.length));
176
+ let lastPartIndex = pathStr.lastIndexOf(pathDelimitEscaped);
177
+ if (lastPartIndex < 0) return "";
178
+ return unescapePathPart(
179
+ pathStr.slice(
180
+ getStartOfLastPart(pathStr),
181
+ lastPartIndex,
182
+ )
183
+ );
177
184
  }
178
185
 
179
186
  /** === getPathStr(getPathFromStr(pathStr).slice(0, -1)) */
@@ -181,8 +188,12 @@ export function removePathLastPart(pathStr: string) {
181
188
  return pathStr.slice(0, getStartOfLastPart(pathStr));
182
189
  }
183
190
 
191
+ let lastPastPath = { path: "", depth: 0 };
184
192
  /** getPathDepth(pathStr) === getPathFromStr(pathStr).length */
185
193
  export function getPathDepth(pathStr: string): number {
194
+ if (lastPastPath.path === pathStr) {
195
+ return lastPastPath.depth;
196
+ }
186
197
  // Start at 1, to skip the first pathDelimit
187
198
  let index = 1;
188
199
  // Start at -1, as the last loop doesn't have pathDelimitEscaped, but is still incremented
@@ -191,6 +202,8 @@ export function getPathDepth(pathStr: string): number {
191
202
  count++;
192
203
  index = pathStr.indexOf(pathDelimitEscaped, index) + 1;
193
204
  }
205
+ lastPastPath.path = pathStr;
206
+ lastPastPath.depth = count;
194
207
  return count;
195
208
  }
196
209
 
@@ -0,0 +1,41 @@
1
+ export type Range = {
2
+ start: number;
3
+ end: number;
4
+ };
5
+
6
+ export type Ranges = Range[];
7
+
8
+ export function rangesOverlap(a: Range, b: Range): boolean {
9
+ return a.start < b.end && a.end > b.start;
10
+ }
11
+
12
+ export function removeRange(ranges: Ranges, range: Range): {
13
+ removedRanges: Range[];
14
+ } {
15
+ let s = range.start;
16
+ let e = range.end;
17
+ let removedRanges: Range[] = [];
18
+ for (let i = ranges.length - 1; i >= 0; i--) {
19
+ let rangeInRanges = ranges[i];
20
+ if (s >= rangeInRanges.end || e <= rangeInRanges.start) continue;
21
+ let startTaken = Math.max(rangeInRanges.start, s);
22
+ let endTaken = Math.min(rangeInRanges.end, e);
23
+ removedRanges.push({ start: startTaken, end: endTaken });
24
+ ranges.splice(i, 1);
25
+ // Add back the parts we didn't overlap
26
+ if (rangeInRanges.start < s) {
27
+ ranges.push({
28
+ start: rangeInRanges.start,
29
+ end: s,
30
+ });
31
+ }
32
+ if (rangeInRanges.end > e) {
33
+ ranges.push({
34
+ start: e,
35
+ end: rangeInRanges.end,
36
+ });
37
+ }
38
+
39
+ }
40
+ return { removedRanges };
41
+ }
package/tempnotes.txt CHANGED
@@ -1,42 +1,24 @@
1
1
 
2
- BUGS:
3
- FIXED? The value audit code says that the servers are missing values, And it's trying to send the test values between the different path value servers, even though they should be sharded to never share values.
4
- FIXED? The CYOA server keeps locking up
5
- █ PathValueSerializer() 46.34% ( 66.6s = 75.3K * 765us + 550K * 16.3us )
6
- - The values keep syncing to both servers.
7
- FIXED? The CYOA and the function server keep failing the audits as well. We might need to look at the logs to see how this could happen. It'll probably be obvious once we look at the logs for those paths.
8
- - Is it because we're unwatching it? That would make a lot of sense.
9
- - I guess we can just step in and see if its watched
10
- pathWatcher.watchers.get(path)
11
- remoteWatcher.remoteWatchPaths.get(path)
2
+ 2) Change the test page lookup to be an actual lookup (and make it actually use the type safe schema)
3
+ - And... deploy, we need to deploy the function too...
12
4
 
5
+ 3) After making it an actual lookup, it should match with the function sharding. And so this should get rid of the cases when one update goes to both servers (Which happens because without the lookup both the data values and the function values could resolve to be on different servers, meaning both servers will know about the write).
13
6
 
14
-
15
- 3) gc, to split it into multiple directories
16
- 4) Verify the site still works
17
-
18
- 5) Verify that the sharded startup only loads in the paths it needs and isn't wasteful
19
- - With 100 granularity the waste should be quite low. Unless we get REALLY unlucky? Hmm...
20
-
21
- 6) Record path waste per PathValueServer, and add an admin warn component that requests this, then shows a warning for PathValueServers that have a high waste load %
22
-
23
-
24
- 3) Test sharding again
25
7
  4) Verify the test page works well, and shards
26
8
  - We have the function sharding, so the values shouldn't be sent to both servers because the function calls themselves should be sharded.
27
9
 
28
10
 
29
- 5) Test full path hash syncing, using data that's NOT in a prefix, and making sure we can sync it with keys to get some of it (and all of it)
30
- a) When connecting from a trusted node, everything should work fine, the flag should pass through correctly, as remote watchers should know what flag to pass.
31
- b) When syncing from the client side, only synchronizing all the keys will work. But that does still need to work, and that still requires talking to the correct nodes and passing them the correct flags.
32
-
11
+ 3) add code so that client-side we can get a breakdown of How many paths we have per authority. The goal is to have it heavily biased, not balanced. We want to only use a single server. (But then, of course, for different users, we should be using a different server)
12
+ 3.1) AND Give a count of how many writes per authority and how many reads, as in how many changes per authority.
13
+ 3.2) Let it popup and show which paths are on each server
33
14
 
34
- 3) add code so that client-side we can get a breakdown of How many paths we have per authority. The goal is to just have most the paths come from a single authority. For reading, it's actually more efficient to talk to a single server rather than many servers, as it's not the amount of data, it's really just the latency overhead of talking to the server. And for writing, same thing, it's a lot easier to just write to one server. And if every single user is reading from every single server, it's just not feasible. It no longer scales well.
35
15
 
36
16
  4) Get the AI to go through and add function remapping code. Basically, the idea is that we take the most accessed lookup that we're accessing and we use the key for that as our hash. For simple functions, we could actually automate this, but I'll see how the AI can do.
37
17
 
38
18
  5) Verify we are mostly accessing paths on one authority
39
19
 
20
+ 3.3) Do more testing with having a partial shard range being down. Realistically, we should be able to set some kind of range, maybe just a very small range in the middle of the two main servers that isn't covered, and still be able to use the site. Previously, we weren't able to. Maybe this was because the transactions just never resolved because we couldn't synchronize some of the values?
21
+
40
22
 
41
23
  7) Test 4 servers, sharded and redundant as the same time
42
24
  8) Verify on the test page
@@ -45,6 +27,10 @@ BUGS:
45
27
  - Now is as good a time as any to deal with any of the sharding issues. And it should work easily enough...
46
28
  - Verify the secondary run backup code works too.
47
29
 
30
+ 9) Do some tests with adding new modules and new functions in development
31
+ - I think we need to have the git prefix code. Get some extra prefixes in development.
32
+ - And likely import the deploy.ts code...
33
+ - Some kind of "getDevelopmentPrefixes" should be fine, which we dynamically call when is not public in getShardPrefixes
48
34
 
49
35
  2) Add disk verification service
50
36
  - pick a random authority => read the same we would as startup => do an extra backblaze getKeys and getInfo to make sure our cache is accurate => load in the data into memory => verify all old paths with that authority?
package/test.ts CHANGED
@@ -21,12 +21,16 @@ import { getAllAuthoritySpec, getOurAuthoritySpec } from "./src/0-path-value-cor
21
21
  import { PathRouter } from "./src/0-path-value-core/PathRouter";
22
22
  import { pathValueSerializer } from "./src/-h-path-value-serialize/PathValueSerializer";
23
23
  import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
24
+ import { getPrefixesForDeploy } from "./src/3-path-functions/syncSchema";
24
25
 
25
26
  async function main() {
26
- let prefixes = await getShardPrefixes();
27
- console.log(prefixes);
28
27
  let deployPath = path.resolve("./deploy.ts");
29
28
  await import(deployPath);
29
+ let prefixes = await getPrefixesForDeploy();
30
+ console.log(prefixes);
31
+ // let prefixes = await getShardPrefixes();
32
+ // console.log(prefixes);
33
+ return;
30
34
  // let paths = await pathValueArchives.getValuePaths(getAllAuthoritySpec());
31
35
  // paths = paths.filter(x => x.startsWith("P!REMAINING/"));
32
36
  // for (let path of paths) {
@@ -60,7 +64,7 @@ async function main() {
60
64
  // return path.split("/").at(-1)!;
61
65
  // }
62
66
 
63
- /*
67
+ //*
64
68
  let allValuesObj = await pathValueArchives.loadValues({
65
69
  nodeId: "",
66
70
  prefixes: [],
@@ -69,7 +73,7 @@ async function main() {
69
73
  });
70
74
  let allValues = Object.values(allValuesObj.values).flat();
71
75
  console.log(`Total values loaded: ${formatNumber(allValues.length)}`);
72
- */
76
+ //*/
73
77
 
74
78
  // const badDeleteTransaction = "transaction_ tSeqNum=887 tWriteTime=1774715868374.9143 thread=9dc0 cCount=14 dCount=3482 create=X,X,X,X,X,X,X,X,X,X,X,X,X,X delete=2@0977,3@0977,4@0977,1@0a2a,2@0a2a,3@0a2a,4@0a2... .transaction";
75
79
 
@@ -1,64 +0,0 @@
1
- import { SocketFunctionClientHook } from "socket-function/SocketFunctionTypes";
2
- import { cache, lazy } from "socket-function/src/caching";
3
- import { decodeCborx, encodeCborx } from "../../misc/cloneHelpers";
4
- import { sha256 } from "js-sha256";
5
- import { errorToUndefined } from "../../errors";
6
- import { isNode } from "typesafecss";
7
-
8
- let getRootDirectory = lazy(async () => {
9
- await navigator.storage.persist();
10
- return navigator.storage.getDirectory();
11
- });
12
-
13
- export const getBrowserLargeFileCache = cache((name: string) => new BrowserLargeFileCache(name));
14
-
15
- export class BrowserLargeFileCache {
16
- constructor(private name: string) { }
17
-
18
- private getDir = lazy(async () => {
19
- let root = await getRootDirectory();
20
- return await root.getDirectoryHandle(this.name, { create: true });
21
- });
22
-
23
- public async set(key: string, value: Buffer) {
24
- let dir = await this.getDir();
25
- let file = await dir.getFileHandle(key, { create: true });
26
- let writable = await file.createWritable();
27
- await writable.write(value);
28
- await writable.close();
29
- }
30
- public async get(key: string): Promise<Buffer | undefined> {
31
- let dir = await this.getDir();
32
- try {
33
- let file = await dir.getFileHandle(key, { create: false });
34
- let readable = await file.getFile();
35
- return Buffer.from(await readable.arrayBuffer());
36
- } catch {
37
- return undefined;
38
- }
39
- }
40
- }
41
-
42
- /** Cache key = [args, functionName, classGuid]
43
- * - Not nodeId, as that change so frequently, so caching based on server is not usually useful.
44
- * - If you want to cache based on nodeId, pass it as an unused arg.
45
- *
46
- * Uses a file per key, and never cleans them up. So... basically, this sucks, but, it should
47
- * work for a few specific functions with large and consistent values.
48
- */
49
- export const cacheCalls: SocketFunctionClientHook = async config => {
50
- if (isNode()) return;
51
-
52
- let { args, functionName, classGuid } = config.call;
53
- let bucket = sha256(JSON.stringify({ functionName, classGuid }));
54
- let cache = getBrowserLargeFileCache(bucket);
55
- let key = sha256(encodeCborx(args));
56
- let cachedValue = await cache.get(key);
57
- if (cachedValue) {
58
- config.overrideResult = decodeCborx(cachedValue);
59
- return;
60
- }
61
- config.onResult.push(async (result) => {
62
- await errorToUndefined(cache.set(key, encodeCborx(result)));
63
- });
64
- };