socket-function 1.1.42 → 1.1.44

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.
@@ -4,6 +4,7 @@ module.allowclient = true;
4
4
 
5
5
  import { SocketFunction } from "../SocketFunction";
6
6
  import { cache, lazy } from "../src/caching";
7
+ import { createSingleton } from "../src/createSingleton";
7
8
  import * as fs from "fs";
8
9
  import crypto from "crypto";
9
10
  import debugbreak from "debugbreak";
@@ -56,18 +57,23 @@ declare global {
56
57
  var isHotReloading: (() => boolean) | undefined;
57
58
  }
58
59
 
59
- let isHotReloadingValue = false;
60
+ // Shared across copies of this package, so the "currently hot reloading" flag is consistent no
61
+ // matter which copy set it (ex, setExternalHotReloading) or reads it (callManager.registerClass
62
+ // reads it via the globalThis.isHotReloading bridge, which each copy overwrites). See createSingleton.
63
+ const isHotReloadingState = createSingleton("HotReloadController.isHotReloading", 1, () => ({ value: false }));
60
64
  export function isHotReloading() {
61
- return isHotReloadingValue;
65
+ return isHotReloadingState.get().value;
62
66
  }
63
67
  globalThis.isHotReloading = isHotReloading;
64
68
  export function hotReloadingGuard(): true {
65
69
  return !isHotReloading() as any;
66
70
  }
67
71
  export function setExternalHotReloading(value: boolean) {
68
- isHotReloadingValue = value;
72
+ isHotReloadingState.get().value = value;
69
73
  }
70
- let hotReloadCallbacks: ((modules: NodeJS.Module[]) => void)[] = [];
74
+ // Shared across copies, so callbacks registered through one copy's onHotReload still fire when the
75
+ // first-registered copy's controller performs a reload. See createSingleton.
76
+ const hotReloadCallbacks = createSingleton("HotReloadController.hotReloadCallbacks", 1, () => [] as ((modules: NodeJS.Module[]) => void)[]).get();
71
77
  export function onHotReload(callback: (modules: NodeJS.Module[]) => void) {
72
78
  hotReloadCallbacks.push(callback);
73
79
  }
