socket-function 0.15.0 → 0.17.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/SocketFunction.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference path="./require/RequireController.ts" />
2
2
 
3
- import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType, CallType } from "./SocketFunctionTypes";
3
+ import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType, CallType, FncType, SocketRegisterType } from "./SocketFunctionTypes";
4
4
  import { exposeClass, registerClass, registerGlobalClientHook, registerGlobalHook, runClientHooks } from "./src/callManager";
5
5
  import { SocketServerConfig, startSocketServer } from "./src/webSocketServer";
6
6
  import { getCallFactory, getCreateCallFactory, getNodeId, getNodeIdLocation } from "./src/nodeCache";
@@ -84,7 +84,6 @@ export class SocketFunction {
84
84
  return caller;
85
85
  }
86
86
 
87
- private static getShapeHotReloadable = new Map<string, () => SocketExposedShape<SocketExposedInterface>>();
88
87
  // NOTE: We use callbacks we don't run into issues with cyclic dependencies
89
88
  // (ex, using a hook in a controller where the hook also calls the controller).
90
89
  public static register<
@@ -113,6 +112,18 @@ export class SocketFunction {
113
112
  noFunctionMeasure?: boolean;
114
113
  }
115
114
  ): SocketRegistered<ExtractShape<ClassInstance, Shape>> & Statics {
115
+ void Promise.resolve().then(() => {
116
+ let onMount = getDefaultHooks?.().onMount;
117
+ if (onMount) {
118
+ let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
119
+ if (!callbacks) {
120
+ callbacks = [];
121
+ SocketFunction.onMountCallbacks.set(classGuid, callbacks);
122
+ }
123
+ callbacks.push(onMount);
124
+ }
125
+ });
126
+
116
127
  let getDefaultHooks = defaultHooksFnc && lazy(defaultHooksFnc);
117
128
  const getShape = lazy(() => {
118
129
  let shape = shapeFnc() as SocketExposedShape;
@@ -142,58 +153,76 @@ export class SocketFunction {
142
153
  });
143
154
  });
144
155
 
145
- let nodeProxy = getCallProxy(classGuid, async (call) => {
146
- let nodeId = call.nodeId;
147
- let functionName = call.functionName;
148
- let time = Date.now();
149
- if (SocketFunction.logMessages) {
150
- console.log(`START\t\t\t${classGuid}.${functionName} at ${Date.now()}`);
151
- }
152
- try {
153
- let callFactory = await getCreateCallFactory(nodeId);
156
+ let socketCaller = SocketFunction.rehydrateSocketCaller({
157
+ _classGuid: classGuid,
158
+ _internalType: null as any,
159
+ }, getShape);
154
160
 
155
- let shapeObj = getShape()[functionName];
156
- if (!shapeObj) {
157
- throw new Error(`Function ${functionName} is not in shape`);
158
- }
161
+ if (!config?.noAutoExpose) {
162
+ this.expose(socketCaller);
163
+ }
164
+ return Object.assign(socketCaller, config?.statics);
165
+ }
159
166
 
160
- let hookResult = await runClientHooks(call, shapeObj as Exclude<SocketExposedShape[""], undefined>, callFactory.connectionId);
167
+ private static socketCache = new Map<string, SocketRegistered>();
168
+ public static rehydrateSocketCaller<Controller>(
169
+ socketRegistered: SocketRegisterType<Controller>,
170
+ // Shape is required for client hooks.
171
+ shapeFnc?: () => SocketExposedShape,
172
+ ): SocketRegistered<Controller> {
173
+ let cached = this.socketCache.get(socketRegistered._classGuid);
174
+ if (!cached) {
175
+ let getShape = lazy(() => shapeFnc?.());
176
+ let classGuid = socketRegistered._classGuid;
177
+ let nodeProxy = getCallProxy(classGuid, async (call) => {
178
+ return await SocketFunction.callFromGuid(call, classGuid, getShape());
179
+ });
161
180
 
162
- if ("overrideResult" in hookResult) {
163
- return hookResult.overrideResult;
164
- }
181
+ cached = {
182
+ nodes: nodeProxy,
183
+ _classGuid: classGuid,
184
+ _internalType: null as any,
185
+ };
165
186
 
166
- return await callFactory.performCall(call);
167
- } finally {
168
- time = Date.now() - time;
169
- if (SocketFunction.logMessages) {
170
- console.log(`FINISHED\t${time}ms\t${classGuid}.${functionName} at ${Date.now()}`);
171
- }
172
- }
173
- });
187
+ this.socketCache.set(classGuid, cached);
188
+ }
189
+ return cached;
190
+ }
174
191
 
