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.
@@ -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 async function runPromise(command: string, config?: {
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
- return Querysub.fastRead(() => {
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
- return `${fncName} (on ${nodeId})`;
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 SocketRegistered>(
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
- SocketFunction.onNextDisconnect(nodeId, () => {
183
- Querysub.commitLocal(() => {
184
- let nodeObj = syncedData()[id][nodeId];
185
- for (let fnc in nodeObj) {
186
- for (let argsHash in nodeObj[fnc]) {
187
- delete nodeObj[fnc][argsHash].promise;
188
- delete nodeObj[fnc][argsHash].result;
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 nodeObj[fnc];
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
- return Querysub.commitLocal(() => {
239
- let argsHash = JSON.stringify(args);
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
- let result = atomic(obj.result);
243
- let promise = atomic(obj.promise);
254
+ let result = atomic(obj.result);
255
+ let promise = atomic(obj.promise);
244
256
 
245
- // 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!
246
- if (!promise && (!result || atomic(obj.invalidated))) {
247
- obj.invalidated = false;
248
- let timeStart = Date.now();
249
- function logFinished() {
250
- let duration = Date.now() - timeStart;
251
- if (duration > 500) {
252
- console.warn(`Slow call ${fncName} took ${formatTime(duration)}`);
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
- let promise = controller.nodes[nodeId][fncName](...args) as Promise<unknown>;
256
- doAtomicWrites(() => {
257
- obj.promise = promise;
258
- });
259
- function invalidateReaders() {
260
- let root = syncedData();
261
- for (let writesTo of config?.writes?.[fncName] || []) {
262
- for (let watcher of writeWatchers.get(writesTo) || []) {
263
- for (let fncs of Object.values(root[watcher.controllerId])) {
264
- for (let fnc of Object.values(fncs)) {
265
- for (let obj of Object.values(fnc)) {
266
- obj.invalidated = true;
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
- if (result) {
295
- if ("error" in result) {
296
- throw result.error;
297
- } else {
298
- return result.result;
299
- }
322
+ if (result) {
323
+ if ("error" in result) {
324
+ throw result.error;
325
+ } else {
326
+ return result.result;
300
327
  }
301
- return undefined;
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
- // 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.
309
- obj.promise = undefined;
310
- obj.invalidated = true;
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
- // Don't cache promise calls
320
- void promise.finally(() => {
321
- Querysub.fastRead(() => {
322
- if (obj.promise === promise) {
323
- obj.promise = undefined;
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} {