@@ -124,7 +130,7 @@ const hotReloadModule = cache((module: NodeJS.Module) => {
124
130
  || module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
125
131
  ) {
126
132
  console.log(`Serverside reloading ${module.id}`);
127
- isHotReloadingValue = true;
133
+ isHotReloadingState.get().value = true;
128
134
  try {
129
135
  module.loaded = false;
130
136
  module.load(module.id);
@@ -133,7 +139,7 @@ const hotReloadModule = cache((module: NodeJS.Module) => {
133
139
  console.error(e);
134
140
  } finally {
135
141
  setTimeout(() => {
136
- isHotReloadingValue = false;
142
+ isHotReloadingState.get().value = false;
137
143
  }, 1000);
138
144
  }
139
145
  }
@@ -215,12 +221,12 @@ class HotReloadControllerBase {
215
221
  for (let module of modules) {
216
222
  module.loaded = false;
217
223
  }
218
- isHotReloadingValue = true;
224
+ isHotReloadingState.get().value = true;
219
225
  try {
220
226
  await Promise.all(modules.map(module => module.load(module.filename)));
221
227
  } finally {
222
228
  setTimeout(() => {
223
- isHotReloadingValue = false;
229
+ isHotReloadingState.get().value = false;
224
230
  }, 1000);
225
231
  }
226
232
 
package/index.d.ts CHANGED
@@ -360,8 +360,14 @@ declare module "socket-function/require/RequireController" {
360
360
  declare function addStaticRoot(root: string): void;
361
361
  type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
362
362
  export type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
363
- declare let mapGetModules: {
364
- remap(result: GetModulesResult, args: GetModulesArgs): Promise<GetModulesResult>;
363
+ declare const mapGetModules: {
364
+ remap(result: GetModulesResult, args: [pathRequests: string[], alreadyHave?: {
365
+ requireSeqNumProcessId: string;
366
+ seqNumRanges: {
367
+ s: number;
368
+ e?: number;
369
+ }[];
370
+ } | undefined, config?: {} | undefined]): Promise<GetModulesResult>;
365
371
  }[];
366
372
  declare function addMapGetModules(remap: typeof mapGetModules[number]["remap"]): void;
367
373
  declare class RequireControllerBase {
@@ -624,6 +630,8 @@ declare module "socket-function/src/batching" {
624
630
  shouldRetry?: (message: string) => boolean;
625
631
  minDelay?: number;
626
632
  maxDelay?: number;
633
+ timeout?: number;
634
+ warningInterval?: number;
627
635
  }): T;
628
636
  /** @deprecated Use safeLoop instead */
629
637
  export declare const throttledLoop: typeof unblockLoop;
@@ -1125,6 +1133,9 @@ declare module "socket-function/src/misc" {
1125
1133
  export declare function timeoutToUndefined<T>(time: number, p: Promise<T>): Promise<T | undefined>;
1126
1134
  export declare function timeoutToUndefinedSilent<T>(time: number, p: Promise<T>): Promise<T | undefined>;
1127
1135
  export declare function errorToWarning<T>(promise: Promise<T>): void;
1136
+ export declare function watchSlowPromise<T>(title: string, promise: Promise<T>, config: {
1137
+ interval?: number;
1138
+ }): Promise<T>;
1128
1139
 
1129
1140
  }
1130
1141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -45,8 +45,14 @@ declare function injectHTMLBeforeStartup(text: string | (() => Promise<string>))
45
45
  declare function addStaticRoot(root: string): void;
46
46
  type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
47
47
  export type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
48
- declare let mapGetModules: {
49
- remap(result: GetModulesResult, args: GetModulesArgs): Promise<GetModulesResult>;
48
+ declare const mapGetModules: {
49
+ remap(result: GetModulesResult, args: [pathRequests: string[], alreadyHave?: {
50
+ requireSeqNumProcessId: string;
51
+ seqNumRanges: {
52
+ s: number;
53
+ e?: number;
54
+ }[];
55
+ } | undefined, config?: {} | undefined]): Promise<GetModulesResult>;
50
56
  }[];
51
57
  declare function addMapGetModules(remap: typeof mapGetModules[number]["remap"]): void;
52
58
  declare class RequireControllerBase {
@@ -6,6 +6,7 @@ import { getCurrentHTTPRequest, setHTTPResultHeaders } from "../src/callHTTPHand
6
6
  import { formatNumberSuffixed, isNode, isNodeTrue, sha256Hash, sha256HashPromise } from "../src/misc";
7
7
  import zlib from "zlib";
8
8
  import { cacheLimited, lazy } from "../src/caching";
9
+ import { createSingleton } from "../src/createSingleton";
9
10
  import { formatNumber } from "../src/formatting/format";
10
11
  import { requireMain } from "./require";
11
12
  import path from "path";
@@ -88,11 +89,16 @@ const resolvedHTMLFile = lazy(() => {
88
89
  );
89
90
  });
90
91
 
91
- let beforeEntryText: (string | (() => Promise<string>))[] = [];
92
+ // All of these are mutated by the static helpers below (injectHTMLBeforeStartup, addStaticRoot,
93
+ // addMapGetModules), which callers invoke on RequireController. But only the first-registered
94
+ // copy's controller actually serves requireHTML/getModules, and it reads these vars. So they must
95
+ // be shared across copies, or a caller using a different copy would add (ex) before-entry HTML
96
+ // that the serving controller never reads. See createSingleton.
97
+ const beforeEntryText = createSingleton("RequireController.beforeEntryText", 1, () => [] as (string | (() => Promise<string>))[]).get();
92
98
  function injectHTMLBeforeStartup(text: string | (() => Promise<string>)) {
93
99
  beforeEntryText.push(text);
94
100
  }
95
- let staticRoots: string[] = [];
101
+ const staticRoots = createSingleton("RequireController.staticRoots", 1, () => [] as string[]).get();
96
102
  function addStaticRoot(root: string) {
97
103
  if (!root.endsWith("/")) {
98
104
  root += "/";
@@ -102,9 +108,9 @@ function addStaticRoot(root: string) {
102
108
 
103
109
  type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
104
110
  export type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
105
- let mapGetModules: {
111
+ const mapGetModules = createSingleton("RequireController.mapGetModules", 1, () => [] as {
106
112
  remap(result: GetModulesResult, args: GetModulesArgs): Promise<GetModulesResult>
107
- }[] = [];
113
+ }[]).get();
108
114
  function addMapGetModules(remap: typeof mapGetModules[number]["remap"]) {
109
115
  mapGetModules.push({ remap });
110
116
  }
@@ -421,7 +427,7 @@ async function compressCached(bufferKey: string, buffer: () => Buffer): Promise<
421
427
  export function getIsAllowClient(module: NodeJS.Module) {
422
428
  if (module.serveronly) return false;
423
429
  // IMPORTANT! We do not allow everything in node modules by default, as most things in node modules, you don't want to import client-side, and it will break if you import it client-side. Many of these are imported, but will never end up being called client-side, so it's fine to exclude them.
424
- if (allowAllNodeModulesFlag && module.filename.includes("node_modules")) {
430
+ if (allowAllNodeModulesFlag.get().value && module.filename.includes("node_modules")) {
425
431
  return true;
426
432
  }
427
433
  return module.allowclient;
@@ -434,7 +440,9 @@ declare global {
434
440
  var remapImportRequestsClientside: undefined | ClientRemapCallback[];
435
441
  }
436
442
 
437
- let baseController = new RequireControllerBase();
443
+ // Shared across copies of this package, so the registered controller and rootResolvePath (set by
444
+ // setRequireBootRequire) are the same instance everyone reads/writes. See createSingleton.
445
+ const baseController = createSingleton("RequireController.baseController", 1, () => new RequireControllerBase()).get();
438
446
  /** @deprecated, not needed, as this defaults to ".", which is a lot easier to reason about anyways. */
439
447
  export function setRequireBootRequire(dir: string) {
440
448
  dir = path.resolve(dir);
@@ -445,9 +453,11 @@ export function setRequireBootRequire(dir: string) {
445
453
  baseController.rootResolvePath = dir;
446
454
  }
447
455
 
448
- let allowAllNodeModulesFlag = false;
456
+ // Shared across copies, so calling allowAllNodeModules() on any copy is seen by the serving
457
+ // controller's getIsAllowClient. See createSingleton.
458
+ const allowAllNodeModulesFlag = createSingleton("RequireController.allowAllNodeModules", 1, () => ({ value: false }));
449
459
  export function allowAllNodeModules() {
450
- allowAllNodeModulesFlag = true;
460
+ allowAllNodeModulesFlag.get().value = true;
451
461
  }
452
462
 
453
463
  export const RequireController = SocketFunction.register(
package/src/batching.d.ts CHANGED
@@ -32,6 +32,8 @@ export declare function retryFunctional<T extends AnyFunction>(fnc: T, config?:
32
32
  shouldRetry?: (message: string) => boolean;
33
33
  minDelay?: number;
34
34
  maxDelay?: number;
35
+ timeout?: number;
36
+ warningInterval?: number;
35
37
  }): T;
36
38
  /** @deprecated Use safeLoop instead */
37
39
  export declare const throttledLoop: typeof unblockLoop;
package/src/batching.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { formatTime } from "./formatting/format";
2
2
  import { red } from "./formatting/logColors";
3
- import { PromiseObj, isNode, timeoutToError } from "./misc";
3
+ import { PromiseObj, isNode, timeoutToError, watchSlowPromise } from "./misc";
4
4
  import { measureWrap } from "./profiling/measure";
5
5
  import { AnyFunction, Args, MaybePromise } from "./types";
6
6
 
@@ -312,18 +312,32 @@ export function retryFunctional<T extends AnyFunction>(fnc: T, config?: {
312
312
  shouldRetry?: (message: string) => boolean;
313
313
  minDelay?: number;
314
314
  maxDelay?: number;
315
+ timeout?: number;
316
+ warningInterval?: number;
315
317
  }): T {
316
- let { maxRetries = DEFAULT_MAX_RETRIES, shouldRetry, minDelay = DEFAULT_RETRY_DELAY, maxDelay = DEFAULT_RETRY_DELAY } = config || {};
318
+ let {
319
+ maxRetries = DEFAULT_MAX_RETRIES, shouldRetry, minDelay = DEFAULT_RETRY_DELAY, maxDelay = DEFAULT_RETRY_DELAY,
320
+ timeout,
321
+ warningInterval,
322
+ } = config || {};
317
323
  let expFactor = Math.max(1, Math.log(maxDelay / minDelay) / Math.log(Math.max(maxRetries, 2)));
324
+ let name = fnc.name || fnc.toString().slice(0, 100);
318
325
  async function runFnc(args: any[], retries: number): Promise<ReturnType<T>> {
319
326
  try {
320
- return await (fnc as any)(...args);
327
+ let promise = (fnc as any)(...args);
328
+ if (timeout !== undefined) {
329
+ promise = timeoutToError(timeout, promise, () => new Error(`timed out after ${formatTime(timeout)}`));
330
+ }
331
+ if (warningInterval !== undefined) {
332
+ promise = watchSlowPromise(name, promise, { interval: warningInterval });
333
+ }
334
+ return await promise;
321
335
  } catch (e: any) {
322
336
  if (shouldRetry && !shouldRetry(String(e.stack))) {
323
337
  throw e;
324
338
  }
325
339
  if (retries < 0) throw e;
326
- console.warn(`Retrying ${fnc.name}, due to error ${String(e.stack)}`);
340
+ console.warn(`Retrying ${name}, due to error ${String(e.stack)}`);
327
341
  retries--;
328
342
  let curCount = maxRetries - retries;
329
343
  await delay(minDelay * expFactor ** curCount);
package/src/misc.d.ts CHANGED
@@ -96,3 +96,6 @@ export declare function timeoutToError<T>(time: number, p: Promise<T>, err: () =
96
96
  export declare function timeoutToUndefined<T>(time: number, p: Promise<T>): Promise<T | undefined>;
97
97
  export declare function timeoutToUndefinedSilent<T>(time: number, p: Promise<T>): Promise<T | undefined>;
98
98
  export declare function errorToWarning<T>(promise: Promise<T>): void;
99
+ export declare function watchSlowPromise<T>(title: string, promise: Promise<T>, config: {
100
+ interval?: number;
101
+ }): Promise<T>;
package/src/misc.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { canHaveChildren, MaybePromise } from "./types";
2
- import { formatNumber } from "./formatting/format";
2
+ import { formatNumber, formatTime } from "./formatting/format";
3
+ import { delay } from "./batching";
4
+ import { yellow } from "./formatting/logColors";
3
5
 
4
6
  export const timeInSecond = 1000;
5
7
  export const timeInMinute = timeInSecond * 60;
@@ -458,3 +460,20 @@ export function errorToWarning<T>(promise: Promise<T>): void {
458
460
  return undefined as any;
459
461
  }) as any;
460
462
  }
463
+
464
+ export function watchSlowPromise<T>(title: string, promise: Promise<T>, config: {
465
+ interval?: number;
466
+ }) {
467
+ let { interval = 5000 } = config;
468
+ let fulfilled = false;
469
+ void promise.finally(() => fulfilled = true);
470
+ let startTime = Date.now();
471
+ void (async () => {
472
+ while (true) {
473
+ await delay(interval);
474
+ if (fulfilled) break;
475
+ console.log(`${yellow(`Slow promise running for ${formatTime(Date.now() - startTime)}`)} | ${title}`);
476
+ }
477
+ })();
478
+ return promise;
479
+ }