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,475 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { cache } from "socket-function/src/caching";
|
|
3
|
+
import { getStringKeys, sort } from "socket-function/src/misc";
|
|
4
|
+
import { Args } from "socket-function/src/types";
|
|
5
|
+
import { appendToPathStr, getPathFromStr, getPathStr, rootPathStr } from "../path";
|
|
6
|
+
import { writeFunctionCall } from "./PathFunctionHelpers";
|
|
7
|
+
import { CallSpec, functionSchema } from "./PathFunctionRunner";
|
|
8
|
+
import { getDomain } from "../config";
|
|
9
|
+
import { isHotReloading } from "socket-function/hot/HotReloadController";
|
|
10
|
+
import { Schema2, Schema2Fncs, Schema2T, SchemaPath } from "../2-proxy/schema2";
|
|
11
|
+
import { PathValueProxyWatcher, atomic, proxyWatcher, registerSchema } from "../2-proxy/PathValueProxyWatcher";
|
|
12
|
+
import { registerAliveChecker } from "../2-proxy/garbageCollection";
|
|
13
|
+
import { rawSchema } from "../2-proxy/pathDatabaseProxyBase";
|
|
14
|
+
import { ARCHIVE_FLUSH_LIMIT } from "../0-path-value-core/pathValueCore";
|
|
15
|
+
import debugbreak from "debugbreak";
|
|
16
|
+
import { getPathFromProxy, getProxyPath } from "../2-proxy/pathValueProxy";
|
|
17
|
+
import { CALL_PERMISSIONS_KEY } from "../4-querysub/permissionsShared";
|
|
18
|
+
import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
|
|
19
|
+
import path from "path";
|
|
20
|
+
|
|
21
|
+
// This is the the function id which should be used when creating the FunctionSpec (in order to load the module),
|
|
22
|
+
// to access the permissions in the schema.
|
|
23
|
+
export const PERMISSIONS_FUNCTION_ID = "PERMISSIONS_FUNCTION_ID";
|
|
24
|
+
|
|
25
|
+
const SCHEMA_EXPORT_KEY = "schema-00c87ab5-10bb-4795-8f6b-e187a7e45841";
|
|
26
|
+
export function getSchemaObject(module: NodeJS.Module): SchemaObject | undefined {
|
|
27
|
+
return module.exports[SCHEMA_EXPORT_KEY];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getExportPath(functionId: string): string {
|
|
31
|
+
return getPathStr([SCHEMA_EXPORT_KEY, "rawFunctions", functionId]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type FunctionMetadata<F = unknown> = {
|
|
35
|
+
nopredict?: boolean;
|
|
36
|
+
/** Delay functions commits by Querysub.DELAY_COMMIT_DELAY
|
|
37
|
+
* - We collapse redundant delayed calls, by looking at reads/writes.
|
|
38
|
+
* - IMPORTANT! If you delay a function which writes to A, then write to B, which triggers
|
|
39
|
+
* a remote server to read A, the server will read the old value of A, which breaks things.
|
|
40
|
+
* If this is happening, either stop delaying writes to A, OR, call flushDelayedFunctions
|
|
41
|
+
* before writing to B.
|
|
42
|
+
*/
|
|
43
|
+
delayCommit?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface SchemaObject<Schema = any, Functions = any> {
|
|
47
|
+
data: () => Schema;
|
|
48
|
+
moduleId: string;
|
|
49
|
+
functions: {
|
|
50
|
+
[key in keyof Functions]: (
|
|
51
|
+
//Functions[key] &
|
|
52
|
+
// NOTE: Functions don't have results, if you want a result write to the database
|
|
53
|
+
// and then watch it for the result.
|
|
54
|
+
// - The promise resolves once the function has started the commit process, NOT when
|
|
55
|
+
// it has finished. Once it resolves you can terminate your process and the function
|
|
56
|
+
// will run (but it might still error out due to taking too long to reach the server,
|
|
57
|
+
// or too long to run, or insufficient privileges, etc, etc).
|
|
58
|
+
((...args: Args<Functions[key]>) => CallSpec) & {
|
|
59
|
+
// Add a "debugger;" when the holding function is called, to make it easy to debug the function
|
|
60
|
+
debug(): void;
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
};
|
|
64
|
+
functionMetadata: {
|
|
65
|
+
[key in keyof Functions]?: FunctionMetadata<Functions[key]> | undefined;
|
|
66
|
+
};
|
|
67
|
+
rawFunctions: Functions;
|
|
68
|
+
permissions: SchemaToPermissions<Schema> | undefined;
|
|
69
|
+
permissionsNonWildcards: Map<string, PermissionsCallback>;
|
|
70
|
+
// Sorted by most nested first
|
|
71
|
+
permissionsWildcards: {
|
|
72
|
+
pathParts: string[];
|
|
73
|
+
callback: PermissionsCallback;
|
|
74
|
+
}[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let existsCached = cache((path: string) => {
|
|
78
|
+
return fs.existsSync(path);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
export function getModuleRelativePath(module: NodeJS.Module): string {
|
|
83
|
+
let root = path.resolve(".").replaceAll("\\", "/");
|
|
84
|
+
if (!existsCached(root + "/package.json")) {
|
|
85
|
+
throw new Error(`Expected to find package.json at ${root}. The current directory must be the root of the project when deploying, so we can resolve relative file references.`);
|
|
86
|
+
}
|
|
87
|
+
// Get the relative path
|
|
88
|
+
let relativePath = path.relative(root, module.filename).replaceAll("\\", "/");
|
|
89
|
+
if (relativePath.startsWith("../")) {
|
|
90
|
+
let otherFolder = relativePath.split("/")[1];
|
|
91
|
+
if (existsCached("node_modules/" + otherFolder)) {
|
|
92
|
+
relativePath = "node_modules/" + otherFolder + "/" + relativePath.split("/").slice(2).join("/");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return "/" + relativePath;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type PermissionsParameters = {
|
|
99
|
+
// Each part of the path that matched with a wildcard ("*") is added to this array (in order).
|
|
100
|
+
pathWildcards: string[];
|
|
101
|
+
/**
|
|
102
|
+
* The path the permissions check matched.
|
|
103
|
+
* NOTE: The full path matched is not provided for optimization purposes. Otherwise for large
|
|
104
|
+
* writes the permissions check would be needlessly run. IF further depth is required,
|
|
105
|
+
* use "*" wildcards. If the depth is dynamic... your structure is probably too complex,
|
|
106
|
+
* or something similar to a tree. Destructure it to a list, and all of your
|
|
107
|
+
* code will run significantly faster.
|
|
108
|
+
*
|
|
109
|
+
* TODO: If we ABSOLUTELY need it, we COULD conditionally expose fullPath, and just disable
|
|
110
|
+
* optimizations when we do. We could even have a parameter which replaces matchedPath with
|
|
111
|
+
* fullPath. But, ideally we don't, as it is just very inefficient...
|
|
112
|
+
* NOTE: Usually pathWildcards should be used instead. This is only useful if you want to create
|
|
113
|
+
* a very generic permission check which can be used in many hierarchies, and depends on the
|
|
114
|
+
* hierarchy (ex, if it checks path.slice(0, 3).users for the users table).
|
|
115
|
+
**/
|
|
116
|
+
matchedPath: string;
|
|
117
|
+
/** The callerMachineId will be unique to the calling machine (but a machine may have multiple,
|
|
118
|
+
ex, 1 per browser, another a fresh one if they are in incognito mode).
|
|
119
|
+
- To tie machineIds to userIds simple create a lookup in your database, for example:
|
|
120
|
+
`{ machineToUserId: { [machineId: string]: string | undefined } }`
|
|
121
|
+
Logging out from a machine is simply:
|
|
122
|
+
`machineToUserId[machineId] = undefined`
|
|
123
|
+
A reverse index may also be useful, to efficiently log out everywhere (or just
|
|
124
|
+
show all machines that a user is logged into)
|
|
125
|
+
```
|
|
126
|
+
{ userIdToMachineIds: { [userId: string]: { [machineId: string]: 1 } } }
|
|
127
|
+
funcion logoutEverywhere(userId: string) {
|
|
128
|
+
for (let machineId in userIdToMachineIds[userId]) {
|
|
129
|
+
userIdToMachineIds[userId][machineId] = undefined;
|
|
130
|
+
machineToUserId[machineId] = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
* */
|
|
135
|
+
callerMachineId: string;
|
|
136
|
+
};
|
|
137
|
+
/** A false of false will deny read permissions, resulting in all reads being given value with a value
|
|
138
|
+
* of undefined, and a time of 0.
|
|
139
|
+
* NOTE: Permissions checks at the root (permissions: { READ_PERMISSIONS(config) { } }) will
|
|
140
|
+
* be used to verify calls are allowed as well, even if the call doesn't read or write any data
|
|
141
|
+
* that is under a permissions check.
|
|
142
|
+
*/
|
|
143
|
+
export type PermissionsCallback = (config: PermissionsParameters) => PermissionsCheckResult;
|
|
144
|
+
export type PermissionsCheckResult = boolean | { allowed: boolean; skipParentChecks?: boolean; };
|
|
145
|
+
/*
|
|
146
|
+
NOTE: All ancestor permissions checks are applied as well.
|
|
147
|
+
|
|
148
|
+
IMPORTANT! Wildcards are tested via the "" path. This means if the "" key is provided access to a user, they can run
|
|
149
|
+
Object.keys()/Object.values() on the data. In order to not accidentally provide list access, never allow "" to be
|
|
150
|
+
a valid path, by never providing access by exclusion. For example:
|
|
151
|
+
BROKEN: `{ ["*"]: { PERMISSIONS(){ return config.pathWildcards?.[0] !== "admin" || users()[config.callerId].isAdmin; } } }`
|
|
152
|
+
- This will allow users to access the "admin" data via Object.values(), because the "" !== "admin"
|
|
153
|
+
BROKEN: `{ admin: { PERMISSIONS(){ return users()[config.callerId].isAdmin; } } }`
|
|
154
|
+
- Because "" isn't checked
|
|
155
|
+
CORRECT: `{ ["*"]: { PERMISSIONS(){ return config.pathWildcards?.[0] === config.callerId; } } }`
|
|
156
|
+
CORRECT: `{ ["*"]: { PERMISSIONS(){ return users()[config.callerId].isAdmin || config.pathWildcards?.[0] === config.callerId; } } }`
|
|
157
|
+
|
|
158
|
+
NOTE: Permission checks lag a bit behind data. This means not only will a write that removes checks of itself
|
|
159
|
+
still be sent (ex, if a user removes their admin access, and only admins can see who is an admin or not,
|
|
160
|
+
they will be told about their admin change). BUT, a user will receive writes for a bit after that as well!
|
|
161
|
+
The delay is equal to the network propagation time between two server, which is small (ideally milliseconds),
|
|
162
|
+
but in the case of excess lag could easily be 10s of seconds (in which case everything on the site would
|
|
163
|
+
be lagging by 10s of seconds as well).
|
|
164
|
+
|
|
165
|
+
For example
|
|
166
|
+
permissions: {
|
|
167
|
+
userSecretData: {
|
|
168
|
+
["*"]: {
|
|
169
|
+
PERMISSIONS(pathWildcards) {
|
|
170
|
+
return config.pathWildcards[0] === config.callerMachineId;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
*/
|
|
176
|
+
export type SchemaToPermissions<Schema> = {
|
|
177
|
+
[key in keyof Schema]?: SchemaToPermissions<Schema[key]>;
|
|
178
|
+
} & {
|
|
179
|
+
PERMISSIONS?: PermissionsCallback;
|
|
180
|
+
} | {
|
|
181
|
+
PERMISSIONS?: PermissionsCallback;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** A special key which is prefixed (along with a random value) before special keys used to check child
|
|
185
|
+
* permissions. This allows us to explicitly disallow parent syncing (ex Object.keys()) of values.
|
|
186
|
+
*/
|
|
187
|
+
export const CHILD_CHECK_PREFIX = "";
|
|
188
|
+
|
|
189
|
+
export function hasWildcardMatch(permissionsPath: string[], valuePathParts: string[]): boolean {
|
|
190
|
+
for (let i = 0; i < permissionsPath.length; i++) {
|
|
191
|
+
let part = permissionsPath[i];
|
|
192
|
+
if (part === "*") {
|
|
193
|
+
|
|
194
|
+
} else if (part !== valuePathParts[i]) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
export function getWildcardMatches(permissionsPath: string[], valuePathParts: string[]): string[] | undefined {
|
|
201
|
+
if (permissionsPath.length > valuePathParts.length) return undefined;
|
|
202
|
+
let wildcardMatches: string[] | undefined;
|
|
203
|
+
for (let i = 0; i < permissionsPath.length; i++) {
|
|
204
|
+
let part = permissionsPath[i];
|
|
205
|
+
if (part === "*") {
|
|
206
|
+
if (!wildcardMatches) wildcardMatches = [];
|
|
207
|
+
wildcardMatches.push(valuePathParts[i]);
|
|
208
|
+
} else if (part !== valuePathParts[i]) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return wildcardMatches;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let inliningCalls = false;
|
|
216
|
+
export function inlineNestedCalls<T>(code: () => T) {
|
|
217
|
+
let prev = inliningCalls;
|
|
218
|
+
inliningCalls = true;
|
|
219
|
+
try {
|
|
220
|
+
return code();
|
|
221
|
+
} finally {
|
|
222
|
+
inliningCalls = prev;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
type SyncSchemaResult<Schema> = {
|
|
227
|
+
// NOTE: The functions are over the network, AND, eventually consistent, so it is not possible
|
|
228
|
+
// for them to return a result. If a result is required, pass an id which will instruct them where
|
|
229
|
+
// to write their result to, and then watch that location.
|
|
230
|
+
<Functions extends { [name: string]: ((...args: any) => void) }>(config: {
|
|
231
|
+
domainName?: string;
|
|
232
|
+
/** NOTE: moduleId is used to store your data in a unique location, that doesn't change
|
|
233
|
+
* if you move this file around. Any unique identifier is allowed, as the deploy command
|
|
234
|
+
* will warn you if you accidentally reused a moduleId.
|
|
235
|
+
*/
|
|
236
|
+
moduleId: string;
|
|
237
|
+
module: NodeJS.Module;
|
|
238
|
+
functions: Functions;
|
|
239
|
+
functionMetadata?: { [name in keyof Functions]?: FunctionMetadata<Functions[name]> };
|
|
240
|
+
/** Provide access to [Querysub.CALL_PERMISSIONS_KEY] to allow function calls (or provide access at the root). */
|
|
241
|
+
permissions?: SchemaToPermissions<Schema> & {
|
|
242
|
+
[CALL_PERMISSIONS_KEY]?: {
|
|
243
|
+
PERMISSIONS: PermissionsCallback;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
}): SchemaObject<Schema, Functions>;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export function syncSchema<Schema>(): SyncSchemaResult<Schema>;
|
|
250
|
+
export function syncSchema<SchemaDef extends Schema2>(schema: SchemaDef): SyncSchemaResult<Schema2T<SchemaDef>>;
|
|
251
|
+
export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
252
|
+
return function getFunctions(config) {
|
|
253
|
+
const { moduleId, functions, module, permissions } = config;
|
|
254
|
+
const domainName = config.domainName ?? getDomain();
|
|
255
|
+
|
|
256
|
+
const data = () => functionSchema()[domainName].PathFunctionRunner[moduleId].Data;
|
|
257
|
+
|
|
258
|
+
let fncs = { ...functions };
|
|
259
|
+
|
|
260
|
+
let undoChecksAdded: {
|
|
261
|
+
name: string;
|
|
262
|
+
path: SchemaPath;
|
|
263
|
+
}[] = [];
|
|
264
|
+
if (schema) {
|
|
265
|
+
let dataPrefixPath = getPathFromStr(getProxyPath(data));
|
|
266
|
+
registerSchema({ domainName, moduleId, schema });
|
|
267
|
+
|
|
268
|
+
let gcDefs = Schema2Fncs.getGCObjects(schema);
|
|
269
|
+
for (let { path, gcDelay } of gcDefs) {
|
|
270
|
+
createObjectAliveChecker(data, path, gcDelay);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let undoChecks = Schema2Fncs.getAllUndoChecks(schema);
|
|
274
|
+
for (let checkObj of undoChecks) {
|
|
275
|
+
// Need at least one check
|
|
276
|
+
if (checkObj.undoChecks.length === 0) continue;
|
|
277
|
+
function niceSchemaPath(path: SchemaPath) {
|
|
278
|
+
return path.map(x => x === Schema2Fncs.WildCard ? "*" : x).join(".");
|
|
279
|
+
}
|
|
280
|
+
let name = `__undoer__${niceSchemaPath(checkObj.path)}`;
|
|
281
|
+
let fullPath = (dataPrefixPath as SchemaPath).concat(checkObj.path);
|
|
282
|
+
undoChecksAdded.push({
|
|
283
|
+
name,
|
|
284
|
+
path: fullPath,
|
|
285
|
+
});
|
|
286
|
+
(fncs as any)[name] = function undoCallback(path: string[], value: unknown) {
|
|
287
|
+
if (!isChildPath(fullPath, path)) {
|
|
288
|
+
throw new Error(`Tried to access undo function with path not under undo spec ${niceSchemaPath(fullPath)} . Tried to use path ${path.join(".")}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let permissionsChecker = proxyWatcher.getTriggeredWatcher().permissionsChecker;
|
|
292
|
+
if (permissionsChecker) {
|
|
293
|
+
let permissionsCheck = permissionsChecker.checkPermissions(getPathStr(path));
|
|
294
|
+
if (!permissionsCheck?.allowed) {
|
|
295
|
+
throw new Error(`Failed read permission check for undo call at path ${path.join(".")}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (let check of checkObj.undoChecks) {
|
|
299
|
+
let nestedPath = path.slice(0, fullPath.length);
|
|
300
|
+
let key = nestedPath[nestedPath.length - 1];
|
|
301
|
+
if (!atomic(check(key, nestedPath))) {
|
|
302
|
+
throw new Error(`Failed undo permission check at path ${nestedPath.join(".")}, check ${check.name || check.toString().slice(0, 100)}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
let parent = rawSchema()() as any;
|
|
306
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
307
|
+
parent = parent[path[i]];
|
|
308
|
+
}
|
|
309
|
+
let key = path[path.length - 1];
|
|
310
|
+
|
|
311
|
+
// NOTE: This isn't an atomic set, because we leave it up proxyWatcher (and schema2)
|
|
312
|
+
// to correctly handle assigning the values atomically.
|
|
313
|
+
parent[key] = value;
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let wrappedFunctions: { [name: string]: (...args: any) => CallSpec } = Object.create(null);
|
|
319
|
+
let functionMetadata = config.functionMetadata;
|
|
320
|
+
for (let [name, fnc] of Object.entries(fncs)) {
|
|
321
|
+
let metadata = functionMetadata?.[name] ?? {};
|
|
322
|
+
let debugging = false;
|
|
323
|
+
wrappedFunctions[name] = Object.assign((...args: any[]): any => {
|
|
324
|
+
if (inliningCalls) {
|
|
325
|
+
return fnc(...args);
|
|
326
|
+
}
|
|
327
|
+
if (debugging) {
|
|
328
|
+
debugger;
|
|
329
|
+
}
|
|
330
|
+
return writeFunctionCall({
|
|
331
|
+
domainName,
|
|
332
|
+
moduleId,
|
|
333
|
+
functionId: name,
|
|
334
|
+
args,
|
|
335
|
+
metadata,
|
|
336
|
+
});
|
|
337
|
+
}, {
|
|
338
|
+
debug() {
|
|
339
|
+
debugging = true;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (let check of undoChecksAdded) {
|
|
345
|
+
registeredUndoChecks.push({
|
|
346
|
+
path: check.path,
|
|
347
|
+
undoSet: wrappedFunctions[check.name] as any,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let permissionsNonWildcards = new Map<string, PermissionsCallback>();
|
|
352
|
+
let permissionsWildcards: { pathParts: string[]; callback: PermissionsCallback }[] = [];
|
|
353
|
+
if (permissions) {
|
|
354
|
+
addPermissions(rootPathStr, permissions);
|
|
355
|
+
}
|
|
356
|
+
function addPermissions(path: string, permissions: SchemaToPermissions<Schema> | undefined) {
|
|
357
|
+
if (!permissions) return;
|
|
358
|
+
for (let key of getStringKeys(permissions)) {
|
|
359
|
+
if (key === "PERMISSIONS") {
|
|
360
|
+
let value = permissions.PERMISSIONS;
|
|
361
|
+
if (value) {
|
|
362
|
+
if (path.includes("*")) {
|
|
363
|
+
permissionsWildcards.push({
|
|
364
|
+
pathParts: getPathFromStr(path),
|
|
365
|
+
callback: value,
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
permissionsNonWildcards.set(path, value);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
addPermissions(appendToPathStr(path, key), permissions[key]);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Sort reversed, so the most specific checks are first
|
|
377
|
+
permissionsWildcards.sort((b, a) => a.pathParts.length - b.pathParts.length);
|
|
378
|
+
|
|
379
|
+
let result: SchemaObject = {
|
|
380
|
+
data,
|
|
381
|
+
moduleId,
|
|
382
|
+
functions: wrappedFunctions as any,
|
|
383
|
+
functionMetadata: functionMetadata as any,
|
|
384
|
+
rawFunctions: fncs,
|
|
385
|
+
permissions,
|
|
386
|
+
permissionsNonWildcards,
|
|
387
|
+
permissionsWildcards,
|
|
388
|
+
};
|
|
389
|
+
if (!isHotReloading() && module.exports[SCHEMA_EXPORT_KEY]) {
|
|
390
|
+
throw new Error(`Module ${moduleId} already has a schema. Only 1 schema is allowed per module.`);
|
|
391
|
+
}
|
|
392
|
+
// NOTE: We clobber the previous value, as we might just be hotreloading, which
|
|
393
|
+
// could mean the exports value has our previous value.
|
|
394
|
+
module.exports[SCHEMA_EXPORT_KEY] = result;
|
|
395
|
+
return result;
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function isChildPath(spec: SchemaPath, path: string[]): boolean {
|
|
400
|
+
if (spec.length > path.length) return false;
|
|
401
|
+
for (let i = 0; i < spec.length; i++) {
|
|
402
|
+
if (spec[i] === Schema2Fncs.WildCard) continue;
|
|
403
|
+
if (spec[i] !== path[i]) return false;
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// pathStr => ...
|
|
409
|
+
let registeredUndoChecks: {
|
|
410
|
+
path: SchemaPath;
|
|
411
|
+
undoSet: (nestedPath: string[], value: unknown) => void;
|
|
412
|
+
}[] = [];
|
|
413
|
+
|
|
414
|
+
export function registerUndoCallback(path: SchemaPath, undoSet: (nestedPath: string[], value: unknown) => void) {
|
|
415
|
+
registeredUndoChecks.push({ path, undoSet });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Throws if there is no undo callback available at this path. */
|
|
419
|
+
export function getUndoCallback(path: string[]): {
|
|
420
|
+
setValue(path: string[], value: unknown): void;
|
|
421
|
+
} {
|
|
422
|
+
// Local undos are always allowed
|
|
423
|
+
if (path[0] === LOCAL_DOMAIN) {
|
|
424
|
+
return {
|
|
425
|
+
setValue(path, value) {
|
|
426
|
+
if (path[0] !== LOCAL_DOMAIN) throw new Error(`Cannot set non-local value with local undo function. Tried to use path ${path.join(".")}`);
|
|
427
|
+
let parent = rawSchema()() as any;
|
|
428
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
429
|
+
parent = parent[path[i]];
|
|
430
|
+
}
|
|
431
|
+
let key = path[path.length - 1];
|
|
432
|
+
parent[key] = value;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
// Any callback should be fine?
|
|
437
|
+
let undoCallbacks = registeredUndoChecks.filter(x => isChildPath(x.path, path));
|
|
438
|
+
sort(undoCallbacks, x => -x.path.length);
|
|
439
|
+
if (undoCallbacks.length === 0) {
|
|
440
|
+
throw new Error(`No undo callback available at path ${path.join(".")}. There needs to be an undo region at or above this path, via t.undoRegion`);
|
|
441
|
+
}
|
|
442
|
+
return { setValue: undoCallbacks[0].undoSet };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function createObjectAliveChecker(data: () => any, path: SchemaPath, delay: number | undefined) {
|
|
446
|
+
registerAliveChecker({
|
|
447
|
+
object(wildcard) {
|
|
448
|
+
let obj = data();
|
|
449
|
+
for (let part of path) {
|
|
450
|
+
if (part === Schema2Fncs.WildCard) {
|
|
451
|
+
obj = obj[wildcard];
|
|
452
|
+
} else {
|
|
453
|
+
obj = obj[part];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return obj;
|
|
457
|
+
},
|
|
458
|
+
isAlive(value, path) {
|
|
459
|
+
let parent = rawSchema()() as any;
|
|
460
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
461
|
+
parent = parent[path[i]];
|
|
462
|
+
}
|
|
463
|
+
let key = path[path.length - 1];
|
|
464
|
+
let isAlive = key in parent;
|
|
465
|
+
return isAlive;
|
|
466
|
+
},
|
|
467
|
+
deleteDelayTime: delay,
|
|
468
|
+
// NOTE: This is actually safe, as we only read a single value, and are really
|
|
469
|
+
// just looking to see if ANYTHING has created the value. Without this there is
|
|
470
|
+
// a large gap when the value could be deleted, then undeleted, and then
|
|
471
|
+
// as we only read old values we might not see the undelete. WITH this the gap
|
|
472
|
+
// is much smaller (but still there).
|
|
473
|
+
unsafeLiveReads: true,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { delay } from "socket-function/src/batching";
|
|
3
|
+
import { magenta, red } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { measureCode } from "socket-function/src/profiling/measure";
|
|
5
|
+
import { getThreadKeyCert } from "../../-a-auth/certs";
|
|
6
|
+
import { logErrors } from "../../errors";
|
|
7
|
+
import { getPathStr1 } from "../../path";
|
|
8
|
+
import { proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
|
|
9
|
+
import { pathValueCommitter } from "../../0-path-value-core/PathValueController";
|
|
10
|
+
import { quietCoreMode } from "../../0-path-value-core/pathValueCore";
|
|
11
|
+
import { PathFunctionRunner } from "../PathFunctionRunner";
|
|
12
|
+
import * as FunctionRunner from "../PathFunctionRunner";
|
|
13
|
+
import * as test from "../../../../shard-app/src/data";
|
|
14
|
+
import { deployFunction } from "../PathFunctionHelpers";
|
|
15
|
+
|
|
16
|
+
// async function testCallFunctions() {
|
|
17
|
+
// //ActionsHistory.LOG_ACTION_HISTORY = "caller";
|
|
18
|
+
|
|
19
|
+
// quietCoreMode();
|
|
20
|
+
// // ClientWatcher.DEBUG_READS = true;
|
|
21
|
+
// // ClientWatcher.DEBUG_WRITES = true;
|
|
22
|
+
// //PathValueProxyWatcher.DEBUG = true;
|
|
23
|
+
// //PathFunctionRunner.DEBUG_CALLS = true;
|
|
24
|
+
|
|
25
|
+
// await SocketFunction.mount({ port: 0, ...await getThreadKeyCert() });
|
|
26
|
+
|
|
27
|
+
// let count = 1000 * 1000;
|
|
28
|
+
// let writesBetweenDelays = 500;
|
|
29
|
+
// let delayTime = 1000;
|
|
30
|
+
// // writesBetweenDelays = 10;
|
|
31
|
+
// //count = 1;
|
|
32
|
+
// //count = 0;
|
|
33
|
+
// // 0 + 30 * 4 = 120
|
|
34
|
+
// //count = 1000 * 100;
|
|
35
|
+
// //count = 1000 * 10;
|
|
36
|
+
// //count = 1000;
|
|
37
|
+
// //count = 100;
|
|
38
|
+
// count = 1;
|
|
39
|
+
|
|
40
|
+
// await delay(1000);
|
|
41
|
+
|
|
42
|
+
// await measureCode(async function testCallFunctions() {
|
|
43
|
+
|
|
44
|
+
// const moduleId = "880d9f63-232a-4df7-86b8-eeb711ac3e48";
|
|
45
|
+
// const functionId = "80d87119-68fc-4440-87d9-fcbab00bec22";
|
|
46
|
+
// // Deploy the function
|
|
47
|
+
// await deployFunction({
|
|
48
|
+
// domainName: "querysub.com",
|
|
49
|
+
// moduleId,
|
|
50
|
+
// functionId,
|
|
51
|
+
// filePath: "/test.ts",
|
|
52
|
+
// exportPathStr: getPathStr1("testWrite"),
|
|
53
|
+
// gitRef: "main",
|
|
54
|
+
// gitURL: "https://github.com/sliftist/shard-app.git",
|
|
55
|
+
// });
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// let lastValue = await proxyWatcher.commitFunction({
|
|
59
|
+
// debugName: "readValue",
|
|
60
|
+
// watchFunction() {
|
|
61
|
+
// return +test.schema()["querysub.com"].testData.test;
|
|
62
|
+
// },
|
|
63
|
+
// });
|
|
64
|
+
// let endValue = lastValue + count;
|
|
65
|
+
|
|
66
|
+
// console.log(magenta(`Start at ${lastValue}`));
|
|
67
|
+
|
|
68
|
+
// let triggerReaderCount = 0;
|
|
69
|
+
// // Watch for updates
|
|
70
|
+
// proxyWatcher.createWatcher({
|
|
71
|
+
// watchFunction: function reader() {
|
|
72
|
+
// const testData = test.schema()["querysub.com"].testData;
|
|
73
|
+
// let value = testData.test;
|
|
74
|
+
// lastValue = value as number;
|
|
75
|
+
// triggerReaderCount++;
|
|
76
|
+
// }
|
|
77
|
+
// });
|
|
78
|
+
|
|
79
|
+
// // Write function calls
|
|
80
|
+
// for (let i = 0; i < count; i++) {
|
|
81
|
+
// FunctionRunner.writeFunctionCall({
|
|
82
|
+
// domainName: "querysub.com",
|
|
83
|
+
// moduleId,
|
|
84
|
+
// functionId,
|
|
85
|
+
// args: ["test"],
|
|
86
|
+
// });
|
|
87
|
+
// if (i % (writesBetweenDelays + 1) === 0) {
|
|
88
|
+
// // Wait a while, so FunctionRunner has a change to actually run the calls.
|
|
89
|
+
// console.log(`Delay to give functions time to run (at ${lastValue})`);
|
|
90
|
+
// await delay(delayTime);
|
|
91
|
+
// console.log(`(at ${lastValue})`);
|
|
92
|
+
// }
|
|
93
|
+
// }
|
|
94
|
+
|
|
95
|
+
// await pathValueCommitter.waitForValuesToCommit();
|
|
96
|
+
// await delay(1000 * 2);
|
|
97
|
+
|
|
98
|
+
// while (!(lastValue >= endValue)) {
|
|
99
|
+
// console.log(`Waiting for ${endValue}, value is ${lastValue} (${endValue - lastValue} to go)`);
|
|
100
|
+
// await delay(1000);
|
|
101
|
+
// }
|
|
102
|
+
|
|
103
|
+
// console.log(magenta(`Finished at ${endValue}`));
|
|
104
|
+
// });
|
|
105
|
+
// console.log(`PathFunctionRunner.RUN_FINISH_COUNT = ${PathFunctionRunner.RUN_FINISH_COUNT}`);
|
|
106
|
+
|
|
107
|
+
// /*
|
|
108
|
+
// // NOTE: Temporary, to ensure we aren't leaking memory
|
|
109
|
+
// console.log(`Wait for memory to clear`);
|
|
110
|
+
// await delay(MAX_CHANGE_AGE);
|
|
111
|
+
// await delay(MAX_CHANGE_AGE);
|
|
112
|
+
// global.gc?.();
|
|
113
|
+
// console.log(`Done waiting memory to clear`);
|
|
114
|
+
// await writeHeapSnapshot(getSubFolder("heapdumps"));
|
|
115
|
+
// console.log(`Done post heap dump`);
|
|
116
|
+
// */
|
|
117
|
+
|
|
118
|
+
// process.exit();
|
|
119
|
+
// }
|
|
120
|
+
// if (process.argv.includes("--caller")) {
|
|
121
|
+
// logErrors(testCallFunctions());
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
// async function watch() {
|
|
125
|
+
// await SocketFunction.mount({ port: 0, ...await getThreadKeyCert() });
|
|
126
|
+
// proxyWatcher.createWatcher({
|
|
127
|
+
// watchFunction: function reader() {
|
|
128
|
+
// const testData = test.schema()["querysub.com"].testData;
|
|
129
|
+
// console.log(red(`testData.test = ${testData.test}`));
|
|
130
|
+
// }
|
|
131
|
+
// });
|
|
132
|
+
// }
|
|
133
|
+
// if (process.argv.includes("--watch")) {
|
|
134
|
+
// logErrors(watch());
|
|
135
|
+
// }
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { delay } from "socket-function/src/batching";
|
|
3
|
+
import { getThreadKeyCert } from "../../-a-auth/certs";
|
|
4
|
+
import { ActionsHistory } from "../../diagnostics/ActionsHistory";
|
|
5
|
+
import { errorToUndefined } from "../../errors";
|
|
6
|
+
import { rawSchema } from "../../2-proxy/pathDatabaseProxyBase";
|
|
7
|
+
import { ClientWatcher, clientWatcher } from "../../1-path-client/pathValueClientWatcher";
|
|
8
|
+
import { PathValueProxyWatcher, proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
|
|
9
|
+
import { pathValueCommitter } from "../../0-path-value-core/PathValueController";
|
|
10
|
+
import { getNextTime } from "../../0-path-value-core/pathValueCore";
|
|
11
|
+
import { PathFunctionRunner } from "../PathFunctionRunner";
|
|
12
|
+
|
|
13
|
+
export const schema = rawSchema<{
|
|
14
|
+
["querysub.com"]: {
|
|
15
|
+
rejectTest: {
|
|
16
|
+
value: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
async function reader() {
|
|
22
|
+
await proxyWatcher.createWatcher({
|
|
23
|
+
debugName: "reader",
|
|
24
|
+
watchFunction() {
|
|
25
|
+
console.log(`Reader sees ${JSON.stringify(schema()["querysub.com"].rejectTest.value)}`);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function badWriter() {
|
|
30
|
+
let time = getNextTime();
|
|
31
|
+
console.log(`Saving time`);
|
|
32
|
+
await delay(10 * 1000);
|
|
33
|
+
console.log(`Applying write`);
|
|
34
|
+
await proxyWatcher.commitFunction({
|
|
35
|
+
debugName: "badWriter",
|
|
36
|
+
runAtTime: time,
|
|
37
|
+
canWrite: true,
|
|
38
|
+
watchFunction() {
|
|
39
|
+
schema()["querysub.com"].rejectTest.value += 2;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function writer() {
|
|
44
|
+
await proxyWatcher.commitFunction({
|
|
45
|
+
debugName: "badWriter",
|
|
46
|
+
canWrite: true,
|
|
47
|
+
watchFunction() {
|
|
48
|
+
schema()["querysub.com"].rejectTest.value++;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
ClientWatcher.DEBUG_READS = true;
|
|
55
|
+
ClientWatcher.DEBUG_WRITES = true;
|
|
56
|
+
PathValueProxyWatcher.TRACE = true;
|
|
57
|
+
PathFunctionRunner.DEBUG_CALLS = true;
|
|
58
|
+
|
|
59
|
+
await SocketFunction.mount({ port: 0, ...await getThreadKeyCert() });
|
|
60
|
+
if (process.argv.includes("--reader")) {
|
|
61
|
+
ActionsHistory.LOG_ACTION_HISTORY = "reader";
|
|
62
|
+
await reader();
|
|
63
|
+
await new Promise(() => { });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (process.argv.includes("--bad")) {
|
|
67
|
+
ActionsHistory.LOG_ACTION_HISTORY = "bad";
|
|
68
|
+
await badWriter();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
ActionsHistory.LOG_ACTION_HISTORY = "writer";
|
|
72
|
+
await writer();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
errorToUndefined(main())
|
|
76
|
+
.finally(() => pathValueCommitter.waitForValuesToCommit())
|
|
77
|
+
.finally(() => process.exit());
|