querysub 0.437.0 → 0.439.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/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +2 -2
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/PathValueController.ts +0 -2
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +37 -1
- package/src/0-path-value-core/pathValueCore.ts +12 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunner.ts +24 -13
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -516
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +400 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/PathDistributionInfo.tsx +9 -1
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/logs/IndexedLogs/BufferUnitIndex.ts +1 -1
- package/src/diagnostics/logs/diskLogger.ts +6 -0
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +78 -1
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/library-components/uncaughtToast.tsx +2 -0
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import { delay } from "socket-function/src/batching";
|
|
2
|
-
import { PathValue, hashPathForTransaction, compareTime, Time } from "../0-path-value-core/pathValueCore";
|
|
3
|
-
import { proxyWatcher } from "./PathValueProxyWatcher";
|
|
4
|
-
|
|
5
|
-
const MISSING_TRANSACTION_PART_TIMEOUT = 5000;
|
|
6
|
-
|
|
7
|
-
export function isMissingTransactionPart(pathValues: PathValue[]): { time: number; path: string; } | undefined {
|
|
8
|
-
// Create a map of path hash -> time for quick lookups
|
|
9
|
-
const pathHashToTime = new Map<string, {
|
|
10
|
-
time: Time;
|
|
11
|
-
path: string;
|
|
12
|
-
}>();
|
|
13
|
-
for (const pv of pathValues) {
|
|
14
|
-
const hash = hashPathForTransaction(pv.path);
|
|
15
|
-
const existing = pathHashToTime.get(hash);
|
|
16
|
-
// Keep the latest time for this path
|
|
17
|
-
if (!existing || compareTime(pv.time, existing.time) > 0) {
|
|
18
|
-
pathHashToTime.set(hash, {
|
|
19
|
-
time: pv.time,
|
|
20
|
-
path: pv.path,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Group PathValues by their transactionPaths array reference
|
|
26
|
-
const uniqueTransactions = new Map<string[], Time>();
|
|
27
|
-
for (const pathValue of pathValues) {
|
|
28
|
-
if (!pathValue.transactionPaths || pathValue.transactionPaths.length === 0) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const existing = uniqueTransactions.get(pathValue.transactionPaths);
|
|
33
|
-
if (!existing) {
|
|
34
|
-
uniqueTransactions.set(pathValue.transactionPaths, pathValue.time);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let newestWaitTime: {
|
|
39
|
-
time: number;
|
|
40
|
-
path: string;
|
|
41
|
-
} | undefined = undefined;
|
|
42
|
-
|
|
43
|
-
// Check each unique transaction only once
|
|
44
|
-
for (const [transactionPaths, transactionTime] of uniqueTransactions) {
|
|
45
|
-
for (const transactionPathHash of transactionPaths) {
|
|
46
|
-
const pathTime = pathHashToTime.get(transactionPathHash);
|
|
47
|
-
if (!pathTime) continue;
|
|
48
|
-
|
|
49
|
-
// We know that there's a value with this path hash at the transaction time. And so if the value that we've read is before that, then we haven't read the latest value. So we know that we're missing part of the transaction.
|
|
50
|
-
const timeComparison = compareTime(pathTime.time, transactionTime);
|
|
51
|
-
if (timeComparison < 0) {
|
|
52
|
-
if (!newestWaitTime || transactionTime.time > newestWaitTime.time) {
|
|
53
|
-
newestWaitTime = {
|
|
54
|
-
time: transactionTime.time,
|
|
55
|
-
path: pathTime.path,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (!newestWaitTime) return undefined;
|
|
62
|
-
return newestWaitTime;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getAllPendingPathValueReads() {
|
|
66
|
-
let values: PathValue[] = [];
|
|
67
|
-
let watcher = proxyWatcher.getTriggeredWatcher();
|
|
68
|
-
for (let map of watcher.pendingAccesses.values()) {
|
|
69
|
-
for (let pathValue of map.values()) {
|
|
70
|
-
values.push(pathValue.pathValue);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return values;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
export function waitIfReceivedIncompleteTransaction() {
|
|
78
|
-
// 1) Keep track of the last proxy watcher we just registered it for. And then if the proxy watcher is the same as that one and that proxy watcher is waiting on a promise, don't register it again.
|
|
79
|
-
let watcher = proxyWatcher.getTriggeredWatcher();
|
|
80
|
-
// We don't want to register a whole bunch of duplicate promises. So if somebody's waiting, there's no need to even do our check.
|
|
81
|
-
if (watcher.specialPromiseUnsynced) return;
|
|
82
|
-
|
|
83
|
-
let newestTime = isMissingTransactionPart(getAllPendingPathValueReads());
|
|
84
|
-
if (!newestTime) return;
|
|
85
|
-
|
|
86
|
-
let timeout = newestTime.time + MISSING_TRANSACTION_PART_TIMEOUT;
|
|
87
|
-
let now = Date.now();
|
|
88
|
-
if (timeout < now) return;
|
|
89
|
-
|
|
90
|
-
let promise = delay(timeout - now);
|
|
91
|
-
// This really nicely both blocks it from finishing because of the waiting for the promise and also triggers it automatically when the delay finishes. HOWEVER, In practice, the promise should never be required to trigger it. It should trigger when we receive the missing parts of the transaction, which we will be, of course, watching as they're in the path values that we're accessing. The timeout is just in case something goes wrong and we incorrectly think we should receive the values, but we won't. That way, eventually, we do commit the value.
|
|
92
|
-
// ALSO! Plus the hash I think only stores like 48 bits. So the chance of collision is somewhat high, especially if we're accessing thousands of paths. So sometimes this will trigger even though we're not missing any part just because we had a collision between the path hashes.
|
|
93
|
-
proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: "Missing transaction part" });
|
|
94
|
-
console.error(`(NOT an error, convert this to a warning after we finish testing). Waiting for missing transaction part ${newestTime.path} which was written at time ${newestTime.time} (now is ${now}). We have parts of this transaction, but we are missing this specific path.`);
|
|
1
|
+
import { delay } from "socket-function/src/batching";
|
|
2
|
+
import { PathValue, hashPathForTransaction, compareTime, Time } from "../0-path-value-core/pathValueCore";
|
|
3
|
+
import { proxyWatcher } from "./PathValueProxyWatcher";
|
|
4
|
+
|
|
5
|
+
const MISSING_TRANSACTION_PART_TIMEOUT = 5000;
|
|
6
|
+
|
|
7
|
+
export function isMissingTransactionPart(pathValues: PathValue[]): { time: number; path: string; } | undefined {
|
|
8
|
+
// Create a map of path hash -> time for quick lookups
|
|
9
|
+
const pathHashToTime = new Map<string, {
|
|
10
|
+
time: Time;
|
|
11
|
+
path: string;
|
|
12
|
+
}>();
|
|
13
|
+
for (const pv of pathValues) {
|
|
14
|
+
const hash = hashPathForTransaction(pv.path);
|
|
15
|
+
const existing = pathHashToTime.get(hash);
|
|
16
|
+
// Keep the latest time for this path
|
|
17
|
+
if (!existing || compareTime(pv.time, existing.time) > 0) {
|
|
18
|
+
pathHashToTime.set(hash, {
|
|
19
|
+
time: pv.time,
|
|
20
|
+
path: pv.path,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Group PathValues by their transactionPaths array reference
|
|
26
|
+
const uniqueTransactions = new Map<string[], Time>();
|
|
27
|
+
for (const pathValue of pathValues) {
|
|
28
|
+
if (!pathValue.transactionPaths || pathValue.transactionPaths.length === 0) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const existing = uniqueTransactions.get(pathValue.transactionPaths);
|
|
33
|
+
if (!existing) {
|
|
34
|
+
uniqueTransactions.set(pathValue.transactionPaths, pathValue.time);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let newestWaitTime: {
|
|
39
|
+
time: number;
|
|
40
|
+
path: string;
|
|
41
|
+
} | undefined = undefined;
|
|
42
|
+
|
|
43
|
+
// Check each unique transaction only once
|
|
44
|
+
for (const [transactionPaths, transactionTime] of uniqueTransactions) {
|
|
45
|
+
for (const transactionPathHash of transactionPaths) {
|
|
46
|
+
const pathTime = pathHashToTime.get(transactionPathHash);
|
|
47
|
+
if (!pathTime) continue;
|
|
48
|
+
|
|
49
|
+
// We know that there's a value with this path hash at the transaction time. And so if the value that we've read is before that, then we haven't read the latest value. So we know that we're missing part of the transaction.
|
|
50
|
+
const timeComparison = compareTime(pathTime.time, transactionTime);
|
|
51
|
+
if (timeComparison < 0) {
|
|
52
|
+
if (!newestWaitTime || transactionTime.time > newestWaitTime.time) {
|
|
53
|
+
newestWaitTime = {
|
|
54
|
+
time: transactionTime.time,
|
|
55
|
+
path: pathTime.path,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!newestWaitTime) return undefined;
|
|
62
|
+
return newestWaitTime;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getAllPendingPathValueReads() {
|
|
66
|
+
let values: PathValue[] = [];
|
|
67
|
+
let watcher = proxyWatcher.getTriggeredWatcher();
|
|
68
|
+
for (let map of watcher.pendingAccesses.values()) {
|
|
69
|
+
for (let pathValue of map.values()) {
|
|
70
|
+
values.push(pathValue.pathValue);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
export function waitIfReceivedIncompleteTransaction() {
|
|
78
|
+
// 1) Keep track of the last proxy watcher we just registered it for. And then if the proxy watcher is the same as that one and that proxy watcher is waiting on a promise, don't register it again.
|
|
79
|
+
let watcher = proxyWatcher.getTriggeredWatcher();
|
|
80
|
+
// We don't want to register a whole bunch of duplicate promises. So if somebody's waiting, there's no need to even do our check.
|
|
81
|
+
if (watcher.specialPromiseUnsynced) return;
|
|
82
|
+
|
|
83
|
+
let newestTime = isMissingTransactionPart(getAllPendingPathValueReads());
|
|
84
|
+
if (!newestTime) return;
|
|
85
|
+
|
|
86
|
+
let timeout = newestTime.time + MISSING_TRANSACTION_PART_TIMEOUT;
|
|
87
|
+
let now = Date.now();
|
|
88
|
+
if (timeout < now) return;
|
|
89
|
+
|
|
90
|
+
let promise = delay(timeout - now);
|
|
91
|
+
// This really nicely both blocks it from finishing because of the waiting for the promise and also triggers it automatically when the delay finishes. HOWEVER, In practice, the promise should never be required to trigger it. It should trigger when we receive the missing parts of the transaction, which we will be, of course, watching as they're in the path values that we're accessing. The timeout is just in case something goes wrong and we incorrectly think we should receive the values, but we won't. That way, eventually, we do commit the value.
|
|
92
|
+
// ALSO! Plus the hash I think only stores like 48 bits. So the chance of collision is somewhat high, especially if we're accessing thousands of paths. So sometimes this will trigger even though we're not missing any part just because we had a collision between the path hashes.
|
|
93
|
+
proxyWatcher.triggerOnPromiseFinish(promise, { waitReason: "Missing transaction part" });
|
|
94
|
+
console.error(`(NOT an error, convert this to a warning after we finish testing). Waiting for missing transaction part ${newestTime.path} which was written at time ${newestTime.time} (now is ${now}). We have parts of this transaction, but we are missing this specific path.`);
|
|
95
95
|
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import debugbreak from "debugbreak";
|
|
2
|
-
|
|
3
|
-
let currentDatabase!: { value: unknown };
|
|
4
|
-
const CURRENT_DATABASE_GLOBAL_KEY = "pathDatabaseProxy_currentDatabase-7010c456-d83a-40f4-a58a-0ff0251cef16";
|
|
5
|
-
// NOTE: We use the global state, so that required libraries can use a different version
|
|
6
|
-
// of our library, but still share the same database!
|
|
7
|
-
if ((global as any)[CURRENT_DATABASE_GLOBAL_KEY]) {
|
|
8
|
-
currentDatabase = (global as any)[CURRENT_DATABASE_GLOBAL_KEY];
|
|
9
|
-
} else {
|
|
10
|
-
currentDatabase = { value: undefined };
|
|
11
|
-
(global as any)[CURRENT_DATABASE_GLOBAL_KEY] = currentDatabase;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function rawSchema<Schema>(): () => Schema {
|
|
15
|
-
return () => {
|
|
16
|
-
if (!currentDatabase.value) {
|
|
17
|
-
debugbreak(2);
|
|
18
|
-
debugger;
|
|
19
|
-
throw new Error(`Cannot access synced data outside of tracked code (render functions, event callbacks, QuerySub.serviceWrite, QuerySub.createWatcher callback, etc)`);
|
|
20
|
-
}
|
|
21
|
-
return currentDatabase.value as unknown as Schema;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isInProxyDatabase() {
|
|
26
|
-
return !!currentDatabase.value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function runCodeWithDatabase<T>(database: unknown, code: () => T): T {
|
|
30
|
-
let prevDatabase = currentDatabase.value;
|
|
31
|
-
currentDatabase.value = database;
|
|
32
|
-
try {
|
|
33
|
-
return code();
|
|
34
|
-
} finally {
|
|
35
|
-
currentDatabase.value = prevDatabase;
|
|
36
|
-
}
|
|
1
|
+
import debugbreak from "debugbreak";
|
|
2
|
+
|
|
3
|
+
let currentDatabase!: { value: unknown };
|
|
4
|
+
const CURRENT_DATABASE_GLOBAL_KEY = "pathDatabaseProxy_currentDatabase-7010c456-d83a-40f4-a58a-0ff0251cef16";
|
|
5
|
+
// NOTE: We use the global state, so that required libraries can use a different version
|
|
6
|
+
// of our library, but still share the same database!
|
|
7
|
+
if ((global as any)[CURRENT_DATABASE_GLOBAL_KEY]) {
|
|
8
|
+
currentDatabase = (global as any)[CURRENT_DATABASE_GLOBAL_KEY];
|
|
9
|
+
} else {
|
|
10
|
+
currentDatabase = { value: undefined };
|
|
11
|
+
(global as any)[CURRENT_DATABASE_GLOBAL_KEY] = currentDatabase;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function rawSchema<Schema>(): () => Schema {
|
|
15
|
+
return () => {
|
|
16
|
+
if (!currentDatabase.value) {
|
|
17
|
+
debugbreak(2);
|
|
18
|
+
debugger;
|
|
19
|
+
throw new Error(`Cannot access synced data outside of tracked code (render functions, event callbacks, QuerySub.serviceWrite, QuerySub.createWatcher callback, etc)`);
|
|
20
|
+
}
|
|
21
|
+
return currentDatabase.value as unknown as Schema;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isInProxyDatabase() {
|
|
26
|
+
return !!currentDatabase.value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function runCodeWithDatabase<T>(database: unknown, code: () => T): T {
|
|
30
|
+
let prevDatabase = currentDatabase.value;
|
|
31
|
+
currentDatabase.value = database;
|
|
32
|
+
try {
|
|
33
|
+
return code();
|
|
34
|
+
} finally {
|
|
35
|
+
currentDatabase.value = prevDatabase;
|
|
36
|
+
}
|
|
37
37
|
}
|
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
import { runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
-
import { cache, lazy } from "socket-function/src/caching";
|
|
3
|
-
import { appendToPathStr, rootPathStr } from "../path";
|
|
4
|
-
import { runCodeWithDatabase } from "./pathDatabaseProxyBase";
|
|
5
|
-
import { canHaveChildren } from "socket-function/src/types";
|
|
6
|
-
import { isNode, timeInMinute } from "socket-function/src/misc";
|
|
7
|
-
|
|
8
|
-
let noopProxy = createPathValueProxy({ getCallback: () => undefined, setCallback: () => { }, getSymbol: () => undefined, getKeys: () => [] });
|
|
9
|
-
let gettingPath: { value: string } | undefined;
|
|
10
|
-
let gettingPathAndWatch: { value: string } | undefined;
|
|
11
|
-
// IMPORTANT! Does NOT add a watch to the proxy path! This means it can be used to check
|
|
12
|
-
// if a path is being watched.
|
|
13
|
-
// - HOWEVER, it also runs the get() WITHOUT synced state, so if you want to access any nested synced
|
|
14
|
-
// objects, you need to get their values outside of the get (which can incur a watch).
|
|
15
|
-
export function getProxyPath(get: () => unknown): string {
|
|
16
|
-
// We need to set the database incase it isn't set.
|
|
17
|
-
return runCodeWithDatabase(noopProxy, () => {
|
|
18
|
-
if (gettingPath) throw new Error(`Can't call getProxyPath while already getting a path.`);
|
|
19
|
-
gettingPath = { value: "" };
|
|
20
|
-
try {
|
|
21
|
-
get();
|
|
22
|
-
return gettingPath.value;
|
|
23
|
-
} finally {
|
|
24
|
-
gettingPath = undefined;
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getProxyPathAndWatch(get: () => unknown): string {
|
|
30
|
-
// We need to set the database incase it isn't set.
|
|
31
|
-
return runCodeWithDatabase(noopProxy, () => {
|
|
32
|
-
if (gettingPathAndWatch) throw new Error(`Can't call getProxyPathAndWatch while already getting a path.`);
|
|
33
|
-
gettingPathAndWatch = { value: "" };
|
|
34
|
-
try {
|
|
35
|
-
get();
|
|
36
|
-
return gettingPathAndWatch.value;
|
|
37
|
-
} finally {
|
|
38
|
-
gettingPathAndWatch = undefined;
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const isProxySymbol = Symbol.for("isProxySymbol");
|
|
44
|
-
export function isValueProxy(value: unknown) {
|
|
45
|
-
return canHaveChildren(value) && value[isProxySymbol];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const isProxySymbol2 = Symbol.for("isProxySymbol2");
|
|
49
|
-
/** If it is a proxy, returns the proxy path */
|
|
50
|
-
export function isValueProxy2(value: unknown): string | undefined {
|
|
51
|
-
return canHaveChildren(value) && value[isProxySymbol2] as any || undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const getPathFromProxy = isValueProxy2;
|
|
55
|
-
(globalThis as any).getPathFromProxy = getPathFromProxy;
|
|
56
|
-
|
|
57
|
-
export function createPathValueProxy(
|
|
58
|
-
config: {
|
|
59
|
-
getCallback: (pathStr: string) => { value: unknown } | undefined;
|
|
60
|
-
setCallback: (pathStr: string, value: unknown) => void;
|
|
61
|
-
hasCallback?: (pathStr: string) => boolean;
|
|
62
|
-
getSymbol: (pathStr: string, symbol: symbol) => { value: unknown } | undefined;
|
|
63
|
-
getKeys: (pathStr: string) => string[];
|
|
64
|
-
}
|
|
65
|
-
): unknown {
|
|
66
|
-
let createdCount = 0;
|
|
67
|
-
let createProxyCached = cache(createProxy);
|
|
68
|
-
let proxyClearLoop = lazy(() => {
|
|
69
|
-
runInfinitePoll(timeInMinute * 5, function clearProxyCache() {
|
|
70
|
-
// TODO: Maybe allow proxies to stick around?
|
|
71
|
-
//if (createdCount > 1000 * 100)
|
|
72
|
-
{
|
|
73
|
-
createdCount = 0;
|
|
74
|
-
createProxyCached = cache(createProxy);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
let ensureProxyClearLoop = () => {
|
|
79
|
-
if (createdCount < 1000 * 10) return;
|
|
80
|
-
proxyClearLoop();
|
|
81
|
-
};
|
|
82
|
-
return createProxyCached(rootPathStr);
|
|
83
|
-
function createProxy(parentPathStr: string) {
|
|
84
|
-
createdCount++;
|
|
85
|
-
let proxy: object = new Proxy(
|
|
86
|
-
// NOTE: This is an object now, because the data won't contain any functions, as that makes things way too
|
|
87
|
-
// complicated, and dangerous.
|
|
88
|
-
Object.create(null),
|
|
89
|
-
{
|
|
90
|
-
get(target, property) {
|
|
91
|
-
if (property === isProxySymbol) return true;
|
|
92
|
-
if (property === isProxySymbol2) return parentPathStr;
|
|
93
|
-
ensureProxyClearLoop();
|
|
94
|
-
if (typeof property === "symbol") {
|
|
95
|
-
let value = config.getSymbol(parentPathStr, property);
|
|
96
|
-
if (value) {
|
|
97
|
-
return value.value;
|
|
98
|
-
}
|
|
99
|
-
return createProxyCached(parentPathStr);
|
|
100
|
-
}
|
|
101
|
-
// if (property === "[object Object]") {
|
|
102
|
-
// console.warn(`Reading from key that looks like an object. Is it possible that you passed a proxy as a parameter which had no value? You might want to check if the key has a value first with if(atomic(value)). In ${parentPathStr}`);
|
|
103
|
-
// }
|
|
104
|
-
|
|
105
|
-
let pathStr = appendToPathStr(parentPathStr, property);
|
|
106
|
-
if (gettingPath) {
|
|
107
|
-
gettingPath.value = pathStr;
|
|
108
|
-
} else {
|
|
109
|
-
if (gettingPathAndWatch) {
|
|
110
|
-
gettingPathAndWatch.value = pathStr;
|
|
111
|
-
}
|
|
112
|
-
let value = config.getCallback(pathStr);
|
|
113
|
-
if (value) {
|
|
114
|
-
return value.value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return createProxyCached(pathStr);
|
|
118
|
-
},
|
|
119
|
-
set(obj, prop, value) {
|
|
120
|
-
if (typeof prop === "symbol") {
|
|
121
|
-
throw new Error("Can't set symbol properties in the database");
|
|
122
|
-
}
|
|
123
|
-
let pathStr = appendToPathStr(parentPathStr, prop);
|
|
124
|
-
config.setCallback(pathStr, value);
|
|
125
|
-
return true;
|
|
126
|
-
},
|
|
127
|
-
// Just convert delete to be a set to undefined, so everything works
|
|
128
|
-
deleteProperty(target, prop) {
|
|
129
|
-
if (typeof prop === "symbol") {
|
|
130
|
-
throw new Error("Can't set symbol properties in the database");
|
|
131
|
-
}
|
|
132
|
-
let pathStr = appendToPathStr(parentPathStr, prop);
|
|
133
|
-
config.setCallback(pathStr, undefined);
|
|
134
|
-
return true;
|
|
135
|
-
},
|
|
136
|
-
getOwnPropertyDescriptor(target, property) {
|
|
137
|
-
// NOTE: The "value" field shouldn't even be part of the property descriptor, and usually isn't
|
|
138
|
-
// used. Returning it would just be inefficient, So while we should have
|
|
139
|
-
// `value: this[prop]` here, I'm not adding it.
|
|
140
|
-
return { configurable: true, enumerable: true, writable: true };
|
|
141
|
-
},
|
|
142
|
-
ownKeys(target) {
|
|
143
|
-
return config.getKeys(parentPathStr);
|
|
144
|
-
},
|
|
145
|
-
has(target, property) {
|
|
146
|
-
if (typeof property === "symbol") {
|
|
147
|
-
return (proxy as any)[property] !== undefined;
|
|
148
|
-
}
|
|
149
|
-
if (config.hasCallback) {
|
|
150
|
-
let pathStr = appendToPathStr(parentPathStr, property);
|
|
151
|
-
return config.hasCallback(pathStr);
|
|
152
|
-
}
|
|
153
|
-
let keys = config.getKeys(parentPathStr);
|
|
154
|
-
return keys.includes(property as string);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
);
|
|
158
|
-
return proxy;
|
|
159
|
-
}
|
|
1
|
+
import { runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
+
import { cache, lazy } from "socket-function/src/caching";
|
|
3
|
+
import { appendToPathStr, rootPathStr } from "../path";
|
|
4
|
+
import { runCodeWithDatabase } from "./pathDatabaseProxyBase";
|
|
5
|
+
import { canHaveChildren } from "socket-function/src/types";
|
|
6
|
+
import { isNode, timeInMinute } from "socket-function/src/misc";
|
|
7
|
+
|
|
8
|
+
let noopProxy = createPathValueProxy({ getCallback: () => undefined, setCallback: () => { }, getSymbol: () => undefined, getKeys: () => [] });
|
|
9
|
+
let gettingPath: { value: string } | undefined;
|
|
10
|
+
let gettingPathAndWatch: { value: string } | undefined;
|
|
11
|
+
// IMPORTANT! Does NOT add a watch to the proxy path! This means it can be used to check
|
|
12
|
+
// if a path is being watched.
|
|
13
|
+
// - HOWEVER, it also runs the get() WITHOUT synced state, so if you want to access any nested synced
|
|
14
|
+
// objects, you need to get their values outside of the get (which can incur a watch).
|
|
15
|
+
export function getProxyPath(get: () => unknown): string {
|
|
16
|
+
// We need to set the database incase it isn't set.
|
|
17
|
+
return runCodeWithDatabase(noopProxy, () => {
|
|
18
|
+
if (gettingPath) throw new Error(`Can't call getProxyPath while already getting a path.`);
|
|
19
|
+
gettingPath = { value: "" };
|
|
20
|
+
try {
|
|
21
|
+
get();
|
|
22
|
+
return gettingPath.value;
|
|
23
|
+
} finally {
|
|
24
|
+
gettingPath = undefined;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getProxyPathAndWatch(get: () => unknown): string {
|
|
30
|
+
// We need to set the database incase it isn't set.
|
|
31
|
+
return runCodeWithDatabase(noopProxy, () => {
|
|
32
|
+
if (gettingPathAndWatch) throw new Error(`Can't call getProxyPathAndWatch while already getting a path.`);
|
|
33
|
+
gettingPathAndWatch = { value: "" };
|
|
34
|
+
try {
|
|
35
|
+
get();
|
|
36
|
+
return gettingPathAndWatch.value;
|
|
37
|
+
} finally {
|
|
38
|
+
gettingPathAndWatch = undefined;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const isProxySymbol = Symbol.for("isProxySymbol");
|
|
44
|
+
export function isValueProxy(value: unknown) {
|
|
45
|
+
return canHaveChildren(value) && value[isProxySymbol];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const isProxySymbol2 = Symbol.for("isProxySymbol2");
|
|
49
|
+
/** If it is a proxy, returns the proxy path */
|
|
50
|
+
export function isValueProxy2(value: unknown): string | undefined {
|
|
51
|
+
return canHaveChildren(value) && value[isProxySymbol2] as any || undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const getPathFromProxy = isValueProxy2;
|
|
55
|
+
(globalThis as any).getPathFromProxy = getPathFromProxy;
|
|
56
|
+
|
|
57
|
+
export function createPathValueProxy(
|
|
58
|
+
config: {
|
|
59
|
+
getCallback: (pathStr: string) => { value: unknown } | undefined;
|
|
60
|
+
setCallback: (pathStr: string, value: unknown) => void;
|
|
61
|
+
hasCallback?: (pathStr: string) => boolean;
|
|
62
|
+
getSymbol: (pathStr: string, symbol: symbol) => { value: unknown } | undefined;
|
|
63
|
+
getKeys: (pathStr: string) => string[];
|
|
64
|
+
}
|
|
65
|
+
): unknown {
|
|
66
|
+
let createdCount = 0;
|
|
67
|
+
let createProxyCached = cache(createProxy);
|
|
68
|
+
let proxyClearLoop = lazy(() => {
|
|
69
|
+
runInfinitePoll(timeInMinute * 5, function clearProxyCache() {
|
|
70
|
+
// TODO: Maybe allow proxies to stick around?
|
|
71
|
+
//if (createdCount > 1000 * 100)
|
|
72
|
+
{
|
|
73
|
+
createdCount = 0;
|
|
74
|
+
createProxyCached = cache(createProxy);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
let ensureProxyClearLoop = () => {
|
|
79
|
+
if (createdCount < 1000 * 10) return;
|
|
80
|
+
proxyClearLoop();
|
|
81
|
+
};
|
|
82
|
+
return createProxyCached(rootPathStr);
|
|
83
|
+
function createProxy(parentPathStr: string) {
|
|
84
|
+
createdCount++;
|
|
85
|
+
let proxy: object = new Proxy(
|
|
86
|
+
// NOTE: This is an object now, because the data won't contain any functions, as that makes things way too
|
|
87
|
+
// complicated, and dangerous.
|
|
88
|
+
Object.create(null),
|
|
89
|
+
{
|
|
90
|
+
get(target, property) {
|
|
91
|
+
if (property === isProxySymbol) return true;
|
|
92
|
+
if (property === isProxySymbol2) return parentPathStr;
|
|
93
|
+
ensureProxyClearLoop();
|
|
94
|
+
if (typeof property === "symbol") {
|
|
95
|
+
let value = config.getSymbol(parentPathStr, property);
|
|
96
|
+
if (value) {
|
|
97
|
+
return value.value;
|
|
98
|
+
}
|
|
99
|
+
return createProxyCached(parentPathStr);
|
|
100
|
+
}
|
|
101
|
+
// if (property === "[object Object]") {
|
|
102
|
+
// console.warn(`Reading from key that looks like an object. Is it possible that you passed a proxy as a parameter which had no value? You might want to check if the key has a value first with if(atomic(value)). In ${parentPathStr}`);
|
|
103
|
+
// }
|
|
104
|
+
|
|
105
|
+
let pathStr = appendToPathStr(parentPathStr, property);
|
|
106
|
+
if (gettingPath) {
|
|
107
|
+
gettingPath.value = pathStr;
|
|
108
|
+
} else {
|
|
109
|
+
if (gettingPathAndWatch) {
|
|
110
|
+
gettingPathAndWatch.value = pathStr;
|
|
111
|
+
}
|
|
112
|
+
let value = config.getCallback(pathStr);
|
|
113
|
+
if (value) {
|
|
114
|
+
return value.value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return createProxyCached(pathStr);
|
|
118
|
+
},
|
|
119
|
+
set(obj, prop, value) {
|
|
120
|
+
if (typeof prop === "symbol") {
|
|
121
|
+
throw new Error("Can't set symbol properties in the database");
|
|
122
|
+
}
|
|
123
|
+
let pathStr = appendToPathStr(parentPathStr, prop);
|
|
124
|
+
config.setCallback(pathStr, value);
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
// Just convert delete to be a set to undefined, so everything works
|
|
128
|
+
deleteProperty(target, prop) {
|
|
129
|
+
if (typeof prop === "symbol") {
|
|
130
|
+
throw new Error("Can't set symbol properties in the database");
|
|
131
|
+
}
|
|
132
|
+
let pathStr = appendToPathStr(parentPathStr, prop);
|
|
133
|
+
config.setCallback(pathStr, undefined);
|
|
134
|
+
return true;
|
|
135
|
+
},
|
|
136
|
+
getOwnPropertyDescriptor(target, property) {
|
|
137
|
+
// NOTE: The "value" field shouldn't even be part of the property descriptor, and usually isn't
|
|
138
|
+
// used. Returning it would just be inefficient, So while we should have
|
|
139
|
+
// `value: this[prop]` here, I'm not adding it.
|
|
140
|
+
return { configurable: true, enumerable: true, writable: true };
|
|
141
|
+
},
|
|
142
|
+
ownKeys(target) {
|
|
143
|
+
return config.getKeys(parentPathStr);
|
|
144
|
+
},
|
|
145
|
+
has(target, property) {
|
|
146
|
+
if (typeof property === "symbol") {
|
|
147
|
+
return (proxy as any)[property] !== undefined;
|
|
148
|
+
}
|
|
149
|
+
if (config.hasCallback) {
|
|
150
|
+
let pathStr = appendToPathStr(parentPathStr, property);
|
|
151
|
+
return config.hasCallback(pathStr);
|
|
152
|
+
}
|
|
153
|
+
let keys = config.getKeys(parentPathStr);
|
|
154
|
+
return keys.includes(property as string);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
return proxy;
|
|
159
|
+
}
|
|
160
160
|
}
|