socket-function 0.8.38 → 0.8.39

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/SocketFunction.ts CHANGED
@@ -9,6 +9,7 @@ import { Args, MaybePromise } from "./src/types";
9
9
  import { setDefaultHTTPCall } from "./src/callHTTPHandler";
10
10
  import debugbreak from "debugbreak";
11
11
  import { lazy } from "./src/caching";
12
+ import { delay } from "./src/batching";
12
13
 
13
14
  module.allowclient = true;
14
15
 
@@ -76,7 +77,7 @@ export class SocketFunction {
76
77
  return shape as any as SocketExposedShape;
77
78
  });
78
79
 
79
- setImmediate(() => {
80
+ void Promise.resolve().then(() => {
80
81
  registerClass(classGuid, instance as SocketExposedInterface, getShape());
81
82
  });
82
83
 
@@ -115,7 +116,7 @@ export class SocketFunction {
115
116
  _classGuid: classGuid,
116
117
  };
117
118
 
118
- setImmediate(() => {
119
+ void Promise.resolve().then(() => {
119
120
  let onMount = getDefaultHooks?.().onMount;
120
121
  if (onMount) {
121
122
  let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
@@ -209,6 +210,10 @@ export class SocketFunction {
209
210
  if (config.ip) {
210
211
  this.mountedIP = config.ip;
211
212
  }
213
+
214
+ // Wait for any additionals functions to expose themselves
215
+ await delay("immediate");
216
+
212
217
  this.mountedNodeId = await startSocketServer(config);
213
218
  this.hasMounted = true;
214
219
  for (let classGuid of SocketFunction.exposedClasses) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.8.38",
3
+ "version": "0.8.39",
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",
@@ -10,7 +10,7 @@
10
10
  "node-forge": "https://github.com/sliftist/forge#name",
11
11
  "preact": "^10.10.6",
12
12
  "rdtsc-now": "^0.3.0",
13
- "typenode": "^4.9.4-b",
13
+ "typenode": "^4.9.4-c",
14
14
  "ws": "^8.8.0"
15
15
  },
16
16
  "scripts": {
@@ -13,6 +13,11 @@ declare global {
13
13
  /** Indicates the module is allowed clientside. */
14
14
  allowclient?: boolean;
15
15
 
16
+ /** Causes the module to not preload, requiring `await import()` for it to load correctly
17
+ * - Shouldn't be set recursively, otherwise nested packages will break.
18
+ */
19
+ lazyload?: boolean;
20
+
16
21
  /** Indicates the module is definitely not allowed clientside */
17
22
  serveronly?: boolean;
18
23
 
@@ -118,7 +123,8 @@ class RequireControllerBase {
118
123
  let modules: {
119
124
  [resolvedPath: string]: SerializedModule;
120
125
  } = Object.create(null);
121
- function addModule(module: NodeJS.Module) {
126
+ function addModule(module: NodeJS.Module, rootImport = false) {
127
+ if (!rootImport && module.lazyload) return;
122
128
  if (!module.requireControllerSeqNum) {
123
129
  module.requireControllerSeqNum = nextModuleSeqNum++;
124
130
  }
@@ -223,7 +229,7 @@ class RequireControllerBase {
223
229
  clientModule = createNotFoundModule(`Module ${pathRequest} (resolved to ${resolvedPath}) is not allowed clientside (set module.allowclient in it, or call setFlag when it is imported).`);
224
230
  }
225
231
 
226
- addModule(clientModule);
232
+ addModule(clientModule, true);
227
233
  }
228
234
 
229
235
  return { requestsResolvedPaths, modules, requireSeqNumProcessId };
@@ -242,6 +242,15 @@
242
242
  }
243
243
 
244
244
  let resolvedPath = serializedModule.requests[request];
245
+ if (resolvedPath !== "NOTALLOWEDCLIENTSIDE" && !serializedModules[resolvedPath]) {
246
+ if (!asyncIsFine) {
247
+ console.warn(`Accessed unexpected module %c${request}%c in %c${module.id}%c\n\tTreating it as an async require.\n\tAll modules require synchronously clientside must be required serverside at a module level.`,
248
+ "color: red", "color: unset",
249
+ "color: red", "color: unset",
250
+ );
251
+ }
252
+ return rootRequire(resolvedPath);
253
+ }
245
254
 
246
255
  let exportsOverride = undefined;
247
256
  if (resolvedPath === "NOTALLOWEDCLIENTSIDE" || !serializedModules[resolvedPath].allowclient) {
@@ -337,6 +346,7 @@
337
346
  module.id = resolvedId;
338
347
  module.filename = serializedModule?.filename;
339
348
  module.exports = {};
349
+ module.exports.default = module.exports;
340
350
  module.children = [];
341
351
 
342
352
  module.load = load;
@@ -383,19 +393,6 @@
383
393
 
384
394
  let dirname = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
385
395
 
386
- var __createBinding = (Object.create ? (function (o, m, k, k2) {
387
- if (k2 === undefined) k2 = k;
388
- Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
389
- }) : (function (o, m, k, k2) {
390
- if (k2 === undefined) k2 = k;
391
- o[k2] = m[k];
392
- }));
393
- var __setModuleDefault = (Object.create ? (function (o, v) {
394
- Object.defineProperty(o, "default", { enumerable: true, value: v });
395
- }) : function (o, v) {
396
- o["default"] = v;
397
- });
398
-
399
396
  let time = Date.now();
400
397
  currentModuleEvaluationStack.push(module.filename);
401
398
  try {
@@ -405,21 +402,9 @@
405
402
  // which checks for unloadedModule and returns undefined in that case.
406
403
  __importStar(mod) {
407
404
  if (mod[unloadedModule]) return undefined;
408
- if (mod && mod.__esModule) return mod;
409
- var result = {};
410
- if (mod !== null && mod !== undefined) {
411
- for (var k in mod) {
412
- if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) {
413
- __createBinding(result, mod, k);
414
- }
415
- }
416
- }
417
- __setModuleDefault(result, mod);
418
- return result;
405
+ return mod;
419
406
  },
420
407
  __importDefault(mod) {
421
- // If typescript isn't going to complain about importing from a module with no default export,
422
- // then we'll just change our implementation to work the same way as typescript types...
423
408
  return mod.default ? mod : { default: mod };
424
409
  },
425
410
  },
@@ -1,7 +1,7 @@
1
1
  import { CallerContext, CallerContextBase, CallType, FullCallType } from "../SocketFunctionTypes";
2
2
  import * as ws from "ws";
3
3
  import { performLocalCall } from "./callManager";
4
- import { convertErrorStackToError, formatNumberSuffixed, isNode } from "./misc";
4
+ import { convertErrorStackToError, formatNumberSuffixed, isNode, list } from "./misc";
5
5
  import { createWebsocketFactory, getTLSSocket } from "./websocketFactory";
6
6
  import { SocketFunction } from "../SocketFunction";
7
7
  import { gzip } from "zlib";
@@ -11,6 +11,7 @@ import debugbreak from "debugbreak";
11
11
  import { lazy } from "./caching";
12
12
  import { JSONLACKS } from "./JSONLACKS/JSONLACKS";
13
13
  import { red } from "./formatting/logColors";
14
+ import { isSplitableArray } from "./fixLargeNetworkCalls";
14
15
 
15
16
  const MIN_RETRY_DELAY = 1000;
16
17
 
@@ -115,6 +116,31 @@ export async function createCallFactory(
115
116
  if (time > SocketFunction.WIRE_WARN_TIME) {
116
117
  console.log(red(`Slow serialize, took ${time}ms to serialize ${data.byteLength} bytes. For ${call.classGuid}.${call.functionName}`));
117
118
  }
119
+
120
+ if (data.byteLength > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
121
+ let splitArgIndex = call.args.findIndex(isSplitableArray);
122
+ if (splitArgIndex >= 0) {
123
+ let SPLIT_GROUPS = 10;
124
+ let splitArg = call.args[splitArgIndex] as unknown[];
125
+ let subCalls = list(SPLIT_GROUPS).map(index => {
126
+ let start = Math.floor(index / SPLIT_GROUPS * splitArg.length);
127
+ let end = Math.floor((index + 1) / SPLIT_GROUPS * splitArg.length);
128
+ return splitArg.slice(start, end);
129
+ }).filter(x => x.length > 0);
130
+ for (let splitList of subCalls) {
131
+ let subCall = { ...call };
132
+ subCall.args = subCall.args.slice();
133
+ subCall.args[splitArgIndex] = splitList;
134
+ await callFactory.performCall(subCall);
135
+ }
136
+ // Eh... we COULD return the array of results, but... then the result would sometimes be an array,
137
+ // some times not, so, it is better to return a string which will make it more clear why it sometimes varies.
138
+ return "CALLS_SPLIT_DUE_TO_LARGE_ARGS";
139
+ }
140
+
141
+ throw new Error(`Call too large to send (${call.classGuid}.${call.functionName}, size: ${data.byteLength} > ${SocketFunction.MAX_MESSAGE_SIZE}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`);
142
+ }
143
+
118
144
  let resultPromise = new Promise((resolve, reject) => {
119
145
  let callback = (result: InternalReturnType) => {
120
146
  if (SocketFunction.logMessages) {
@@ -130,10 +156,6 @@ export async function createCallFactory(
130
156
  pendingCalls.set(seqNum, { callback, data, call: fullCall });
131
157
  });
132
158
 
133
- if (data.byteLength > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
134
- throw new Error(`Call too large to send (${call.classGuid}.${call.functionName}, size: ${data.byteLength} > ${SocketFunction.MAX_MESSAGE_SIZE}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`);
135
- }
136
-
137
159
  await send(data);
138
160
 
139
161
  return await resultPromise;
package/src/batching.ts CHANGED
@@ -91,4 +91,37 @@ export function runInSerial<T extends (...args: any[]) => Promise<any>>(fnc: T):
91
91
  updateQueue[0]?.();
92
92
  }
93
93
  }) as T;
94
+ }
95
+
96
+ export function runInfinitePoll(
97
+ delayTime: number,
98
+ fnc: () => Promise<void> | void
99
+ ) {
100
+ void (async () => {
101
+ while (true) {
102
+ await delay(delayTime);
103
+ try {
104
+ await fnc();
105
+ } catch (e: any) {
106
+ console.error(`Error in infinite poll ${fnc.name || fnc.toString().slice(0, 100).split("\n").slice(0, 2).join("\n")} (continuing poll loop)\n${e.stack}`);
107
+ }
108
+ }
109
+ })();
110
+ }
111
+
112
+ export async function runInfinitePollCallAtStart(
113
+ delayTime: number,
114
+ fnc: () => Promise<void> | void
115
+ ) {
116
+ void (async () => {
117
+ while (true) {
118
+ await delay(delayTime);
119
+ try {
120
+ await fnc();
121
+ } catch (e: any) {
122
+ console.error(`Error in infinite poll ${fnc.name} (continuing poll loop)\n${e.stack}`);
123
+ }
124
+ }
125
+ })();
126
+ return await fnc();
94
127
  }
@@ -0,0 +1,9 @@
1
+ const arrayIsSplitable = Symbol.for("arrayIsSplitable");
2
+ export function markArrayAsSplitable<T>(data: T[]): T[] {
3
+ (data as any)[arrayIsSplitable] = true;
4
+ return data;
5
+ }
6
+ export function isSplitableArray<T>(data: T): data is T & (unknown[]) {
7
+ if (!Array.isArray(data)) return false;
8
+ return (data as any)[arrayIsSplitable] === true;
9
+ }
@@ -12,6 +12,10 @@ export function enableMeasurements() {
12
12
  }
13
13
  measurementsEnabled = true;
14
14
  }
15
+ /** NOTE: Must be called BEFORE anything else is imported! */
16
+ export function disableMeasurements() {
17
+ measurementsEnabled = false;
18
+ }
15
19
 
16
20
  let functionsSkipped = 0;
17
21
 
@@ -209,7 +213,7 @@ interface ProfileEntry {
209
213
  stillOpenCount: number;
210
214
  }
211
215
 
212
- let measurementsEnabled = false;
216
+ let measurementsEnabled = true;
213
217
 
214
218
  let outstandingProfiles: MeasureProfile[] = [];
215
219
  function recordOwnTime(ownTimeObj: OwnTimeObj) {