175
- let output: SocketRegistered = {
176
- nodes: nodeProxy,
177
- _classGuid: classGuid,
178
- };
192
+ private static async callFromGuid<FncT extends FncType>(
193
+ call: FullCallType<FncT>,
194
+ classGuid: string,
195
+ shape?: SocketExposedShape,
196
+ ): Promise<ReturnType<FncType>> {
197
+ let nodeId = call.nodeId;
198
+ let functionName = call.functionName;
199
+ let time = Date.now();
200
+ if (SocketFunction.logMessages) {
201
+ console.log(`START\t\t\t${classGuid}.${functionName} at ${Date.now()}`);
202
+ }
203
+ try {
204
+ let callFactory = await getCreateCallFactory(nodeId);
179
205
 
180
- void Promise.resolve().then(() => {
181
- let onMount = getDefaultHooks?.().onMount;
182
- if (onMount) {
183
- let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
184
- if (!callbacks) {
185
- callbacks = [];
186
- SocketFunction.onMountCallbacks.set(classGuid, callbacks);
187
- }
188
- callbacks.push(onMount);
206
+ let shapeObj = shape?.[functionName];
207
+ // NOTE: Actually... this just means the client doesn't have a definition for it. The server
208
+ // might, so call it, and let them throw if it is unrecognized.
209
+ // if (!shapeObj) {
210
+ // throw new Error(`Function ${functionName} is not in shape`);
211
+ // }
212
+
213
+ let hookResult = await runClientHooks(call, shapeObj as Exclude<SocketExposedShape[""], undefined>, callFactory.connectionId);
214
+
215
+ if ("overrideResult" in hookResult) {
216
+ return hookResult.overrideResult;
189
217
  }
190
- });
191
218
 
192
- let result = output as any as SocketRegistered;
193
- if (!config?.noAutoExpose) {
194
- this.expose(result);
219
+ return await callFactory.performCall(call);
220
+ } finally {
221
+ time = Date.now() - time;
222
+ if (SocketFunction.logMessages) {
223
+ console.log(`FINISHED\t${time}ms\t${classGuid}.${functionName} at ${Date.now()}`);
224
+ }
195
225
  }
196
- return Object.assign(result, config?.statics);
197
226
  }
198
227
 
199
228
  public static onNextDisconnect(nodeId: string, callback: () => void) {
@@ -42,12 +42,13 @@ export type SocketExposedShape<ExposedType extends SocketExposedInterface = Sock
42
42
  };
43
43
  };
44
44
 
45
- export interface CallType {
45
+ export type FncType = (...args: any[]) => Promise<unknown>;
46
+ export interface CallType<FncT extends FncType = FncType, FncName extends string = string> {
46
47
  classGuid: string;
47
- functionName: string;
48
+ functionName: FncName;
48
49
  args: unknown[];
49
50
  }
50
- export interface FullCallType extends CallType {
51
+ export interface FullCallType<FncT extends FncType = FncType, FncName extends string = string> extends CallType<FncT, FncName> {
51
52
  nodeId: string;
52
53
  }
53
54
 
@@ -72,6 +73,11 @@ export interface SocketFunctionClientHook<ExposedType extends SocketExposedInter
72
73
  (config: ClientHookContext<ExposedType>): MaybePromise<void>;
73
74
  }
74
75
 
76
+ export interface SocketRegisterType<ExposedType = any> {
77
+ _classGuid: string;
78
+ _internalType: ExposedType;
79
+ }
80
+
75
81
  export interface SocketRegistered<ExposedType = any> {
76
82
  nodes: {
77
83
  // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
@@ -83,7 +89,11 @@ export interface SocketRegistered<ExposedType = any> {
83
89
  };
84
90
  };
85
91
  _classGuid: string;
92
+ _internalType: ExposedType;
86
93
  }
94
+ export type ControllerPick<T extends SocketRegistered, K extends keyof T["_internalType"]> = (
95
+ SocketRegistered<Pick<T["_internalType"], K>>
96
+ );
87
97
  export type CallerContext = Readonly<CallerContextBase>;
88
98
  export type CallerContextBase = {
89
99
  // IMPORTANT! Do not pass nodeId to other nodes with the intention of having
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
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",
package/src/batching.ts CHANGED
@@ -149,7 +149,12 @@ export function batchFunction<Arg, Result = void>(
149
149
  let curPrevPromise = prevPromise;
150
150
  let args: Arg[] = [arg];
151
151
  let promise = Promise.resolve().then(async () => {
152
- await curPrevPromise;
152
+ // Ignore the error. New callers don't care about errors in previous calls,
153
+ // as they are unrelated to the current call, and just break valid calls
154
+ // due to invalid calls.
155
+ try {
156
+ await curPrevPromise;
157
+ } catch { }
153
158
  await delay(curDelay, "immediateShortDelays");
154
159
  // Reset batching, as we once we start the function we can't accept args. `prevPromise` will block
155
160
  // the next batch from starting before we finish.