socket-function 1.2.28 → 1.2.30

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.
@@ -5,15 +5,12 @@ import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, S
5
5
  import { SocketServerConfig } from "./src/webSocketServer";
6
6
  import { Args, MaybePromise } from "./src/types";
7
7
  import "./SetProcessVariables";
8
- /** The values behind SocketFunction's configuration statics. They are exposed on the class as
9
- * accessors onto a singleton (see the defineSingletonConfig call at the bottom of this file), so
10
- * that configuration set through one copy of this package applies to all of them. This interface
11
- * is the single source of truth for their types - the class declares each one as
12
- * `declare static X: SocketFunctionConfig["X"]`.
13
- */
14
- export interface SocketFunctionConfig {
15
- logMessages: boolean;
16
- trackMessageSizes: {
8
+ type ExtractShape<ClassType, Shape> = {
9
+ [key in keyof ClassType]: (key extends keyof Shape ? ClassType[key] extends SocketExposedInterface[""] ? ClassType[key] : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never : "Function has implementation but is not exposed in the SocketFunction.register call");
10
+ };
11
+ export declare class SocketFunction {
12
+ static logMessages: boolean;
13
+ static trackMessageSizes: {
17
14
  upload: ((size: number, nodeId: string) => void)[];
18
15
  download: ((size: number, nodeId: string) => void)[];
19
16
  callTimes: ((obj: {
@@ -22,48 +19,24 @@ export interface SocketFunctionConfig {
22
19
  nodeId: string;
23
20
  }) => void)[];
24
21
  };
25
- MAX_MESSAGE_SIZE: number;
26
- HTTP_ETAG_CACHE: boolean;
27
- silent: boolean;
28
- HTTP_COMPRESS: boolean;
29
- /** If you have HTTP resources that require cookies you might to set `SocketFunction.COEP = "require-corp"`
30
- * - Cross-origin-resource-policy.
31
- * NOTE: This COOP and COEP defaults are required so window.crossOriginIsolated will be true.
32
- */
33
- COEP: string;
34
- COOP: string;
35
- TOTAL_CALLS: number;
36
- ENABLE_CLIENT_MODE: boolean;
37
- WIRE_SERIALIZER: {
22
+ static MAX_MESSAGE_SIZE: number;
23
+ static HTTP_ETAG_CACHE: boolean;
24
+ static silent: boolean;
25
+ static HTTP_COMPRESS: boolean;
26
+ static COEP: string;
27
+ static COOP: string;
28
+ static TOTAL_CALLS: number;
29
+ static ENABLE_CLIENT_MODE: boolean;
30
+ static readonly WIRE_SERIALIZER: {
38
31
  serialize: (obj: unknown) => MaybePromise<Buffer[]>;
39
32
  deserialize: (buffers: Buffer[]) => MaybePromise<unknown>;
40
33
  };
41
34
  /** We will try the alternate node IDs first, however, if they fail, we will go through all of them and then eventually try the original node ID.
42
35
  * VERY useful, allowing us to change global ips to local ones, which short-circuits the router, massively increasing bandwidth and decreasing latency.
43
36
  */
44
- GET_ALTERNATE_NODE_IDS: (nodeId: string) => MaybePromise<string[] | undefined>;
45
- WIRE_WARN_TIME: number;
46
- /** Process-wide compression kill switch. When set before connections are established, LZ4 is left out of the protocol negotiation entirely (both for connections we initiate and ones we accept), so NEITHER side compresses — the wire format stays the plain backwards-compatible one. Overrides per-function `compress` flags. */
47
- DISABLE_COMPRESSION: boolean;
48
- }
49
- type ExtractShape<ClassType, Shape> = {
50
- [key in keyof ClassType]: (key extends keyof Shape ? ClassType[key] extends SocketExposedInterface[""] ? ClassType[key] : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never : "Function has implementation but is not exposed in the SocketFunction.register call");
51
- };
52
- export declare class SocketFunction {
53
- static logMessages: SocketFunctionConfig["logMessages"];
54
- static trackMessageSizes: SocketFunctionConfig["trackMessageSizes"];
55
- static MAX_MESSAGE_SIZE: SocketFunctionConfig["MAX_MESSAGE_SIZE"];
56
- static HTTP_ETAG_CACHE: SocketFunctionConfig["HTTP_ETAG_CACHE"];
57
- static silent: SocketFunctionConfig["silent"];
58
- static HTTP_COMPRESS: SocketFunctionConfig["HTTP_COMPRESS"];
59
- static COEP: SocketFunctionConfig["COEP"];
60
- static COOP: SocketFunctionConfig["COOP"];
61
- static TOTAL_CALLS: SocketFunctionConfig["TOTAL_CALLS"];
62
- static ENABLE_CLIENT_MODE: SocketFunctionConfig["ENABLE_CLIENT_MODE"];
63
- static readonly WIRE_SERIALIZER: SocketFunctionConfig["WIRE_SERIALIZER"];
64
- static GET_ALTERNATE_NODE_IDS: SocketFunctionConfig["GET_ALTERNATE_NODE_IDS"];
65
- static WIRE_WARN_TIME: SocketFunctionConfig["WIRE_WARN_TIME"];
66
- static DISABLE_COMPRESSION: SocketFunctionConfig["DISABLE_COMPRESSION"];
37
+ static GET_ALTERNATE_NODE_IDS: (nodeId: string) => MaybePromise<string[] | undefined>;
38
+ static WIRE_WARN_TIME: number;
39
+ static DISABLE_COMPRESSION: boolean;
67
40
  static isClient(): boolean;
68
41
  private static onMountCallbacks;
69
42
  private static exposedClassesSingleton;
package/SocketFunction.ts CHANGED
@@ -45,86 +45,69 @@ const socketContext = createSingleton("SocketFunction.socketContext", 1, () => (
45
45
  caller: undefined as CallerContext | undefined,
46
46
  }));
47
47
 
48
- /** The values behind SocketFunction's configuration statics. They are exposed on the class as
49
- * accessors onto a singleton (see the defineSingletonConfig call at the bottom of this file), so
50
- * that configuration set through one copy of this package applies to all of them. This interface
51
- * is the single source of truth for their types - the class declares each one as
52
- * `declare static X: SocketFunctionConfig["X"]`.
53
- */
54
- export interface SocketFunctionConfig {
55
- logMessages: boolean;
56
- trackMessageSizes: {
57
- upload: ((size: number, nodeId: string) => void)[];
58
- download: ((size: number, nodeId: string) => void)[];
59
- callTimes: ((obj: { start: number; end: number; nodeId: string; }) => void)[];
48
+ type ExtractShape<ClassType, Shape> = {
49
+ [key in keyof ClassType]: (
50
+ key extends keyof Shape
51
+ ? ClassType[key] extends SocketExposedInterface[""]
52
+ ? ClassType[key]
53
+ : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never
54
+ : "Function has implementation but is not exposed in the SocketFunction.register call"
55
+ );
56
+ };
57
+
58
+ export class SocketFunction {
59
+
60
+
61
+ // #region Shared config statics. EVERYTHING in this region (and nothing outside it) is redefined
62
+ // below the class by defineSingletonConfig, which replaces each property (via defineProperty)
63
+ // with accessors onto a singleton shared by every copy of this package - the inline values here
64
+ // are just the defaults.
65
+ public static logMessages = false;
66
+ public static trackMessageSizes = {
67
+ upload: [] as ((size: number, nodeId: string) => void)[],
68
+ download: [] as ((size: number, nodeId: string) => void)[],
69
+ callTimes: [] as ((obj: { start: number; end: number; nodeId: string; }) => void)[],
60
70
  };
61
71
 
62
- MAX_MESSAGE_SIZE: number;
72
+ public static MAX_MESSAGE_SIZE = 1024 * 1024 * 32;
63
73
 
64
- HTTP_ETAG_CACHE: boolean;
65
- silent: boolean;
74
+ public static HTTP_ETAG_CACHE = false;
75
+ public static silent = true;
66
76
 
67
- HTTP_COMPRESS: boolean;
77
+ public static HTTP_COMPRESS = false;
68
78
 
69
- /** If you have HTTP resources that require cookies you might to set `SocketFunction.COEP = "require-corp"`
70
- * - Cross-origin-resource-policy.
71
- * NOTE: This COOP and COEP defaults are required so window.crossOriginIsolated will be true.
72
- */
73
- COEP: string;
74
- COOP: string;
79
+ // If you have HTTP resources that require cookies you might to set `SocketFunction.COEP = "require-corp"`
80
+ // - Cross-origin-resource-policy.
81
+ public static COEP = "credentialless";
82
+ // NOTE: This COOP and COEP defaults are required so window.crossOriginIsolated will be true.
83
+ public static COOP = "same-origin";
75
84
 
76
- TOTAL_CALLS: number;
85
+ public static TOTAL_CALLS = 0;
77
86
 
78
- ENABLE_CLIENT_MODE: boolean;
87
+ public static ENABLE_CLIENT_MODE = false;
79
88
 
80
89
  // In retrospect... dynamically changing the wire serializer is a BAD idea. If any calls happen
81
90
  // before it is changed, things just break. Also, it needs to be changed on both sides,
82
91
  // or else things break. Also, it is very hard to detect when the issue is different serializers
83
92
  // NOTE: The only reason this is still exposed is in case in the future we want to intercept our traffic, and we want convenient functions to know how to decode it (although there are a still few other layers under this, for compression and Buffer[] sending efficiency).
84
- WIRE_SERIALIZER: {
85
- serialize: (obj: unknown) => MaybePromise<Buffer[]>;
86
- deserialize: (buffers: Buffer[]) => MaybePromise<unknown>;
93
+ public static readonly WIRE_SERIALIZER = {
94
+ serialize: measureWrap((obj: unknown): MaybePromise<Buffer[]> => [cborxInstance.encode(obj)], "WIRE_SERIALIZER|serialize"),
95
+ deserialize: measureWrap((buffers: Buffer[]): MaybePromise<unknown> => cborxInstance.decode(buffers[0]), "WIRE_SERIALIZER|deserialize"),
87
96
  };
88
97
 
89
98
  /** We will try the alternate node IDs first, however, if they fail, we will go through all of them and then eventually try the original node ID.
90
99
  * VERY useful, allowing us to change global ips to local ones, which short-circuits the router, massively increasing bandwidth and decreasing latency.
91
100
  */
92
- GET_ALTERNATE_NODE_IDS: (nodeId: string) => MaybePromise<string[] | undefined>;
101
+ public static GET_ALTERNATE_NODE_IDS = (nodeId: string): MaybePromise<string[] | undefined> => undefined;
93
102
 
94
- WIRE_WARN_TIME: number;
103
+ public static WIRE_WARN_TIME = 100;
104
+
105
+ // Process-wide compression kill switch. When set before connections are established, LZ4 is left out of the protocol negotiation entirely (both for connections we initiate and ones we accept), so NEITHER side compresses — the wire format stays the plain backwards-compatible one. Overrides per-function `compress` flags.
106
+ public static DISABLE_COMPRESSION = false;
107
+ // #endregion Shared config statics.
95
108
 
96
- /** Process-wide compression kill switch. When set before connections are established, LZ4 is left out of the protocol negotiation entirely (both for connections we initiate and ones we accept), so NEITHER side compresses — the wire format stays the plain backwards-compatible one. Overrides per-function `compress` flags. */
97
- DISABLE_COMPRESSION: boolean;
98
- }
99
109
 
100
- type ExtractShape<ClassType, Shape> = {
101
- [key in keyof ClassType]: (
102
- key extends keyof Shape
103
- ? ClassType[key] extends SocketExposedInterface[""]
104
- ? ClassType[key]
105
- : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never
106
- : "Function has implementation but is not exposed in the SocketFunction.register call"
107
- );
108
- };
109
110
 
110
- export class SocketFunction {
111
- // All of these are accessors onto a shared singleton, installed after the class declaration.
112
- // See SocketFunctionConfig for their documentation, and defineSingletonConfig for why they
113
- // cannot be plain static fields.
114
- public declare static logMessages: SocketFunctionConfig["logMessages"];
115
- public declare static trackMessageSizes: SocketFunctionConfig["trackMessageSizes"];
116
- public declare static MAX_MESSAGE_SIZE: SocketFunctionConfig["MAX_MESSAGE_SIZE"];
117
- public declare static HTTP_ETAG_CACHE: SocketFunctionConfig["HTTP_ETAG_CACHE"];
118
- public declare static silent: SocketFunctionConfig["silent"];
119
- public declare static HTTP_COMPRESS: SocketFunctionConfig["HTTP_COMPRESS"];
120
- public declare static COEP: SocketFunctionConfig["COEP"];
121
- public declare static COOP: SocketFunctionConfig["COOP"];
122
- public declare static TOTAL_CALLS: SocketFunctionConfig["TOTAL_CALLS"];
123
- public declare static ENABLE_CLIENT_MODE: SocketFunctionConfig["ENABLE_CLIENT_MODE"];
124
- public declare static readonly WIRE_SERIALIZER: SocketFunctionConfig["WIRE_SERIALIZER"];
125
- public declare static GET_ALTERNATE_NODE_IDS: SocketFunctionConfig["GET_ALTERNATE_NODE_IDS"];
126
- public declare static WIRE_WARN_TIME: SocketFunctionConfig["WIRE_WARN_TIME"];
127
- public declare static DISABLE_COMPRESSION: SocketFunctionConfig["DISABLE_COMPRESSION"];
128
111
 
129
112
  // Places where we decide if we want to act as a client. Most places we check for is node, but some places it's not, depending on if we're in Node.js or not, it's depending on if we're a client or not.
130
113
  public static isClient() { return !isNode() || SocketFunction.ENABLE_CLIENT_MODE; }
@@ -541,29 +524,22 @@ export class SocketFunction {
541
524
  }
542
525
  }
543
526
 
544
- defineSingletonConfig<SocketFunctionConfig>(SocketFunction, "SocketFunction.config", 1, () => ({
545
- logMessages: false,
546
- trackMessageSizes: {
547
- upload: [],
548
- download: [],
549
- callTimes: [],
550
- },
551
- MAX_MESSAGE_SIZE: 1024 * 1024 * 32,
552
- HTTP_ETAG_CACHE: false,
553
- silent: true,
554
- HTTP_COMPRESS: false,
555
- COEP: "credentialless",
556
- COOP: "same-origin",
557
- TOTAL_CALLS: 0,
558
- ENABLE_CLIENT_MODE: false,
559
- WIRE_SERIALIZER: {
560
- serialize: measureWrap((obj: unknown): MaybePromise<Buffer[]> => [cborxInstance.encode(obj)], "WIRE_SERIALIZER|serialize"),
561
- deserialize: measureWrap((buffers: Buffer[]): MaybePromise<unknown> => cborxInstance.decode(buffers[0]), "WIRE_SERIALIZER|deserialize"),
562
- },
563
- GET_ALTERNATE_NODE_IDS: (nodeId: string): MaybePromise<string[] | undefined> => undefined,
564
- WIRE_WARN_TIME: 100,
565
- DISABLE_COMPRESSION: false,
566
- }));
527
+ defineSingletonConfig(SocketFunction, "SocketFunction.config", 1, [
528
+ "logMessages",
529
+ "trackMessageSizes",
530
+ "MAX_MESSAGE_SIZE",
531
+ "HTTP_ETAG_CACHE",
532
+ "silent",
533
+ "HTTP_COMPRESS",
534
+ "COEP",
535
+ "COOP",
536
+ "TOTAL_CALLS",
537
+ "ENABLE_CLIENT_MODE",
538
+ "WIRE_SERIALIZER",
539
+ "GET_ALTERNATE_NODE_IDS",
540
+ "WIRE_WARN_TIME",
541
+ "DISABLE_COMPRESSION",
542
+ ]);
567
543
 
568
544
  declare global {
569
545
  var BOOTED_EDGE_NODE: { host: string } | undefined;
package/index.d.ts CHANGED
@@ -14,15 +14,12 @@ declare module "socket-function/SocketFunction" {
14
14
  import { SocketServerConfig } from "socket-function/src/webSocketServer";
15
15
  import { Args, MaybePromise } from "socket-function/src/types";
16
16
  import "./SetProcessVariables";
17
- /** The values behind SocketFunction's configuration statics. They are exposed on the class as
18
- * accessors onto a singleton (see the defineSingletonConfig call at the bottom of this file), so
19
- * that configuration set through one copy of this package applies to all of them. This interface
20
- * is the single source of truth for their types - the class declares each one as
21
- * `declare static X: SocketFunctionConfig["X"]`.
22
- */
23
- export interface SocketFunctionConfig {
24
- logMessages: boolean;
25
- trackMessageSizes: {
17
+ type ExtractShape<ClassType, Shape> = {
18
+ [key in keyof ClassType]: (key extends keyof Shape ? ClassType[key] extends SocketExposedInterface[""] ? ClassType[key] : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never : "Function has implementation but is not exposed in the SocketFunction.register call");
19
+ };
20
+ export declare class SocketFunction {
21
+ static logMessages: boolean;
22
+ static trackMessageSizes: {
26
23
  upload: ((size: number, nodeId: string) => void)[];
27
24
  download: ((size: number, nodeId: string) => void)[];
28
25
  callTimes: ((obj: {
@@ -31,48 +28,24 @@ declare module "socket-function/SocketFunction" {
31
28
  nodeId: string;
32
29
  }) => void)[];
33
30
  };
34
- MAX_MESSAGE_SIZE: number;
35
- HTTP_ETAG_CACHE: boolean;
36
- silent: boolean;
37
- HTTP_COMPRESS: boolean;
38
- /** If you have HTTP resources that require cookies you might to set `SocketFunction.COEP = "require-corp"`
39
- * - Cross-origin-resource-policy.
40
- * NOTE: This COOP and COEP defaults are required so window.crossOriginIsolated will be true.
41
- */
42
- COEP: string;
43
- COOP: string;
44
- TOTAL_CALLS: number;
45
- ENABLE_CLIENT_MODE: boolean;
46
- WIRE_SERIALIZER: {
31
+ static MAX_MESSAGE_SIZE: number;
32
+ static HTTP_ETAG_CACHE: boolean;
33
+ static silent: boolean;
34
+ static HTTP_COMPRESS: boolean;
35
+ static COEP: string;
36
+ static COOP: string;
37
+ static TOTAL_CALLS: number;
38
+ static ENABLE_CLIENT_MODE: boolean;
39
+ static readonly WIRE_SERIALIZER: {
47
40
  serialize: (obj: unknown) => MaybePromise<Buffer[]>;
48
41
  deserialize: (buffers: Buffer[]) => MaybePromise<unknown>;
49
42
  };
50
43
  /** We will try the alternate node IDs first, however, if they fail, we will go through all of them and then eventually try the original node ID.
51
44
  * VERY useful, allowing us to change global ips to local ones, which short-circuits the router, massively increasing bandwidth and decreasing latency.
52
45
  */
53
- GET_ALTERNATE_NODE_IDS: (nodeId: string) => MaybePromise<string[] | undefined>;
54
- WIRE_WARN_TIME: number;
55
- /** Process-wide compression kill switch. When set before connections are established, LZ4 is left out of the protocol negotiation entirely (both for connections we initiate and ones we accept), so NEITHER side compresses — the wire format stays the plain backwards-compatible one. Overrides per-function `compress` flags. */
56
- DISABLE_COMPRESSION: boolean;
57
- }
58
- type ExtractShape<ClassType, Shape> = {
59
- [key in keyof ClassType]: (key extends keyof Shape ? ClassType[key] extends SocketExposedInterface[""] ? ClassType[key] : ClassType[key] extends Function ? "All exposed function must be async (or return a Promise)" : never : "Function has implementation but is not exposed in the SocketFunction.register call");
60
- };
61
- export declare class SocketFunction {
62
- static logMessages: SocketFunctionConfig["logMessages"];
63
- static trackMessageSizes: SocketFunctionConfig["trackMessageSizes"];
64
- static MAX_MESSAGE_SIZE: SocketFunctionConfig["MAX_MESSAGE_SIZE"];
65
- static HTTP_ETAG_CACHE: SocketFunctionConfig["HTTP_ETAG_CACHE"];
66
- static silent: SocketFunctionConfig["silent"];
67
- static HTTP_COMPRESS: SocketFunctionConfig["HTTP_COMPRESS"];
68
- static COEP: SocketFunctionConfig["COEP"];
69
- static COOP: SocketFunctionConfig["COOP"];
70
- static TOTAL_CALLS: SocketFunctionConfig["TOTAL_CALLS"];
71
- static ENABLE_CLIENT_MODE: SocketFunctionConfig["ENABLE_CLIENT_MODE"];
72
- static readonly WIRE_SERIALIZER: SocketFunctionConfig["WIRE_SERIALIZER"];
73
- static GET_ALTERNATE_NODE_IDS: SocketFunctionConfig["GET_ALTERNATE_NODE_IDS"];
74
- static WIRE_WARN_TIME: SocketFunctionConfig["WIRE_WARN_TIME"];
75
- static DISABLE_COMPRESSION: SocketFunctionConfig["DISABLE_COMPRESSION"];
46
+ static GET_ALTERNATE_NODE_IDS: (nodeId: string) => MaybePromise<string[] | undefined>;
47
+ static WIRE_WARN_TIME: number;
48
+ static DISABLE_COMPRESSION: boolean;
76
49
  static isClient(): boolean;
77
50
  private static onMountCallbacks;
78
51
  private static exposedClassesSingleton;
@@ -876,17 +849,16 @@ declare module "socket-function/src/createSingleton" {
876
849
  * get()/set() at each access so every copy sees the latest.
877
850
  */
878
851
  export declare function createSingleton<T>(name: string, version: string | number, getDefault: () => T): Singleton<T>;
879
- /** Installs accessors on target for every key of the config object, with the values living in a
880
- * singleton, so `Target.SOME_SETTING = x` configures every copy of the package instead of only
881
- * the copy the caller happened to import (which is otherwise decided by module resolution, and
882
- * so is essentially arbitrary from the caller's perspective).
883
- * - Declare the properties on the class with `declare static X: Config["X"]`. `declare` emits no
884
- * field, so there is no own property to shadow the accessors installed here, and taking the
885
- * types from the config interface keeps the two from drifting apart.
852
+ /** Redefines the given (already initialized) properties of target as accessors onto a singleton, so
853
+ * `Target.SOME_SETTING = x` configures every copy of the package instead of only the copy the
854
+ * caller happened to import (which is otherwise decided by module resolution, and so is
855
+ * essentially arbitrary from the caller's perspective). The properties' current values become
856
+ * the defaults, so config statics stay defined inline in the class as usual - just call this
857
+ * below the class with the list of statics to share.
886
858
  * - Same versioning rules as createSingleton, except that adding a property is not a shape change:
887
859
  * a copy that knows about a property older copies don't backfills it below.
888
860
  */
889
- export declare function defineSingletonConfig<T extends object>(target: object, name: string, version: string | number, getDefaults: () => T): Singleton<T>;
861
+ export declare function defineSingletonConfig<T extends object>(target: T, name: string, version: string | number, keys: (keyof T & string)[]): void;
890
862
 
891
863
  }
892
864
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.2.28",
3
+ "version": "1.2.30",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -316,7 +316,7 @@ export async function createCallFactory(
316
316
  registerOnce();
317
317
  callFactory.receivedInitializeState = undefined;
318
318
 
319
- function onClose(error: string) {
319
+ function onClose(message: string) {
320
320
  // We try various connections, and if they fail, we will just try other node IDs until we finally do connect, and then we stick with that nodeId, and when it disconnects we need to handle disconnections normally.
321
321
  if (skipCloseHandling && !hasEverConnected) {
322
322
  return;
@@ -335,7 +335,7 @@ export async function createCallFactory(
335
335
  call.callback({
336
336
  isReturn: true,
337
337
  result: undefined,
338
- error: error,
338
+ error: `Call failed for ${call.call.classGuid}.${call.call.functionName}: ${message}`,
339
339
  seqNum: call.call.seqNum,
340
340
  });
341
341
  }
@@ -353,12 +353,12 @@ export async function createCallFactory(
353
353
  // NOTE: No more logging, as we throw, so the caller should be logging the
354
354
  // error (or swallowing it, if that is what it wants to do).
355
355
  //console.log(`Websocket error for ${niceConnectionName}`, e.message);
356
- onClose(new Error(`Connection error for ${niceConnectionName}: ${e.message}`).stack!);
356
+ onClose(`Connection error for ${niceConnectionName}: ${e.message}`);
357
357
  });
358
358
 
359
359
  newWebSocket.addEventListener("close", async () => {
360
360
  //console.log(`Websocket closed ${niceConnectionName}`);
361
- onClose(new Error(`Connection closed to ${niceConnectionName}`).stack!);
361
+ onClose(`Connection closed to ${niceConnectionName}`);
362
362
  });
363
363
 
364
364
  newWebSocket.addEventListener("message", onMessage);
@@ -381,7 +381,7 @@ export async function createCallFactory(
381
381
  callFactory.isConnected = true;
382
382
  hasEverConnected = true;
383
383
  } else {
384
- onClose(new Error(`Websocket received in closed state`).stack!);
384
+ onClose(`Websocket received in closed state`);
385
385
  }
386
386
 
387
387
  if (callFactory.lastClosed && callFactory.isConnected) {
@@ -45,9 +45,14 @@ interface HydrateState {
45
45
  export class JSONLACKS {
46
46
  public static readonly LACKS_KEY = "__JSONLACKS__98cfb4a05fa34d828661cae15b8779ce__";
47
47
 
48
+ // #region Shared config statics. EVERYTHING in this region (and nothing outside it) is redefined
49
+ // below the class by defineSingletonConfig, which replaces each property (via defineProperty)
50
+ // with accessors onto a singleton shared by every copy of this package - the inline values here
51
+ // are just the defaults.
48
52
  /** If set to true parses non-quoted field names, comments, trailing commas, etc */
49
- public declare static EXTENDED_PARSER: boolean;
50
- public declare static IGNORE_MISSING_REFERENCES: boolean;
53
+ public static EXTENDED_PARSER = false;
54
+ public static IGNORE_MISSING_REFERENCES = false;
55
+ // #endregion Shared config statics.
51
56
 
52
57
  public static stringify(obj: unknown, config?: JSONLACKS_StringifyConfig): string {
53
58
  let serialized = JSONLACKS.escapeSpecialObjects(obj, config);
@@ -300,10 +305,10 @@ export class JSONLACKS {
300
305
  }
301
306
  }
302
307
 
303
- defineSingletonConfig(JSONLACKS, "JSONLACKS.config", 1, () => ({
304
- EXTENDED_PARSER: false,
305
- IGNORE_MISSING_REFERENCES: false,
306
- }));
308
+ defineSingletonConfig(JSONLACKS, "JSONLACKS.config", 1, [
309
+ "EXTENDED_PARSER",
310
+ "IGNORE_MISSING_REFERENCES",
311
+ ]);
307
312
 
308
313
  async function benchmark() {
309
314
  const loops = 1000 * 100;
@@ -17,14 +17,13 @@ export interface Singleton<T> {
17
17
  * get()/set() at each access so every copy sees the latest.
18
18
  */
19
19
  export declare function createSingleton<T>(name: string, version: string | number, getDefault: () => T): Singleton<T>;
20
- /** Installs accessors on target for every key of the config object, with the values living in a
21
- * singleton, so `Target.SOME_SETTING = x` configures every copy of the package instead of only
22
- * the copy the caller happened to import (which is otherwise decided by module resolution, and
23
- * so is essentially arbitrary from the caller's perspective).
24
- * - Declare the properties on the class with `declare static X: Config["X"]`. `declare` emits no
25
- * field, so there is no own property to shadow the accessors installed here, and taking the
26
- * types from the config interface keeps the two from drifting apart.
20
+ /** Redefines the given (already initialized) properties of target as accessors onto a singleton, so
21
+ * `Target.SOME_SETTING = x` configures every copy of the package instead of only the copy the
22
+ * caller happened to import (which is otherwise decided by module resolution, and so is
23
+ * essentially arbitrary from the caller's perspective). The properties' current values become
24
+ * the defaults, so config statics stay defined inline in the class as usual - just call this
25
+ * below the class with the list of statics to share.
27
26
  * - Same versioning rules as createSingleton, except that adding a property is not a shape change:
28
27
  * a copy that knows about a property older copies don't backfills it below.
29
28
  */
30
- export declare function defineSingletonConfig<T extends object>(target: object, name: string, version: string | number, getDefaults: () => T): Singleton<T>;
29
+ export declare function defineSingletonConfig<T extends object>(target: T, name: string, version: string | number, keys: (keyof T & string)[]): void;
@@ -75,39 +75,35 @@ export function createSingleton<T>(
75
75
  };
76
76
  }
77
77
 
78
- /** Installs accessors on target for every key of the config object, with the values living in a
79
- * singleton, so `Target.SOME_SETTING = x` configures every copy of the package instead of only
80
- * the copy the caller happened to import (which is otherwise decided by module resolution, and
81
- * so is essentially arbitrary from the caller's perspective).
82
- * - Declare the properties on the class with `declare static X: Config["X"]`. `declare` emits no
83
- * field, so there is no own property to shadow the accessors installed here, and taking the
84
- * types from the config interface keeps the two from drifting apart.
78
+ /** Redefines the given (already initialized) properties of target as accessors onto a singleton, so
79
+ * `Target.SOME_SETTING = x` configures every copy of the package instead of only the copy the
80
+ * caller happened to import (which is otherwise decided by module resolution, and so is
81
+ * essentially arbitrary from the caller's perspective). The properties' current values become
82
+ * the defaults, so config statics stay defined inline in the class as usual - just call this
83
+ * below the class with the list of statics to share.
85
84
  * - Same versioning rules as createSingleton, except that adding a property is not a shape change:
86
85
  * a copy that knows about a property older copies don't backfills it below.
87
86
  */
88
87
  export function defineSingletonConfig<T extends object>(
89
- target: object,
88
+ target: T,
90
89
  name: string,
91
90
  version: string | number,
92
- getDefaults: () => T,
93
- ): Singleton<T> {
94
- const defaults = getDefaults();
95
- const singleton = createSingleton<T>(name, version, () => defaults);
96
- const values = singleton.get() as { [key: string]: unknown };
97
- for (const key of Object.keys(defaults)) {
98
- if (!(key in values)) {
99
- values[key] = (defaults as { [key: string]: unknown })[key];
91
+ keys: (keyof T & string)[],
92
+ ): void {
93
+ const store = createSingleton<{ [key: string]: unknown }>(name, version, () => Object.create(null)).get();
94
+ for (const key of keys) {
95
+ if (!(key in store)) {
96
+ store[key] = target[key];
100
97
  }
101
98
  Object.defineProperty(target, key, {
102
99
  get() {
103
- return (singleton.get() as { [key: string]: unknown })[key];
100
+ return store[key];
104
101
  },
105
102
  set(value: unknown) {
106
- (singleton.get() as { [key: string]: unknown })[key] = value;
103
+ store[key] = value;
107
104
  },
108
105
  enumerable: true,
109
106
  configurable: true,
110
107
  });
111
108
  }
112
- return singleton;
113
109
  }
package/src/misc.ts CHANGED
@@ -15,7 +15,11 @@ export type Watchable<T> = (callback: (value: T) => void) => MaybePromise<void>;
15
15
  export function convertErrorStackToError(error: string): Error {
16
16
  let errorObj = new Error();
17
17
  errorObj.stack = String(error);
18
- errorObj.message = String(error).split("\n")[0].slice("Error: ".length);
18
+ let message = String(error).split("\n")[0];
19
+ if (message.startsWith("Error: ")) {
20
+ message = message.slice("Error: ".length);
21
+ }
22
+ errorObj.message = message;
19
23
  return errorObj;
20
24
  }
21
25