querysub 0.2.0 → 0.4.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/package.json +2 -2
- package/src/-a-archives/archiveCache.ts +4 -11
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +4 -0
- package/src/3-path-functions/deployMain.ts +28 -25
- package/src/3-path-functions/pathFunctionLoader.ts +3 -2
- package/src/3-path-functions/syncSchema.ts +4 -3
- package/src/4-querysub/querysubPrediction.ts +7 -7
- package/src/diagnostics/managementPages.tsx +31 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
25
25
|
"pako": "^2.1.0",
|
|
26
26
|
"preact": "^10.11.3",
|
|
27
|
-
"socket-function": "^0.
|
|
27
|
+
"socket-function": "^0.32.0",
|
|
28
28
|
"terser": "^5.31.0",
|
|
29
29
|
"typesafecss": "^0.6.3",
|
|
30
30
|
"yaml": "^2.5.0",
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { getStorageDir, getSubFolder } from "../fs";
|
|
2
2
|
import { Archives } from "./archives";
|
|
3
|
-
import { diskEscapeFileName, getArchivesLocal } from "./archivesDisk";
|
|
4
3
|
import fs from "fs";
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
import { binarySearchBasic, binarySearchIndex, list, nextId, sort, timeInHour, timeInMinute } from "socket-function/src/misc";
|
|
5
|
+
import { list, nextId, timeInHour, timeInMinute } from "socket-function/src/misc";
|
|
8
6
|
import { cache, lazy } from "socket-function/src/caching";
|
|
9
|
-
import {
|
|
7
|
+
import { runInSerial, runInfinitePoll } from "socket-function/src/batching";
|
|
10
8
|
import { sha256 } from "js-sha256";
|
|
11
|
-
import { devDebugbreak } from "../config";
|
|
12
9
|
import child_process from "child_process";
|
|
13
|
-
import os from "os";
|
|
14
|
-
import fsext, { flock } from "fs-ext";
|
|
15
|
-
import debugbreak from "debugbreak";
|
|
16
|
-
import readline from "readline";
|
|
17
10
|
import { getPPID } from "../misc/getParentProcessId";
|
|
18
11
|
import { Args } from "socket-function/src/types";
|
|
19
12
|
import { getArchivesBackblaze } from "./archivesBackBlaze";
|
|
20
|
-
import {
|
|
21
|
-
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
13
|
+
import { formatNumber } from "socket-function/src/formatting/format";
|
|
22
14
|
import { SizeLimiter } from "../diagnostics/SizeLimiter";
|
|
23
15
|
|
|
24
16
|
const SIZE_LIMIT = new SizeLimiter({
|
|
@@ -702,6 +694,7 @@ async function isLocked(path: string, operation?: {
|
|
|
702
694
|
return await lockRetryLoop(async function isLocked() {
|
|
703
695
|
let handle = await getHandle(lockPath);
|
|
704
696
|
try {
|
|
697
|
+
const { flock } = await import("fs-ext");
|
|
705
698
|
await new Promise<void>((r, e) => flock(handle.fd, "ex", err => err ? e(err) : r()));
|
|
706
699
|
let contents = (await handle.readFile()).toString();
|
|
707
700
|
let locks = contents.toString().replaceAll("\0", "").trim().split("\n").filter(x => x);
|
|
@@ -4,6 +4,8 @@ import { getAllNodeIds, getOwnThreadId } from "../../-f-node-discovery/NodeDisco
|
|
|
4
4
|
import { pathValueArchives } from "../pathValueArchives";
|
|
5
5
|
import { ignoreErrors, logErrors, timeoutToUndefinedSilent } from "../../errors";
|
|
6
6
|
import { green, magenta } from "socket-function/src/formatting/logColors";
|
|
7
|
+
import { devDebugbreak } from "../../config";
|
|
8
|
+
import { sort } from "socket-function/src/misc";
|
|
7
9
|
|
|
8
10
|
const snapshots = getArchives("snapshots");
|
|
9
11
|
|
|
@@ -94,6 +96,8 @@ export async function getSnapshotList(): Promise<ArchiveSnapshotOverview[]> {
|
|
|
94
96
|
|
|
95
97
|
overview.push(...snapshotFiles.map(file => fileNameToOverview(file)));
|
|
96
98
|
|
|
99
|
+
sort(overview, x => -x.time);
|
|
100
|
+
|
|
97
101
|
return overview;
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -8,7 +8,7 @@ import { getModuleRelativePath } from "./syncSchema";
|
|
|
8
8
|
import * as child_process from "child_process";
|
|
9
9
|
import { proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
10
10
|
import { green, red } from "socket-function/src/formatting/logColors";
|
|
11
|
-
import { isDefined } from "../misc";
|
|
11
|
+
import { isDefined, isEmpty } from "../misc";
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
14
14
|
import { getThreadKeyCert } from "../-a-auth/certs";
|
|
@@ -23,6 +23,7 @@ import debugbreak from "debugbreak";
|
|
|
23
23
|
import yargs from "yargs";
|
|
24
24
|
import { isPublic } from "../config";
|
|
25
25
|
import { deployBlock } from "./deployBlock";
|
|
26
|
+
import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
|
|
26
27
|
let yargObj = yargs(process.argv)
|
|
27
28
|
.option("domain", { type: "string", required: true, desc: "Domain to deploy to" })
|
|
28
29
|
.option("nogit", { type: "boolean", default: false, desc: `Disable git operations. Useful for debugging, when using --local` })
|
|
@@ -87,31 +88,33 @@ export async function deployMain() {
|
|
|
87
88
|
|
|
88
89
|
if (!isAllowedToDeploy) continue;
|
|
89
90
|
let schema = getSchemaObject(module);
|
|
91
|
+
if (!schema) continue;
|
|
92
|
+
// If it's local, we don't need to deploy it.
|
|
93
|
+
// (And shouldn't?, as we don't want to run local functions by request?)
|
|
94
|
+
if (schema.domainName === LOCAL_DOMAIN) continue;
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
}
|
|
96
|
+
let filePath = getModuleRelativePath(module);
|
|
97
|
+
|
|
98
|
+
let moduleId = getPathStr2(domainName, filePath);
|
|
99
|
+
let otherUsed = usedModules.get(moduleId);
|
|
100
|
+
if (otherUsed) {
|
|
101
|
+
throw new Error(`Overlapping module ids for ${domainName}.${filePath}, at ${otherUsed} and ${filePath}`);
|
|
102
|
+
}
|
|
103
|
+
usedModules.set(moduleId, filePath);
|
|
104
|
+
|
|
105
|
+
const functionIds = Object.keys(schema.rawFunctions);
|
|
106
|
+
functionIds.push(PERMISSIONS_FUNCTION_ID);
|
|
107
|
+
for (let functionId of functionIds) {
|
|
108
|
+
const exportPathStr = getExportPath(functionId);
|
|
109
|
+
currentFunctions.push({
|
|
110
|
+
DomainName: domainName,
|
|
111
|
+
ModuleId: schema.moduleId,
|
|
112
|
+
FilePath: filePath,
|
|
113
|
+
FunctionId: functionId,
|
|
114
|
+
exportPathStr,
|
|
115
|
+
gitURL,
|
|
116
|
+
gitRef,
|
|
117
|
+
});
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
|
|
@@ -183,14 +183,15 @@ async function getModuleFromSpecBase(
|
|
|
183
183
|
// NOTE: The true tells require to not warn about the async loading
|
|
184
184
|
await (require as any)(path, true);
|
|
185
185
|
} catch (e: any) {
|
|
186
|
-
|
|
186
|
+
|
|
187
|
+
throw new Error(`Error when loading function for ${JSON.stringify(path)}:${spec.FunctionId}\n${e.stack}`);
|
|
187
188
|
}
|
|
188
189
|
let moduleId = require.resolve(path) || path;
|
|
189
190
|
let module = require.cache[moduleId];
|
|
190
191
|
if (!module) {
|
|
191
192
|
debugbreak(2);
|
|
192
193
|
debugger;
|
|
193
|
-
throw new Error(`Module not found: ${moduleId}`);
|
|
194
|
+
throw new Error(`Module not found: ${moduleId} (for ${spec.FunctionId})`);
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
//todonext
|
|
@@ -17,6 +17,7 @@ import { getPathFromProxy, getProxyPath } from "../2-proxy/pathValueProxy";
|
|
|
17
17
|
import { CALL_PERMISSIONS_KEY } from "../4-querysub/permissionsShared";
|
|
18
18
|
import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
|
|
19
19
|
import path from "path";
|
|
20
|
+
import { isEmpty } from "../misc";
|
|
20
21
|
|
|
21
22
|
// This is the the function id which should be used when creating the FunctionSpec (in order to load the module),
|
|
22
23
|
// to access the permissions in the schema.
|
|
@@ -45,6 +46,7 @@ export type FunctionMetadata<F = unknown> = {
|
|
|
45
46
|
|
|
46
47
|
export interface SchemaObject<Schema = any, Functions = any> {
|
|
47
48
|
data: () => Schema;
|
|
49
|
+
domainName: string;
|
|
48
50
|
moduleId: string;
|
|
49
51
|
functions: {
|
|
50
52
|
[key in keyof Functions]: (
|
|
@@ -350,9 +352,7 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
|
350
352
|
|
|
351
353
|
let permissionsNonWildcards = new Map<string, PermissionsCallback>();
|
|
352
354
|
let permissionsWildcards: { pathParts: string[]; callback: PermissionsCallback }[] = [];
|
|
353
|
-
|
|
354
|
-
addPermissions(rootPathStr, permissions);
|
|
355
|
-
}
|
|
355
|
+
addPermissions(rootPathStr, permissions);
|
|
356
356
|
function addPermissions(path: string, permissions: SchemaToPermissions<Schema> | undefined) {
|
|
357
357
|
if (!permissions) return;
|
|
358
358
|
for (let key of getStringKeys(permissions)) {
|
|
@@ -378,6 +378,7 @@ export function syncSchema<Schema>(schema?: Schema2): SyncSchemaResult<Schema> {
|
|
|
378
378
|
|
|
379
379
|
let result: SchemaObject = {
|
|
380
380
|
data,
|
|
381
|
+
domainName,
|
|
381
382
|
moduleId,
|
|
382
383
|
functions: wrappedFunctions as any,
|
|
383
384
|
functionMetadata: functionMetadata as any,
|
|
@@ -329,10 +329,10 @@ function predictCall(config: {
|
|
|
329
329
|
};
|
|
330
330
|
try {
|
|
331
331
|
dryRunResult = await getCallWrites({ call, debugName, overrides: config.overrides });
|
|
332
|
-
} catch (e) {
|
|
332
|
+
} catch (e: any) {
|
|
333
333
|
|
|
334
334
|
if (!pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(pathResultWrite, undefined))) {
|
|
335
|
-
console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e);
|
|
335
|
+
console.log(`Skipping prediction for ${debugName} due to error running predictive call. Likely just an out of order error.`, e.stack);
|
|
336
336
|
} else {
|
|
337
337
|
// NOTE: This case happens a lot, because of how we handle locks. We don't receive locked values, and so
|
|
338
338
|
// we assume all values have no locks, and only keep the latest. This is usually fine, but... if we lose
|
|
@@ -482,9 +482,9 @@ export async function getCallWrites(config: {
|
|
|
482
482
|
// throw new Error(`Domain not found ${call.DomainName}`);
|
|
483
483
|
// }
|
|
484
484
|
let domainObject = functionSchema()[call.DomainName];
|
|
485
|
-
if (!(call.ModuleId in domainObject.PathFunctionRunner)) {
|
|
486
|
-
|
|
487
|
-
}
|
|
485
|
+
// if (!(call.ModuleId in domainObject.PathFunctionRunner)) {
|
|
486
|
+
// throw new Error(`Module not found ${call.DomainName}.${call.ModuleId}`);
|
|
487
|
+
// }
|
|
488
488
|
let moduleObject = domainObject.PathFunctionRunner[call.ModuleId];
|
|
489
489
|
if (!(call.FunctionId in moduleObject.Sources)) {
|
|
490
490
|
throw new Error(`Function not found ${call.DomainName}.${call.ModuleId}.${call.FunctionId}`);
|
|
@@ -516,9 +516,9 @@ export async function getCallWrites(config: {
|
|
|
516
516
|
overrides: config.overrides,
|
|
517
517
|
nestedCalls: "inline",
|
|
518
518
|
watchFunction() {
|
|
519
|
-
overrideCurrentCall(call, () => {
|
|
519
|
+
return overrideCurrentCall(call, () => {
|
|
520
520
|
let args = parseArgs(call);
|
|
521
|
-
baseFunction(...args);
|
|
521
|
+
return baseFunction(...args);
|
|
522
522
|
});
|
|
523
523
|
},
|
|
524
524
|
});
|
|
@@ -20,8 +20,9 @@ import { CallSpec, getCurrentCall, getCurrentCallAllowUndefined, overrideCurrent
|
|
|
20
20
|
import { createLazyComponent } from "../library-components/LazyComponent";
|
|
21
21
|
import { LogNotify } from "./errorLogs/LogNotify";
|
|
22
22
|
import { isTrusted } from "../-d-trust/NetworkTrust2";
|
|
23
|
-
import { getDomain } from "../config";
|
|
23
|
+
import { devDebugbreak, getDomain } from "../config";
|
|
24
24
|
import { getCallWrites } from "../4-querysub/querysubPrediction";
|
|
25
|
+
import { SchemaObject } from "../3-path-functions/syncSchema";
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
export const managementPageURL = new URLParam("managementpage", "");
|
|
@@ -33,25 +34,17 @@ let pages: {
|
|
|
33
34
|
title: string;
|
|
34
35
|
}[] = [];
|
|
35
36
|
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// need to update it immediately, across all servers.
|
|
43
|
-
const schema = Querysub.createSchema()({
|
|
44
|
-
module,
|
|
45
|
-
moduleId: "managementPages",
|
|
46
|
-
functions: {
|
|
47
|
-
schemaIsManagementUser,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
function schemaIsManagementUser() {
|
|
51
|
-
return internalIsManagementUser();
|
|
37
|
+
let __schema: SchemaObject<unknown, {
|
|
38
|
+
schemaIsManagementUser: () => boolean;
|
|
39
|
+
}> | undefined = undefined;
|
|
40
|
+
function getSchema() {
|
|
41
|
+
if (!__schema) throw new Error(`registerManagementPages2 must be called for management functions to be accessed`);
|
|
42
|
+
return __schema;
|
|
52
43
|
}
|
|
53
44
|
|
|
45
|
+
let registeredModule: NodeJS.Module | undefined = undefined;
|
|
54
46
|
export async function registerManagementPages2(config: {
|
|
47
|
+
module: NodeJS.Module;
|
|
55
48
|
isManagementUser: () => boolean;
|
|
56
49
|
pages: {
|
|
57
50
|
componentName: string;
|
|
@@ -61,7 +54,11 @@ export async function registerManagementPages2(config: {
|
|
|
61
54
|
title?: string;
|
|
62
55
|
}[];
|
|
63
56
|
}) {
|
|
64
|
-
|
|
57
|
+
if (registeredModule) {
|
|
58
|
+
if (registeredModule === config.module) return;
|
|
59
|
+
throw new Error(`registerManagementPages2 called with a different module than previously registered. This will break isManagementUser, and so is not allowed.`);
|
|
60
|
+
}
|
|
61
|
+
registeredModule = config.module;
|
|
65
62
|
let inputPages = config.pages.slice();
|
|
66
63
|
|
|
67
64
|
inputPages.push({
|
|
@@ -94,6 +91,17 @@ export async function registerManagementPages2(config: {
|
|
|
94
91
|
getModule: () => import("./TimeDebug"),
|
|
95
92
|
});
|
|
96
93
|
|
|
94
|
+
// NOTE: We don't store the UI in the database (here, or anywhere else, at least
|
|
95
|
+
// not yet), but we do want to store the permission function call, as we may
|
|
96
|
+
// need to update it immediately, across all servers.
|
|
97
|
+
__schema = Querysub.createSchema()({
|
|
98
|
+
module: config.module,
|
|
99
|
+
moduleId: "managementPages",
|
|
100
|
+
functions: {
|
|
101
|
+
schemaIsManagementUser: config.isManagementUser,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
97
105
|
if (isServer()) {
|
|
98
106
|
for (let page of inputPages) {
|
|
99
107
|
// NOTE: If we split this into a module for component/controller, we need to make sure we
|
|
@@ -124,11 +132,6 @@ export async function registerManagementPages2(config: {
|
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
let baseIsManagementUser: () => boolean = () => {
|
|
128
|
-
// If we are clientside, all calls come from the server, and so are allowed.
|
|
129
|
-
if (isClient()) return true;
|
|
130
|
-
throw new Error("baseIsManagementUser not set");
|
|
131
|
-
};
|
|
132
135
|
|
|
133
136
|
export async function isManagementUser() {
|
|
134
137
|
let caller = SocketFunction.getCaller();
|
|
@@ -138,6 +141,9 @@ export async function isManagementUser() {
|
|
|
138
141
|
// when clients are given callbacks by servers, for management apis which
|
|
139
142
|
// have every endpoint protected by isManagementUser.
|
|
140
143
|
if (await isTrusted(callerMachineId)) return true;
|
|
144
|
+
let schema = getSchema();
|
|
145
|
+
let functionId = "schemaIsManagementUser" as const;
|
|
146
|
+
if (!schema.functions[functionId]) throw new Error(`Function ${functionId} not found in schema`);
|
|
141
147
|
|
|
142
148
|
let testCall: CallSpec = {
|
|
143
149
|
callerIP: IdentityController_getSecureIP(caller),
|
|
@@ -145,8 +151,8 @@ export async function isManagementUser() {
|
|
|
145
151
|
argsEncoded: encodeArgs([]),
|
|
146
152
|
CallId: nextId(),
|
|
147
153
|
DomainName: getDomain(),
|
|
148
|
-
ModuleId:
|
|
149
|
-
FunctionId:
|
|
154
|
+
ModuleId: schema.moduleId,
|
|
155
|
+
FunctionId: functionId,
|
|
150
156
|
runAtTime: getNextTime(),
|
|
151
157
|
};
|
|
152
158
|
|