querysub 0.339.0 → 0.341.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 +3 -3
- package/src/-a-archives/archivesBackBlaze.ts +29 -11
- package/src/2-proxy/PathValueProxyWatcher.ts +219 -53
- package/src/2-proxy/garbageCollection.ts +2 -1
- package/src/3-path-functions/syncSchema.ts +8 -7
- package/src/4-dom/qreact.tsx +12 -1
- package/src/4-querysub/Querysub.ts +28 -3
- package/src/4-querysub/QuerysubController.ts +71 -43
- package/src/4-querysub/predictionQueue.tsx +183 -0
- package/src/4-querysub/querysubPrediction.ts +148 -274
- package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +2 -2
- package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +1 -1
- package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +1 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +5 -0
- package/src/functional/promiseCache.ts +10 -1
- package/src/functional/runCommand.ts +2 -63
- package/src/library-components/SyncedController.ts +120 -68
- package/src/library-components/SyncedControllerLoadingIndicator.tsx +1 -1
|
@@ -1,71 +1,10 @@
|
|
|
1
1
|
import child_process from "child_process";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { blue, red } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { runPromise } from "socket-function/src/runPromise";
|
|
4
5
|
|
|
5
6
|
export const runAsync = runPromise;
|
|
6
|
-
export
|
|
7
|
-
cwd?: string;
|
|
8
|
-
quiet?: boolean;
|
|
9
|
-
// Never throw, just return the full output
|
|
10
|
-
nothrow?: boolean;
|
|
11
|
-
detach?: boolean;
|
|
12
|
-
}) {
|
|
13
|
-
return new Promise<string>((resolve, reject) => {
|
|
14
|
-
console.log(">" + blue(command));
|
|
15
|
-
const childProc = child_process.spawn(command, {
|
|
16
|
-
shell: true,
|
|
17
|
-
cwd: config?.cwd,
|
|
18
|
-
stdio: ["inherit", "pipe", "pipe"],
|
|
19
|
-
detached: config?.detach,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
let fullOutput = "";
|
|
23
|
-
let stderr = "";
|
|
24
|
-
|
|
25
|
-
// Always collect output
|
|
26
|
-
childProc.stdout?.on("data", (data) => {
|
|
27
|
-
const chunk = data.toString();
|
|
28
|
-
fullOutput += chunk;
|
|
29
|
-
|
|
30
|
-
// Stream to console unless quiet mode
|
|
31
|
-
if (!config?.quiet) {
|
|
32
|
-
process.stdout.write(chunk);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
childProc.stderr?.on("data", (data) => {
|
|
37
|
-
const chunk = data.toString();
|
|
38
|
-
stderr += chunk;
|
|
39
|
-
fullOutput += chunk;
|
|
40
|
-
|
|
41
|
-
// Stream to console unless quiet mode
|
|
42
|
-
if (!config?.quiet) {
|
|
43
|
-
process.stderr.write(red(chunk));
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
childProc.on("error", (err) => {
|
|
48
|
-
if (config?.nothrow) {
|
|
49
|
-
resolve(fullOutput);
|
|
50
|
-
} else {
|
|
51
|
-
reject(err);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
childProc.on("close", (code) => {
|
|
56
|
-
if (code === 0 || config?.nothrow) {
|
|
57
|
-
resolve(fullOutput);
|
|
58
|
-
} else {
|
|
59
|
-
let errorMessage = `Process exited with code ${code} for command: ${command}`;
|
|
60
|
-
if (stderr) {
|
|
61
|
-
errorMessage += `\n${stderr}`;
|
|
62
|
-
}
|
|
63
|
-
const error = new Error(errorMessage);
|
|
64
|
-
reject(error);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
7
|
+
export { runPromise };
|
|
69
8
|
|
|
70
9
|
/** @deprecated, use runPromise */
|
|
71
10
|
export async function runCommand(config: {
|
|
@@ -5,7 +5,7 @@ import { FullCallType, SocketRegistered } from "socket-function/SocketFunctionTy
|
|
|
5
5
|
import { onHotReload } from "socket-function/hot/HotReloadController";
|
|
6
6
|
import { cache } from "socket-function/src/caching";
|
|
7
7
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
8
|
-
import { nextId } from "socket-function/src/misc";
|
|
8
|
+
import { nextId, PromiseObj } from "socket-function/src/misc";
|
|
9
9
|
import { getCallObj } from "socket-function/src/nodeProxy";
|
|
10
10
|
import { MaybePromise } from "socket-function/src/types";
|
|
11
11
|
import { isNode } from "typesafecss";
|
|
@@ -37,20 +37,24 @@ onHotReload(() => {
|
|
|
37
37
|
});
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
export function syncedIsAnyLoading() {
|
|
41
|
-
|
|
40
|
+
export function syncedIsAnyLoading(): Map<string, number> | undefined {
|
|
41
|
+
let result = new Map<string, number>();
|
|
42
|
+
Querysub.fastRead(() => {
|
|
42
43
|
for (let controllerId in syncedData()) {
|
|
43
44
|
for (let [nodeId, fncs] of Object.entries(syncedData()[controllerId])) {
|
|
44
45
|
for (let [fncName, fnc] of Object.entries(fncs)) {
|
|
45
46
|
for (let obj of Object.values(fnc)) {
|
|
46
47
|
if (atomic(obj.promise)) {
|
|
47
|
-
|
|
48
|
+
let text = `${fncName} (on ${nodeId})`;
|
|
49
|
+
result.set(text, (result.get(text) || 0) + 1);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
});
|
|
56
|
+
if (result.size === 0) return undefined;
|
|
57
|
+
return result;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
let syncedData = Querysub.createLocalSchema<{
|
|
@@ -58,7 +62,7 @@ let syncedData = Querysub.createLocalSchema<{
|
|
|
58
62
|
[nodeId: string]: {
|
|
59
63
|
[fnc: string]: {
|
|
60
64
|
[argsHash: string]: {
|
|
61
|
-
promise: Promise<unknown> | undefined;
|
|
65
|
+
promise: { promise: Promise<unknown> } | undefined;
|
|
62
66
|
result?: { result: unknown } | { error: Error };
|
|
63
67
|
invalidated?: boolean;
|
|
64
68
|
}
|
|
@@ -87,9 +91,16 @@ const writeWatchers = new Map<string, {
|
|
|
87
91
|
fncName: string;
|
|
88
92
|
}[]>();
|
|
89
93
|
|
|
90
|
-
export function getSyncedController<T extends
|
|
94
|
+
export function getSyncedController<T extends {
|
|
95
|
+
nodes: {
|
|
96
|
+
[key: string]: {
|
|
97
|
+
[key: string]: ((...args: any[]) => Promise<any>) | string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
}>(
|
|
91
101
|
controller: T,
|
|
92
102
|
config?: {
|
|
103
|
+
cachePromiseCalls?: boolean;
|
|
93
104
|
/** When a controller call for a write finishes, we refresh all readers.
|
|
94
105
|
* - Invalidation is global, across all controllers.
|
|
95
106
|
*/
|
|
@@ -124,7 +135,7 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
124
135
|
notAllowedOnServer();
|
|
125
136
|
}
|
|
126
137
|
call.promise = (...args: any[]) => {
|
|
127
|
-
return controller.nodes[nodeId][fncName](...args);
|
|
138
|
+
return (controller.nodes[nodeId][fncName] as any)(...args);
|
|
128
139
|
};
|
|
129
140
|
call.reset = (...args: any[]) => {
|
|
130
141
|
notAllowedOnServer();
|
|
@@ -179,19 +190,21 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
let result = cache((nodeId: string) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
for (let
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
if (nodeId) {
|
|
194
|
+
SocketFunction.onNextDisconnect(nodeId, () => {
|
|
195
|
+
Querysub.commitLocal(() => {
|
|
196
|
+
let nodeObj = syncedData()[id][nodeId];
|
|
197
|
+
for (let fnc in nodeObj) {
|
|
198
|
+
for (let argsHash in nodeObj[fnc]) {
|
|
199
|
+
delete nodeObj[fnc][argsHash].promise;
|
|
200
|
+
delete nodeObj[fnc][argsHash].result;
|
|
201
|
+
}
|
|
202
|
+
delete nodeObj[fnc];
|
|
189
203
|
}
|
|
190
|
-
delete
|
|
191
|
-
}
|
|
192
|
-
delete syncedData()[id][nodeId];
|
|
204
|
+
delete syncedData()[id][nodeId];
|
|
205
|
+
});
|
|
193
206
|
});
|
|
194
|
-
}
|
|
207
|
+
}
|
|
195
208
|
return new Proxy({}, {
|
|
196
209
|
get: (target, fncNameUntyped) => {
|
|
197
210
|
if (typeof fncNameUntyped !== "string") return undefined;
|
|
@@ -235,41 +248,41 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
235
248
|
}
|
|
236
249
|
let fncName = fncNameUntyped;
|
|
237
250
|
function call(...args: any[]) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
let obj = syncedData()[id][nodeId][fncName][argsHash];
|
|
251
|
+
let argsHash = JSON.stringify(args);
|
|
252
|
+
let obj = syncedData()[id][nodeId][fncName][argsHash];
|
|
241
253
|
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
let result = atomic(obj.result);
|
|
255
|
+
let promise = atomic(obj.promise);
|
|
244
256
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
257
|
+
// NOTE: If we are invalidated when the promise is running, nothing happens (as we don't watch invalidated if we are running with a promise). BUT, if the promise isn't running, we will run again, and start running again. In this way we don't queue up a lot if we invalidate a lot, but we do always run again after the invalidation to get the latest result!
|
|
258
|
+
if (!promise && (!result || atomic(obj.invalidated))) {
|
|
259
|
+
obj.invalidated = false;
|
|
260
|
+
let timeStart = Date.now();
|
|
261
|
+
function logFinished() {
|
|
262
|
+
let duration = Date.now() - timeStart;
|
|
263
|
+
if (duration > 500) {
|
|
264
|
+
console.warn(`Slow call ${fncName} took ${formatTime(duration)}`);
|
|
254
265
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
}
|
|
266
|
+
}
|
|
267
|
+
let promiseObjBase = new PromiseObj<unknown>();
|
|
268
|
+
let promiseObj: { promise: Promise<unknown> } = {
|
|
269
|
+
promise: promiseObjBase.promise,
|
|
270
|
+
};
|
|
271
|
+
doAtomicWrites(() => {
|
|
272
|
+
obj.promise = promiseObj;
|
|
273
|
+
});
|
|
274
|
+
// We have to wait until we actually commit before making the call. Otherwise, if this commit is rejected because we need to do synchronized values, we'll call the function twice.
|
|
275
|
+
let fnc = controller.nodes[nodeId][fncName] as any;
|
|
276
|
+
Querysub.onCommitFinished(() => {
|
|
277
|
+
if (Querysub.isAllSynced()) {
|
|
278
|
+
void Promise.resolve().then(() => {
|
|
279
|
+
doPromiseCall();
|
|
280
|
+
});
|
|
272
281
|
}
|
|
282
|
+
});
|
|
283
|
+
function doPromiseCall() {
|
|
284
|
+
let promise = fnc(...args) as Promise<unknown>;
|
|
285
|
+
promiseObjBase.resolve(promise);
|
|
273
286
|
promise.then(
|
|
274
287
|
result => {
|
|
275
288
|
logFinished();
|
|
@@ -289,42 +302,81 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
289
302
|
}
|
|
290
303
|
);
|
|
291
304
|
}
|
|
305
|
+
function invalidateReaders() {
|
|
306
|
+
let root = syncedData();
|
|
307
|
+
for (let writesTo of config?.writes?.[fncName] || []) {
|
|
308
|
+
for (let watcher of writeWatchers.get(writesTo) || []) {
|
|
309
|
+
for (let fncs of Object.values(root[watcher.controllerId])) {
|
|
310
|
+
for (let fnc of Object.values(fncs)) {
|
|
311
|
+
for (let obj of Object.values(fnc)) {
|
|
312
|
+
obj.invalidated = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
292
320
|
|
|
293
321
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
322
|
+
if (result) {
|
|
323
|
+
if ("error" in result) {
|
|
324
|
+
throw result.error;
|
|
325
|
+
} else {
|
|
326
|
+
return result.result;
|
|
300
327
|
}
|
|
301
|
-
|
|
302
|
-
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
303
330
|
}
|
|
304
331
|
call.promise = (...args: any[]) => {
|
|
305
332
|
return Querysub.fastRead(() => {
|
|
306
333
|
let argsHash = JSON.stringify(args);
|
|
307
334
|
let obj = syncedData()[id][nodeId][fncName][argsHash];
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
335
|
+
if (!config?.cachePromiseCalls) {
|
|
336
|
+
// Reset promise, to force it to not use the cache, as promise functions should never be cached. This might result in the results being set out of order, but... generally functions called with promise and accessed inside a watcher, so this should be fine.
|
|
337
|
+
obj.promise = undefined;
|
|
338
|
+
obj.invalidated = true;
|
|
339
|
+
} else {
|
|
340
|
+
let result = atomic(obj.result);
|
|
341
|
+
if (result) {
|
|
342
|
+
if ("error" in result) {
|
|
343
|
+
throw result.error;
|
|
344
|
+
} else {
|
|
345
|
+
return result.result;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
311
349
|
call(...args);
|
|
312
350
|
// Assign to itself, to preset the type assumptions typescript makes (otherwise we get an error below)
|
|
313
351
|
obj = obj as any;
|
|
352
|
+
if (config?.cachePromiseCalls) {
|
|
353
|
+
let result = atomic(obj.result);
|
|
354
|
+
if (result) {
|
|
355
|
+
if ("error" in result) {
|
|
356
|
+
throw result.error;
|
|
357
|
+
} else {
|
|
358
|
+
return result.result;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
314
362
|
let promise = atomic(obj.promise);
|
|
315
363
|
if (!promise) {
|
|
316
364
|
debugger;
|
|
317
365
|
throw new Error(`Impossible, called function, but promise is not found for ${fncName}`);
|
|
318
366
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
367
|
+
if (!config?.cachePromiseCalls) {
|
|
368
|
+
Querysub.onCommitFinished(() => {
|
|
369
|
+
// Don't cache promise calls
|
|
370
|
+
void promise?.promise.finally(() => {
|
|
371
|
+
Querysub.fastRead(() => {
|
|
372
|
+
if (obj.promise === promise) {
|
|
373
|
+
obj.promise = undefined;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
325
377
|
});
|
|
326
|
-
}
|
|
327
|
-
return promise;
|
|
378
|
+
}
|
|
379
|
+
return promise.promise;
|
|
328
380
|
});
|
|
329
381
|
};
|
|
330
382
|
call.reset = (...args: any[]) => {
|
|
@@ -34,7 +34,7 @@ export class SyncedControllerLoadingIndicator extends qreact.Component {
|
|
|
34
34
|
.borderRadius("50%")
|
|
35
35
|
+ " " + spinAnimationClass
|
|
36
36
|
}></div>
|
|
37
|
-
<span title={loadingPath}>Syncing data...</span>
|
|
37
|
+
<span title={Array.from(loadingPath.entries()).map(([key, value]) => `${key} (${value})`).join(" | ")}>Syncing data...</span>
|
|
38
38
|
<style>
|
|
39
39
|
{`
|
|
40
40
|
@keyframes ${spinAnimationClass} {
|