querysub 0.2.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/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- package/yarnSpec.txt +56 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { cache, lazy } from "socket-function/src/caching";
|
|
3
|
+
import { appendToPathStr, getPathDepth, getPathFromStr, getPathIndex, getPathStr1, getPathStr2, getPathStr3, hack_getPackedPathSuffix, removePathLastPart } from "../path";
|
|
4
|
+
import { CHILD_CHECK_PREFIX, FunctionMetadata } from "../3-path-functions/syncSchema";
|
|
5
|
+
import { RemoteWatcher, remoteWatcher } from "../1-path-client/RemoteWatcher";
|
|
6
|
+
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
7
|
+
import { atomic, atomicObjectWrite, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
8
|
+
import { CallSpec, DEPTH_TO_DATA, FunctionSpec, debugCallSpec, functionSchema } from "../3-path-functions/PathFunctionRunner";
|
|
9
|
+
import { PathValueController } from "../0-path-value-core/PathValueController";
|
|
10
|
+
import { epochTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, pathWatcher, Time, WatchConfig, compareTime, debugTime, getNextTime, MAX_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
11
|
+
import { IdentityController_getMachineId, IdentityController_getPubKeyShort, IdentityController_getSecureIP } from "../-c-identity/IdentityController";
|
|
12
|
+
import { getControllerNodeId } from "../-g-core-values/NodeCapabilities";
|
|
13
|
+
import { getModuleFromConfig, watchModuleHotreloads } from "../3-path-functions/pathFunctionLoader";
|
|
14
|
+
import { errorToUndefined, ignoreErrors, logErrors } from "../errors";
|
|
15
|
+
import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
16
|
+
import { PermissionsCheck } from "./permissions";
|
|
17
|
+
import { parseArgs, writeCall } from "../3-path-functions/PathFunctionHelpers";
|
|
18
|
+
import { delay } from "socket-function/src/batching";
|
|
19
|
+
import { isNode, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
20
|
+
import { registerResource } from "../diagnostics/trackResources";
|
|
21
|
+
|
|
22
|
+
// Always whitelist preact, as most of the time we want it clientside
|
|
23
|
+
import "preact";
|
|
24
|
+
import { setFlag } from "socket-function/require/compileFlags";
|
|
25
|
+
import { CallerContextBase } from "socket-function/SocketFunctionTypes";
|
|
26
|
+
import { isTrustedByNode } from "../-d-trust/NetworkTrust2";
|
|
27
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
28
|
+
import { Querysub, id } from "./Querysub";
|
|
29
|
+
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
30
|
+
import debugbreak from "debugbreak";
|
|
31
|
+
import { isDefined } from "../misc";
|
|
32
|
+
import { isClient, isServer } from "../config2";
|
|
33
|
+
import { PromiseObj } from "../promise";
|
|
34
|
+
import { LoggingClient } from "../0-path-value-core/LoggingClient";
|
|
35
|
+
import * as prediction from "./querysubPrediction";
|
|
36
|
+
import { getCallResultPath } from "./querysubPrediction";
|
|
37
|
+
import { pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
|
|
38
|
+
import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
39
|
+
setFlag(require, "preact", "allowclient", true);
|
|
40
|
+
|
|
41
|
+
export { Querysub, id };
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// NOTE: Eventually this will be split by domain, because the underlying getAllNodeIds
|
|
45
|
+
// will be split by domain.
|
|
46
|
+
// NOTE: We don't need to shard querysub nodes within a domain, because... they don't
|
|
47
|
+
// store data, and don't run functions, and only really check permissions (which should be
|
|
48
|
+
// very lightweight, hopefully...)
|
|
49
|
+
// - I guess we track our watches, but even that... should be fairly light, and... there
|
|
50
|
+
// is little benefit to sharding that?
|
|
51
|
+
export const querysubNodeId = lazy(() => getControllerNodeId(QuerysubController));
|
|
52
|
+
|
|
53
|
+
setImmediate(() => {
|
|
54
|
+
async function querysubWatchLatest(config: WatchConfig) {
|
|
55
|
+
let nodeId = await querysubNodeId();
|
|
56
|
+
if (!nodeId) throw new Error("No querysub node found");
|
|
57
|
+
await QuerysubController.nodes[nodeId].watch(config);
|
|
58
|
+
}
|
|
59
|
+
async function querysubUnwatchLatest(config: WatchConfig) {
|
|
60
|
+
let nodeId = await querysubNodeId();
|
|
61
|
+
if (!nodeId) throw new Error("No querysub node found");
|
|
62
|
+
await QuerysubController.nodes[nodeId].unwatch(config);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let baseRemoteWatchFunction = RemoteWatcher.REMOTE_WATCH_FUNCTION;
|
|
66
|
+
RemoteWatcher.REMOTE_WATCH_FUNCTION = async (config, authorityId) => {
|
|
67
|
+
let weTrusted = isServer() && await errorToUndefined(isTrustedByNode(authorityId));
|
|
68
|
+
if (!weTrusted) {
|
|
69
|
+
await querysubWatchLatest(config);
|
|
70
|
+
} else {
|
|
71
|
+
await baseRemoteWatchFunction(config, authorityId);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let baseRemoteUnwatchFunction = RemoteWatcher.REMOTE_UNWATCH_FUNCTION;
|
|
76
|
+
RemoteWatcher.REMOTE_UNWATCH_FUNCTION = async (config, authorityIdBase) => {
|
|
77
|
+
let weTrusted = isServer() && await isTrustedByNode(authorityIdBase);
|
|
78
|
+
if (!weTrusted) {
|
|
79
|
+
logErrors(querysubUnwatchLatest(config));
|
|
80
|
+
} else {
|
|
81
|
+
logErrors(baseRemoteUnwatchFunction(config, authorityIdBase));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
function permissionsHash(caller: Readonly<CallerContextBase>) {
|
|
89
|
+
let machineId = IdentityController_getMachineId(caller);
|
|
90
|
+
let ip = IdentityController_getSecureIP(caller);
|
|
91
|
+
// NOTE: We also need the nodeId, as while the permissions are the same per machineId + ip, each
|
|
92
|
+
// process needs to be sent it's own values!
|
|
93
|
+
return getPathStr3(machineId, ip, caller.nodeId);
|
|
94
|
+
}
|
|
95
|
+
interface PermissionsObj {
|
|
96
|
+
nodes: Set<string>;
|
|
97
|
+
|
|
98
|
+
newPaths: Set<string>;
|
|
99
|
+
newParentPaths: Set<string>;
|
|
100
|
+
|
|
101
|
+
// permissionsPath => Set<path>
|
|
102
|
+
permissionsPathGroups: Map<string, {
|
|
103
|
+
allowed: boolean;
|
|
104
|
+
outdated: boolean;
|
|
105
|
+
originalPaths: Set<string>;
|
|
106
|
+
// testPath => parentPath
|
|
107
|
+
originalParentPaths: Map<string, string>;
|
|
108
|
+
}>;
|
|
109
|
+
pathToPermissionsPath: Map<string, string>;
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
checkNow: () => void;
|
|
113
|
+
dispose: () => void;
|
|
114
|
+
}
|
|
115
|
+
let permissionsPerNode = new Map<string, PermissionsObj>();
|
|
116
|
+
registerResource("querysub|permissionsPerNode", permissionsPerNode);
|
|
117
|
+
|
|
118
|
+
const lastCallTime = cache((nodeId: string): { value: Time } => {
|
|
119
|
+
SocketFunction.onNextDisconnect(nodeId, () => {
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
if (SocketFunction.isNodeConnected(nodeId)) return;
|
|
122
|
+
lastCallTime.clear(nodeId);
|
|
123
|
+
}, 1000 * 60);
|
|
124
|
+
});
|
|
125
|
+
return { value: epochTime };
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const isTrustedByDomain = cache(async function isTrustedByDomain(domain: string): Promise<boolean> {
|
|
129
|
+
// NOTE: When getControllerNodeId accepts a domain we will pass them the domain we are using.
|
|
130
|
+
|
|
131
|
+
// If we are trusted by one, we should be trusted by call!
|
|
132
|
+
// TODO: We should probably handle trust revocation? Or... not? Because that would only happen
|
|
133
|
+
// if we take a server offline, in which case... it would be off anyways.
|
|
134
|
+
let pathValueNodeId = await getControllerNodeId(PathValueController);
|
|
135
|
+
if (!pathValueNodeId) {
|
|
136
|
+
return false;
|
|
137
|
+
//throw new Error(`No PathValueController found`);
|
|
138
|
+
}
|
|
139
|
+
return isTrustedByNode(pathValueNodeId);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// NOTE: The last promise (added) ends up being the call we wait on, so that predictions
|
|
143
|
+
// can extend the wait time as they do more work.
|
|
144
|
+
let pendingPredictedCalls = new Map<string, {
|
|
145
|
+
obj: PromiseObj;
|
|
146
|
+
seqNum: number;
|
|
147
|
+
}>();
|
|
148
|
+
export function callWaitOn(callId: string, promise: Promise<unknown>) {
|
|
149
|
+
let waitObj = pendingPredictedCalls.get(callId);
|
|
150
|
+
if (!waitObj) return;
|
|
151
|
+
let seqNum = ++waitObj.seqNum;
|
|
152
|
+
void promise.finally(() => {
|
|
153
|
+
if (waitObj?.seqNum === seqNum) {
|
|
154
|
+
waitObj.obj.resolve();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
let baseWriteCall = writeCall.value;
|
|
159
|
+
writeCall.value = async (callSpec: CallSpec, metadata: FunctionMetadata) => {
|
|
160
|
+
pendingPredictedCalls.set(callSpec.CallId, {
|
|
161
|
+
obj: new PromiseObj(),
|
|
162
|
+
seqNum: 0,
|
|
163
|
+
});
|
|
164
|
+
let promise = (async () => {
|
|
165
|
+
// IMPORTANT! While it may seem like we can use addCall even if we are trusted, so we can
|
|
166
|
+
// have function calling prediction, we can't. If did then we would depend on these
|
|
167
|
+
// predictions with our writes, which could end up on other nodes, which would then
|
|
168
|
+
// be rejected (as our predictions use a Time.version that is only on our
|
|
169
|
+
// node). The in unavoidable, as it is too difficult to have predictions, but also not have
|
|
170
|
+
// predictions, on the same node.
|
|
171
|
+
// - AND, we can't just use the same Time.version as the FunctionRunner will use, as our
|
|
172
|
+
// ValuePaths might differ, which would sometimes work, BUT, other times our ReadLocks
|
|
173
|
+
// would be valid, BUT, the actual read value would be different (as we might compute
|
|
174
|
+
// a different value than FunctionRunner). So... we would lose locking, at which point,
|
|
175
|
+
// why even use FunctionRunner in the first place! Just write the writes directly!
|
|
176
|
+
// TODO: Add the ability to use direct writes BUT if we have a rejection, commit it as a regular function call
|
|
177
|
+
// - This will be a lot more efficient, although, if the committed dies and the function is rejected, the
|
|
178
|
+
// value will be lost, which isn't great. However... trusted nodes shouldn't be dying so easily!
|
|
179
|
+
let isTrusted = isServer() && await isTrustedByDomain(callSpec.DomainName);
|
|
180
|
+
if (isTrusted) {
|
|
181
|
+
// NOTE: Direct writes should probably be used for calls from trusted nodes, which will
|
|
182
|
+
// be a lot faster.
|
|
183
|
+
await baseWriteCall(callSpec, metadata);
|
|
184
|
+
} else {
|
|
185
|
+
await addCall(callSpec, metadata);
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
188
|
+
callWaitOn(callSpec.CallId, promise);
|
|
189
|
+
await promise;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export async function onCallPredict(call: CallSpec | undefined) {
|
|
193
|
+
if (!call) return;
|
|
194
|
+
await pendingPredictedCalls.get(call.CallId)?.obj.promise;
|
|
195
|
+
}
|
|
196
|
+
export async function waitUntilAllPredictionsFinish() {
|
|
197
|
+
try {
|
|
198
|
+
await Promise.allSettled(Array.from(pendingPredictedCalls.values()).map(obj => obj.obj.promise));
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
export async function flushDelayedFunctions() {
|
|
205
|
+
await prediction.flushDelayedFunctions();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function addCall(call: CallSpec, metadata: FunctionMetadata) {
|
|
209
|
+
await prediction.addCall(call, metadata);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function baseAddCall(call: CallSpec, nodeId: string, cancel: () => void, type: string) {
|
|
213
|
+
if (Querysub.DEBUG_CALLS) {
|
|
214
|
+
console.log(`[Querysub] ${magenta(type)} ${green(debugCallSpec(call))} @${debugTime(call.runAtTime)}`);
|
|
215
|
+
}
|
|
216
|
+
let errorWatcher = Querysub.createWatcher(() => {
|
|
217
|
+
let callResult = atomic(prediction.getCallResult(call));
|
|
218
|
+
if (callResult && "error" in callResult && Querysub.isAllSynced()) {
|
|
219
|
+
console.error(callResult.error.split("\n")[0] + `\n (${call.DomainName}.${call.FunctionId})\n ${callResult.error}`);
|
|
220
|
+
// Stop watching when we get the first error, otherwise other changes will trigger duplicate errors
|
|
221
|
+
// (such as when the prediction is removed).
|
|
222
|
+
errorWatcher.dispose();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
errorWatcher.dispose();
|
|
227
|
+
}, MAX_CHANGE_AGE);
|
|
228
|
+
try {
|
|
229
|
+
await QuerysubController.nodes[nodeId].addCall(call);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
// Cancellation is IMPORTANT! Otherwise the prediction might never be rejected,
|
|
232
|
+
// and so the client will keep stale data!
|
|
233
|
+
cancel();
|
|
234
|
+
throw e;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
class QuerysubControllerBase {
|
|
239
|
+
public async watch(config: WatchConfig) {
|
|
240
|
+
for (let path of config.paths) {
|
|
241
|
+
Querysub.assertDomainAllowed(path);
|
|
242
|
+
}
|
|
243
|
+
for (let path of config.parentPaths) {
|
|
244
|
+
Querysub.assertDomainAllowed(path);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Both watch it locally, AND remotely, so we will receive all the changes,
|
|
248
|
+
// AND forward them to the watcher
|
|
249
|
+
let caller = SocketFunction.getCaller();
|
|
250
|
+
let callerId = caller.nodeId;
|
|
251
|
+
let callerMachineId = IdentityController_getMachineId(caller);
|
|
252
|
+
let callerIP = IdentityController_getSecureIP(caller);
|
|
253
|
+
let pHash = permissionsHash(caller);
|
|
254
|
+
|
|
255
|
+
if (Querysub.SIMULATE_LAG) {
|
|
256
|
+
await delay(Querysub.SIMULATE_LAG);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let permissionsObjIn = permissionsPerNode.get(pHash);
|
|
260
|
+
if (!permissionsObjIn) {
|
|
261
|
+
|
|
262
|
+
permissionsObjIn = {
|
|
263
|
+
nodes: new Set(),
|
|
264
|
+
|
|
265
|
+
newPaths: new Set(),
|
|
266
|
+
newParentPaths: new Set(),
|
|
267
|
+
|
|
268
|
+
permissionsPathGroups: new Map(),
|
|
269
|
+
pathToPermissionsPath: new Map(),
|
|
270
|
+
|
|
271
|
+
checkNow: () => { },
|
|
272
|
+
dispose: () => { },
|
|
273
|
+
};
|
|
274
|
+
const permissionsObj = permissionsObjIn;
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
SocketFunction.onNextDisconnect(callerId, () => {
|
|
278
|
+
permissionsPerNode.delete(pHash);
|
|
279
|
+
permissionsObj?.dispose();
|
|
280
|
+
});
|
|
281
|
+
// TODO: Once we have fast and easy incremental watches, use that to make this faster
|
|
282
|
+
let watcher = proxyWatcher.createWatcher({
|
|
283
|
+
watchFunction: this.createPermissionsCheck({ permissionsObj, callerMachineId, callerIP, callerId }),
|
|
284
|
+
});
|
|
285
|
+
watchModuleHotreloads(watcher);
|
|
286
|
+
permissionsObjIn.dispose = watcher.dispose;
|
|
287
|
+
permissionsObjIn.checkNow = watcher.explicitlyTrigger;
|
|
288
|
+
permissionsPerNode.set(pHash, permissionsObjIn);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (let path of config.paths) {
|
|
292
|
+
permissionsObjIn.newPaths.add(path);
|
|
293
|
+
}
|
|
294
|
+
for (let path of config.parentPaths) {
|
|
295
|
+
permissionsObjIn.newParentPaths.add(path);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
permissionsObjIn.checkNow();
|
|
299
|
+
}
|
|
300
|
+
private createPermissionsCheck(config: {
|
|
301
|
+
permissionsObj: PermissionsObj;
|
|
302
|
+
callerMachineId: string;
|
|
303
|
+
callerIP: string;
|
|
304
|
+
callerId: string;
|
|
305
|
+
}) {
|
|
306
|
+
const { permissionsObj, callerMachineId, callerIP, callerId } = config;
|
|
307
|
+
return function querysubCheckPermissions() {
|
|
308
|
+
if (!permissionsObj) throw new Error(`Internal error`);
|
|
309
|
+
|
|
310
|
+
let permissions = new PermissionsCheck({ callerMachineId, callerIP });
|
|
311
|
+
|
|
312
|
+
let extraPathsToCheck = new Set<string>();
|
|
313
|
+
let allowedPaths = new Set<string>();
|
|
314
|
+
let notAllowedPaths = new Set<string>();
|
|
315
|
+
let removedPaths = new Set<string>();
|
|
316
|
+
|
|
317
|
+
// HACK: Maintain a recording of parent paths separately, to make some code simpler.
|
|
318
|
+
let parentPathsLookup = new Map<string, string>();
|
|
319
|
+
|
|
320
|
+
const newPaths = permissionsObj.newPaths;
|
|
321
|
+
const newParentPaths = permissionsObj.newParentPaths;
|
|
322
|
+
|
|
323
|
+
function addPermissionsPath(path: string, parentPath: string | undefined) {
|
|
324
|
+
let checkResult = permissions.checkPermissions(path);
|
|
325
|
+
if (Querysub.anyUnsynced()) return;
|
|
326
|
+
let permissionsPath = checkResult.permissionsPath;
|
|
327
|
+
let group = permissionsObj.permissionsPathGroups.get(permissionsPath);
|
|
328
|
+
if (!group) {
|
|
329
|
+
group = { allowed: checkResult.allowed, originalPaths: new Set(), originalParentPaths: new Map(), outdated: true };
|
|
330
|
+
permissionsObj.permissionsPathGroups.set(permissionsPath, group);
|
|
331
|
+
}
|
|
332
|
+
if (parentPath !== undefined) {
|
|
333
|
+
group.originalParentPaths.set(path, parentPath);
|
|
334
|
+
} else {
|
|
335
|
+
group.originalPaths.add(path);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
permissionsObj.pathToPermissionsPath.set(path, permissionsPath);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (let path of newPaths) {
|
|
342
|
+
extraPathsToCheck.add(path);
|
|
343
|
+
addPermissionsPath(path, undefined);
|
|
344
|
+
}
|
|
345
|
+
for (let parentPath of newParentPaths) {
|
|
346
|
+
// NOTE: checkPermissions handles "" keys specially, but we still need to
|
|
347
|
+
// turn them into a child key watch!
|
|
348
|
+
let testPath = appendToPathStr(parentPath, "");
|
|
349
|
+
parentPathsLookup.set(testPath, parentPath);
|
|
350
|
+
extraPathsToCheck.add(testPath);
|
|
351
|
+
addPermissionsPath(testPath, parentPath);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (let [permissionsPath, obj] of permissionsObj.permissionsPathGroups) {
|
|
355
|
+
// NOTE: Even if it is already unsynced, the allowed flag might be wrong, so we have to check
|
|
356
|
+
// it irregardless.
|
|
357
|
+
let allowed = permissions.checkPermissions(permissionsPath).allowed;
|
|
358
|
+
if (allowed !== obj.allowed && !Querysub.anyUnsynced()) {
|
|
359
|
+
// NOTE: We don't reset outdated even if we return, that way if we run
|
|
360
|
+
// again we will check any previously outdated paths as well (otherwise we miss them).
|
|
361
|
+
obj.outdated = true;
|
|
362
|
+
obj.allowed = allowed;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Don't commit anything if we have unsynced accesses after rechecking our permission paths
|
|
367
|
+
if (Querysub.anyUnsynced()) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ONLY consume the paths if we are ready to apply them
|
|
372
|
+
permissionsObj.newPaths = new Set();
|
|
373
|
+
permissionsObj.newParentPaths = new Set();
|
|
374
|
+
|
|
375
|
+
let outdatedCount = 0;
|
|
376
|
+
for (let [permissionsPath, obj] of permissionsObj.permissionsPathGroups) {
|
|
377
|
+
if (!obj.outdated) continue;
|
|
378
|
+
// if (permissionsPath.startsWith(".,querysubtest._com.,PathFunctionRunner.,Page.,Data.,world.,grid")) {
|
|
379
|
+
// console.log(magenta(`Changed permission path ${permissionsPath}, ${obj.allowed ? "allowed" : "not allowed"}`), obj.originalPaths);
|
|
380
|
+
// }
|
|
381
|
+
outdatedCount++;
|
|
382
|
+
obj.outdated = false;
|
|
383
|
+
for (let path of obj.originalPaths) {
|
|
384
|
+
if (obj.allowed) allowedPaths.add(path);
|
|
385
|
+
else {
|
|
386
|
+
notAllowedPaths.add(path);
|
|
387
|
+
removedPaths.add(path);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
for (let [path, parentPath] of obj.originalParentPaths) {
|
|
391
|
+
parentPathsLookup.set(path, parentPath);
|
|
392
|
+
if (obj.allowed) allowedPaths.add(path);
|
|
393
|
+
else {
|
|
394
|
+
notAllowedPaths.add(path);
|
|
395
|
+
removedPaths.add(path);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for (let path of extraPathsToCheck) {
|
|
401
|
+
let allowed = permissions.checkPermissions(path).allowed;
|
|
402
|
+
if (allowed) allowedPaths.add(path);
|
|
403
|
+
else notAllowedPaths.add(path);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
function splitParentPaths(paths: Set<string>): [string[], string[]] {
|
|
408
|
+
let parentPaths = Array.from(paths).map(path => parentPathsLookup.get(path)).filter(isDefined);
|
|
409
|
+
// IMPORTANT! DO NOT filter parent paths out of regular paths. If we did this, then if they
|
|
410
|
+
// tried to sync any direct path that includes [""], they would never receive the value!
|
|
411
|
+
let justPaths = Array.from(paths);
|
|
412
|
+
return [parentPaths, justPaths];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (notAllowedPaths.size > 0) {
|
|
416
|
+
if (PermissionsCheck.DEBUG && Querysub.isAllSynced()) {
|
|
417
|
+
let hackNotRootPaths = Array.from(notAllowedPaths).filter(path => getPathDepth(path) > DEPTH_TO_DATA);
|
|
418
|
+
if (hackNotRootPaths.length > 0) {
|
|
419
|
+
for (let path of hackNotRootPaths) {
|
|
420
|
+
let test = permissions.checkPermissions(path);
|
|
421
|
+
permissions.checkPermissions(test.permissionsPath);
|
|
422
|
+
}
|
|
423
|
+
console.log(red(`Disallowing watches due to disallowed permissions.`));
|
|
424
|
+
for (let path of hackNotRootPaths) console.log(` ${blue(path)}`);
|
|
425
|
+
logErrors(LoggingClient.nodes[callerId].onMessage({
|
|
426
|
+
type: "warn",
|
|
427
|
+
message: `Disallowing ${hackNotRootPaths.length} watches due to disallowed permissions.`,
|
|
428
|
+
context: (
|
|
429
|
+
hackNotRootPaths.map(path => ` ${path}`).join("\n")
|
|
430
|
+
),
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
let [newParentPathsNotAllowed, newPathsNotAllowed] = splitParentPaths(notAllowedPaths);
|
|
435
|
+
// Send them undefined values at epochTime
|
|
436
|
+
// NOTE: We COULD send at time Date.now(), which would allow clobbering any existing values. This might
|
|
437
|
+
// make the UI look cleaner (instead of showing stale values, it shows nothing)?
|
|
438
|
+
let undefinedValues: PathValue[] = newPathsNotAllowed.map(path => ({ path, value: undefined, canGCValue: true, time: epochTime, locks: [], lockCount: 0, valid: true, event: false }));
|
|
439
|
+
|
|
440
|
+
diskLog(`Disallowing PathValue watches due to disallowed permissions`, { count: newPathsNotAllowed.length, callerId });
|
|
441
|
+
|
|
442
|
+
ignoreErrors(pathValueSerializer.serialize(undefinedValues, { compress: Querysub.COMPRESS_NETWORK }).then(buffers =>
|
|
443
|
+
PathValueController.nodes[callerId].forwardWrites(
|
|
444
|
+
buffers,
|
|
445
|
+
Array.from(newParentPathsNotAllowed),
|
|
446
|
+
)
|
|
447
|
+
));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (removedPaths.size > 0) {
|
|
451
|
+
let [removedParentPaths, removedJustPaths] = splitParentPaths(removedPaths);
|
|
452
|
+
pathWatcher.unwatchPath({ callback: callerId, paths: removedJustPaths, parentPaths: removedParentPaths });
|
|
453
|
+
}
|
|
454
|
+
if (allowedPaths.size > 0) {
|
|
455
|
+
let [newParentPathsAllowed, newPathsAllowed] = splitParentPaths(allowedPaths);
|
|
456
|
+
let watchConfig: WatchConfig = { paths: newPathsAllowed, parentPaths: newParentPathsAllowed };
|
|
457
|
+
pathWatcher.watchPath({ callback: callerId, ...watchConfig });
|
|
458
|
+
remoteWatcher.watchLatest(watchConfig);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
public async unwatch(config: WatchConfig) {
|
|
463
|
+
let pHash = permissionsHash(SocketFunction.getCaller());
|
|
464
|
+
let callerId = SocketFunction.getCaller().nodeId;
|
|
465
|
+
let permissionsObjIn = permissionsPerNode.get(pHash);
|
|
466
|
+
if (permissionsObjIn) {
|
|
467
|
+
const permissionsObj = permissionsObjIn;
|
|
468
|
+
function delPermissionsPath(path: string, parentPath: boolean) {
|
|
469
|
+
let permissionsPath = permissionsObj.pathToPermissionsPath.get(path);
|
|
470
|
+
if (!permissionsPath) return;
|
|
471
|
+
let group = permissionsObj.permissionsPathGroups.get(permissionsPath);
|
|
472
|
+
if (!group) return;
|
|
473
|
+
permissionsObj.pathToPermissionsPath.delete(path);
|
|
474
|
+
group.originalPaths.delete(path);
|
|
475
|
+
if (parentPath) {
|
|
476
|
+
group.originalParentPaths.delete(path);
|
|
477
|
+
}
|
|
478
|
+
if (group.originalPaths.size === 0 && group.originalParentPaths.size === 0) {
|
|
479
|
+
permissionsObj.permissionsPathGroups.delete(permissionsPath);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (let path of config.paths) {
|
|
484
|
+
delPermissionsPath(path, false);
|
|
485
|
+
}
|
|
486
|
+
for (let path of config.parentPaths) {
|
|
487
|
+
delPermissionsPath(appendToPathStr(path, ""), true);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
pathWatcher.unwatchPath({ callback: callerId, ...config });
|
|
491
|
+
}
|
|
492
|
+
public async addCall(call: CallSpec) {
|
|
493
|
+
if (Querysub.DEBUG_CALLS) {
|
|
494
|
+
console.log(`[Querysub] addCall @${debugTime(call.runAtTime)}: ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
|
|
495
|
+
}
|
|
496
|
+
Querysub.assertDomainAllowed(getPathStr1(call.DomainName));
|
|
497
|
+
|
|
498
|
+
let caller = SocketFunction.getCaller();
|
|
499
|
+
let callerMachineId = IdentityController_getMachineId(caller);
|
|
500
|
+
let callerCreatorId = IdentityController_getPubKeyShort(caller);
|
|
501
|
+
call.callerIP = IdentityController_getSecureIP(caller);
|
|
502
|
+
|
|
503
|
+
if (Querysub.SIMULATE_LAG) {
|
|
504
|
+
await delay(Querysub.SIMULATE_LAG * 2);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 0) Ensure the call is valid
|
|
508
|
+
// - Verifies callerMachineId
|
|
509
|
+
// - Uses MAX_ACCEPTED_CHANGE_AGE to check the write time
|
|
510
|
+
// - Rejects out of order times
|
|
511
|
+
// - Verify the time creatorId
|
|
512
|
+
if (call.callerMachineId !== callerMachineId) {
|
|
513
|
+
throw new Error(`Caller machineId does not match call's callerMachineId, ${call.callerMachineId} !== ${callerMachineId}`);
|
|
514
|
+
}
|
|
515
|
+
// NOTE: We use a lower threshold than FunctionRunner to account for extra time for FunctionRunner to receive the call.
|
|
516
|
+
// We want to reject slow calls from the client promptly, as at this point we know it is a client issue. If the call
|
|
517
|
+
// passes here FunctionRunner will actually update runAtTime for old calls, running them anyways. We would prefer
|
|
518
|
+
// if slow clients don't depend on this behavior, as it is only intended to smooth over backend server issues,
|
|
519
|
+
// not to adjust slow (or really, disconnected and then reconnected) clients.
|
|
520
|
+
let now = Date.now();
|
|
521
|
+
let callTimethreshold = now - MAX_ACCEPTED_CHANGE_AGE * 0.5;
|
|
522
|
+
if (call.runAtTime.time < callTimethreshold) {
|
|
523
|
+
throw new Error(`Call is too old, and will be dropped, ${debugCallSpec(call)}, ${call.runAtTime.time} < ${callTimethreshold}`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
let futureThreshold = now + Querysub.MAX_FUTURE_CALL_TIME;
|
|
527
|
+
if (call.runAtTime.time > futureThreshold) {
|
|
528
|
+
let futureTime = call.runAtTime.time - futureThreshold;
|
|
529
|
+
throw new Error(`Call is too far in the future, ${debugCallSpec(call)}, ${call.runAtTime.time}. Future ${futureTime} > ${Querysub.MAX_FUTURE_CALL_TIME}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// NOTE: There isn't really any reason to force this, as they could easily have multiple nodes/machines connected
|
|
533
|
+
// (if they were attempting to cheat). So while running out of order might be annoying, and we would like to prevent it...
|
|
534
|
+
// this check doesn't really accomplish that in any meaningful way (and instead breaks some valid use cases).
|
|
535
|
+
// let lastTime = lastCallTime(caller.nodeId);
|
|
536
|
+
// if (compareTime(call.runAtTime, lastTime.value) <= 0) {
|
|
537
|
+
// throw new Error(`Call is out of order, ${call.runAtTime.time} <= ${lastTime.value.time}`);
|
|
538
|
+
// }
|
|
539
|
+
// lastTime.value = call.runAtTime;
|
|
540
|
+
|
|
541
|
+
if (call.runAtTime.creatorId !== callerCreatorId) {
|
|
542
|
+
throw new Error(`Call creatorId does not match caller creatorId, ${call.runAtTime.creatorId} !== ${callerCreatorId}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 1) Ensure the caller has at least permissions on the data for this module
|
|
546
|
+
// NOTE: We could use commitFunction in watch as well, but... I think watches will benefit
|
|
547
|
+
// more from the batching, where as calls need batching less (and commitFunction is easier to use).
|
|
548
|
+
let hasPermissions = await proxyWatcher.commitFunction({
|
|
549
|
+
watchFunction: function checkPermissions() {
|
|
550
|
+
let dataPath = getProxyPath(() => (functionSchema()[call.DomainName].PathFunctionRunner[call.ModuleId].Data as any)[Querysub.CALL_PERMISSIONS_KEY]);
|
|
551
|
+
let check = new PermissionsCheck(call);
|
|
552
|
+
return check.checkPermissions(dataPath).allowed;
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
if (!hasPermissions) {
|
|
557
|
+
throw new Error(`Caller does not have permission to call ${call.DomainName}.${call.ModuleId}`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// NOTE: We don't wait for our writes to actually commit, because... there isn't any reason they shouldn't
|
|
561
|
+
// except the server being down, in which case, the client can figure that out anyways?
|
|
562
|
+
proxyWatcher.writeOnly({
|
|
563
|
+
canWrite: true,
|
|
564
|
+
eventWrite: true,
|
|
565
|
+
doNotStoreWritesAsPredictions: true,
|
|
566
|
+
watchFunction: function writeCall() {
|
|
567
|
+
functionSchema()[call.DomainName].PathFunctionRunner[call.ModuleId].Calls[call.CallId] = atomicObjectWrite(call);
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
public async getModulePath(config: {
|
|
573
|
+
functionSpec: FunctionSpec;
|
|
574
|
+
}): Promise<string> {
|
|
575
|
+
let spec = config.functionSpec;
|
|
576
|
+
Querysub.assertDomainAllowed(getPathStr1(spec.DomainName));
|
|
577
|
+
let moduleConfig = await proxyWatcher.commitFunction({
|
|
578
|
+
watchFunction: function getModuleConfig() {
|
|
579
|
+
return functionSchema()[spec.DomainName].PathFunctionRunner[spec.ModuleId].Sources[spec.FunctionId];
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
if (!moduleConfig) {
|
|
583
|
+
throw new Error(`Function ${spec.DomainName}.${spec.ModuleId}.${spec.FunctionId} does not exist (a deploy might be required).`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
let module = await getModuleFromConfig(moduleConfig);
|
|
587
|
+
return module.filename;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
// NOTE: Maybe remove this? It's very useful for debugging though...
|
|
592
|
+
public async debugGetSingleReadNode(path: string) {
|
|
593
|
+
return pathValueAuthority2.getSingleReadNodeSync(path);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
public async debugGetPathAuthorities(nodeId: string) {
|
|
597
|
+
return pathValueAuthority2.getAuthorityPaths(nodeId);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export const QuerysubController = SocketFunction.register(
|
|
602
|
+
"QuerysubController-6db5ef05-7563-4473-a440-2f64f03fe6ef",
|
|
603
|
+
new QuerysubControllerBase(),
|
|
604
|
+
() => ({
|
|
605
|
+
// TODO: Maybe allow compression to be enable (or disabled) at a per call / schema level?
|
|
606
|
+
watch: { compress: true, },
|
|
607
|
+
unwatch: { compress: true, },
|
|
608
|
+
addCall: { compress: true, },
|
|
609
|
+
debugGetSingleReadNode: {},
|
|
610
|
+
debugGetPathAuthorities: {},
|
|
611
|
+
getModulePath: {},
|
|
612
|
+
}),
|
|
613
|
+
() => ({
|
|
614
|
+
|
|
615
|
+
}),
|
|
616
|
+
{
|
|
617
|
+
noAutoExpose: true,
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
|
|
File without changes
|