socket-function 0.10.2 → 0.10.4
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 +4 -2
- package/hot/HotReloadController.ts +11 -0
- package/package.json +2 -2
- package/require/RequireController.ts +38 -3
- package/require/require.html +1 -0
- package/require/require.js +6 -0
- package/src/batching.ts +14 -18
- package/src/formatting/format.ts +42 -0
- package/src/misc.ts +1 -1
- package/src/profiling/measure.ts +21 -0
package/SocketFunction.ts
CHANGED
|
@@ -65,6 +65,7 @@ export class SocketFunction {
|
|
|
65
65
|
Shape extends SocketExposedShape<{
|
|
66
66
|
[key in keyof ClassInstance]: (...args: any[]) => Promise<unknown>;
|
|
67
67
|
}>,
|
|
68
|
+
Statics
|
|
68
69
|
>(
|
|
69
70
|
classGuid: string,
|
|
70
71
|
instance: ClassInstance,
|
|
@@ -75,8 +76,9 @@ export class SocketFunction {
|
|
|
75
76
|
config?: {
|
|
76
77
|
/** @noAutoExpose If true SocketFunction.expose(Controller) must be called explicitly. */
|
|
77
78
|
noAutoExpose?: boolean;
|
|
79
|
+
statics?: Statics;
|
|
78
80
|
}
|
|
79
|
-
): SocketRegistered<ExtractShape<ClassInstance, Shape>> {
|
|
81
|
+
): SocketRegistered<ExtractShape<ClassInstance, Shape>> & Statics {
|
|
80
82
|
let getDefaultHooks = defaultHooksFnc && lazy(defaultHooksFnc);
|
|
81
83
|
const getShape = lazy(() => {
|
|
82
84
|
let shape = shapeFnc() as SocketExposedShape;
|
|
@@ -146,7 +148,7 @@ export class SocketFunction {
|
|
|
146
148
|
if (!config?.noAutoExpose) {
|
|
147
149
|
this.expose(result);
|
|
148
150
|
}
|
|
149
|
-
return result;
|
|
151
|
+
return Object.assign(result, config?.statics);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
public static onNextDisconnect(nodeId: string, callback: () => void) {
|
|
@@ -41,6 +41,14 @@ declare global {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
let isHotReloadingValue = false;
|
|
45
|
+
export function isHotReloading() {
|
|
46
|
+
return isHotReloadingValue;
|
|
47
|
+
}
|
|
48
|
+
export function setExternalHotReloading(value: boolean) {
|
|
49
|
+
isHotReloadingValue = value;
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
const hotReloadModule = cache((module: NodeJS.Module) => {
|
|
45
53
|
if (!module.updateContents) return;
|
|
46
54
|
fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
|
|
@@ -55,12 +63,15 @@ const hotReloadModule = cache((module: NodeJS.Module) => {
|
|
|
55
63
|
|| module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
|
|
56
64
|
) {
|
|
57
65
|
console.log(`Reloading ${module.id}`);
|
|
66
|
+
isHotReloadingValue = true;
|
|
58
67
|
try {
|
|
59
68
|
module.loaded = false;
|
|
60
69
|
module.load(module.id);
|
|
61
70
|
} catch (e) {
|
|
62
71
|
console.error(red(`Error hot reloading ${module.id}`));
|
|
63
72
|
console.error(e);
|
|
73
|
+
} finally {
|
|
74
|
+
isHotReloadingValue = false;
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "socket-function",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
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",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"mobx": "^6.6.2",
|
|
12
12
|
"node-forge": "https://github.com/sliftist/forge#name",
|
|
13
13
|
"preact": "^10.10.6",
|
|
14
|
-
"typenode": "^5.3.
|
|
14
|
+
"typenode": "^5.3.8",
|
|
15
15
|
"ws": "^8.8.0"
|
|
16
16
|
},
|
|
17
17
|
"optionalDependencies": {
|
|
@@ -64,6 +64,8 @@ const requireSeqNumProcessId = "requireSeqNumProcessId_" + Date.now() + "_" + Ma
|
|
|
64
64
|
const htmlFile = isNodeTrue() && fs.readFileSync(__dirname + "/require.html").toString();
|
|
65
65
|
const jsFile = isNodeTrue() && fs.readFileSync(__dirname + "/require.js").toString();
|
|
66
66
|
const bufferShim = isNodeTrue() && fs.readFileSync(__dirname + "/buffer.js").toString();
|
|
67
|
+
const BEFORE_ENTRY_TEMPLATE = "<!-- BEFORE_ENTRY_TEMPLATE -->";
|
|
68
|
+
const ENTRY_TEMPLATE = "<!-- ENTRY_TEMPLATE -->";
|
|
67
69
|
|
|
68
70
|
const resolvedHTMLFile = isNodeTrue() && (
|
|
69
71
|
htmlFile
|
|
@@ -71,13 +73,30 @@ const resolvedHTMLFile = isNodeTrue() && (
|
|
|
71
73
|
.replace(`<script src="./require.js"></script>`, `<script>${jsFile}</script>`)
|
|
72
74
|
);
|
|
73
75
|
|
|
76
|
+
let beforeEntryText: string[] = [];
|
|
77
|
+
function injectHTMLBeforeStartup(text: string) {
|
|
78
|
+
beforeEntryText.push(text);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
|
|
82
|
+
type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
|
|
83
|
+
let mapGetModules: {
|
|
84
|
+
remap(result: GetModulesResult, args: GetModulesArgs): Promise<GetModulesResult>
|
|
85
|
+
}[] = [];
|
|
86
|
+
function addMapGetModules(remap: typeof mapGetModules[number]["remap"]) {
|
|
87
|
+
mapGetModules.push({ remap });
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
class RequireControllerBase {
|
|
75
91
|
public rootResolvePath = "";
|
|
76
92
|
|
|
77
93
|
public async requireHTML(bootRequirePath?: string) {
|
|
78
94
|
let result = resolvedHTMLFile;
|
|
95
|
+
if (beforeEntryText.length > 0) {
|
|
96
|
+
result = result.replace(BEFORE_ENTRY_TEMPLATE, beforeEntryText.join("\n"));
|
|
97
|
+
}
|
|
79
98
|
if (bootRequirePath) {
|
|
80
|
-
result = result.replace(
|
|
99
|
+
result = result.replace(ENTRY_TEMPLATE, `<script>require(${JSON.stringify(bootRequirePath)});</script>`);
|
|
81
100
|
}
|
|
82
101
|
return setHTTPResultHeaders(Buffer.from(result), { "Content-Type": "text/html" });
|
|
83
102
|
}
|
|
@@ -101,6 +120,7 @@ class RequireControllerBase {
|
|
|
101
120
|
e?: number;
|
|
102
121
|
}[];
|
|
103
122
|
},
|
|
123
|
+
config?: {}
|
|
104
124
|
): Promise<{
|
|
105
125
|
requestsResolvedPaths: string[];
|
|
106
126
|
modules: {
|
|
@@ -232,10 +252,21 @@ class RequireControllerBase {
|
|
|
232
252
|
addModule(clientModule, true);
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
|
|
255
|
+
let result: GetModulesResult = { requestsResolvedPaths, modules, requireSeqNumProcessId };
|
|
256
|
+
for (let remap of mapGetModules) {
|
|
257
|
+
result = await remap.remap(result, [pathRequests, alreadyHave, config]);
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
236
260
|
}
|
|
237
261
|
}
|
|
238
262
|
|
|
263
|
+
type ClientRemapCallback = (args: GetModulesArgs) => Promise<GetModulesArgs>;
|
|
264
|
+
declare global {
|
|
265
|
+
/** Must be set clientside BEFORE requests are made (so you likely want to use RequireController.addMapGetModules
|
|
266
|
+
* to inject code that will use this) */
|
|
267
|
+
var remapImportRequestsClientside: undefined | ClientRemapCallback[];
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
let baseController = new RequireControllerBase();
|
|
240
271
|
export function setRequireBootRequire(path: string) {
|
|
241
272
|
baseController.rootResolvePath = path;
|
|
@@ -252,6 +283,10 @@ export const RequireController = SocketFunction.register(
|
|
|
252
283
|
}),
|
|
253
284
|
undefined,
|
|
254
285
|
{
|
|
255
|
-
noAutoExpose: true
|
|
286
|
+
noAutoExpose: true,
|
|
287
|
+
statics: {
|
|
288
|
+
injectHTMLBeforeStartup,
|
|
289
|
+
addMapGetModules,
|
|
290
|
+
}
|
|
256
291
|
}
|
|
257
292
|
);
|
package/require/require.html
CHANGED
package/require/require.js
CHANGED
|
@@ -166,6 +166,12 @@
|
|
|
166
166
|
args.push(true);
|
|
167
167
|
}
|
|
168
168
|
let requestUrl = location.origin + location.pathname + `?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules`;
|
|
169
|
+
let remapImportRequestsClientside = globalThis.remapImportRequestsClientside;
|
|
170
|
+
if (remapImportRequestsClientside) {
|
|
171
|
+
for (let fnc of remapImportRequestsClientside) {
|
|
172
|
+
args = await fnc(args);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
169
175
|
let rawText = await requestText(requestUrl, { args });
|
|
170
176
|
let resultObj;
|
|
171
177
|
try {
|
package/src/batching.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function batchFunction<Arg, Result = void>(
|
|
|
61
61
|
fnc = measureWrap(fnc, config.name);
|
|
62
62
|
|
|
63
63
|
let prevPromise: Promise<Result> | undefined;
|
|
64
|
-
let
|
|
64
|
+
let batching: {
|
|
65
65
|
args: Arg[];
|
|
66
66
|
promise: Promise<Result>;
|
|
67
67
|
} | undefined;
|
|
@@ -76,7 +76,8 @@ export function batchFunction<Arg, Result = void>(
|
|
|
76
76
|
}
|
|
77
77
|
let countSinceBreak = 0;
|
|
78
78
|
let lastCall = 0;
|
|
79
|
-
|
|
79
|
+
|
|
80
|
+
return arg => {
|
|
80
81
|
let now = Date.now();
|
|
81
82
|
if (delayRamp) {
|
|
82
83
|
let savedCount = (now - lastCall) / delayTime;
|
|
@@ -94,30 +95,25 @@ export function batchFunction<Arg, Result = void>(
|
|
|
94
95
|
}
|
|
95
96
|
lastCall = now;
|
|
96
97
|
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (batched) {
|
|
101
|
-
batched.args.push(arg);
|
|
102
|
-
return await batched.promise;
|
|
98
|
+
if (batching) {
|
|
99
|
+
batching.args.push(arg);
|
|
100
|
+
return batching.promise;
|
|
103
101
|
}
|
|
104
102
|
|
|
103
|
+
let curPrevPromise = prevPromise;
|
|
105
104
|
let args: Arg[] = [arg];
|
|
106
105
|
let promise = Promise.resolve().then(async () => {
|
|
106
|
+
await curPrevPromise;
|
|
107
107
|
await delay(curDelay);
|
|
108
|
-
//
|
|
109
|
-
|
|
108
|
+
// Reset batching, as we once we start the function we can't accept args. `prevPromise` will block
|
|
109
|
+
// the next batch from starting before we finish.
|
|
110
|
+
batching = undefined;
|
|
110
111
|
return await fnc(args);
|
|
111
112
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
promise,
|
|
115
|
-
};
|
|
116
|
-
// We need to prevent new calls from starting when the previous call can no longer accept
|
|
117
|
-
// args, BUT, before it has finished.
|
|
118
|
-
prevPromise = batched.promise;
|
|
113
|
+
batching = { args, promise, };
|
|
114
|
+
prevPromise = batching.promise;
|
|
119
115
|
|
|
120
|
-
return
|
|
116
|
+
return promise;
|
|
121
117
|
};
|
|
122
118
|
}
|
|
123
119
|
|
package/src/formatting/format.ts
CHANGED
|
@@ -162,5 +162,47 @@ export function formatNumber(count: number | undefined, maxAbsoluteValue?: numbe
|
|
|
162
162
|
|
|
163
163
|
let maxDecimals = noDecimal ? 0 : 3;
|
|
164
164
|
|
|
165
|
+
return formatMaxDecimals(count, maxDecimals, maxAbsoluteValue, currencyDecimalsNeeded ? 2 : undefined) + suffix;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function formatBinaryNumber(count: number | undefined, maxAbsoluteValue?: number, noDecimal?: boolean, specialCurrency?: boolean): string {
|
|
169
|
+
if (typeof count !== "number") return "0";
|
|
170
|
+
if (count < 0) {
|
|
171
|
+
return "-" + formatNumber(-count, maxAbsoluteValue, noDecimal, specialCurrency);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
maxAbsoluteValue = maxAbsoluteValue ?? Math.abs(count);
|
|
175
|
+
|
|
176
|
+
// NOTE: We don't switch units as soon as we possible can, because...
|
|
177
|
+
// 3.594 vs 3.584 is harder to quickly distinguish compared to 3594 and 3584,
|
|
178
|
+
// the decimal simply makes it harder to read, and larger.
|
|
179
|
+
// NOTE: This number should prevent us from ever using 5 digits, so that we never need commas
|
|
180
|
+
// For example, if the factor is 10 then, 9999.5 is kept with a divisor of 1, and then rounds up to
|
|
181
|
+
// "10,000". So we want any value which rounds up at 5 digits to be put in the next group (and having
|
|
182
|
+
// extra values put in the next group is fine, as we won't show the decimal value anyways, so it only
|
|
183
|
+
// means 9999 wraps around to 10K a bit faster).
|
|
184
|
+
const extraFactor = 9.99949999;
|
|
185
|
+
let divisor = 1;
|
|
186
|
+
let suffix = "";
|
|
187
|
+
let currencyDecimalsNeeded = false;
|
|
188
|
+
if (maxAbsoluteValue < 1024 * extraFactor) {
|
|
189
|
+
if (specialCurrency) {
|
|
190
|
+
currencyDecimalsNeeded = true;
|
|
191
|
+
}
|
|
192
|
+
} else if (maxAbsoluteValue < 1024 * 1024 * extraFactor) {
|
|
193
|
+
suffix = "K";
|
|
194
|
+
divisor = 1024;
|
|
195
|
+
} else if (maxAbsoluteValue < 1024 * 1024 * 1024 * extraFactor) {
|
|
196
|
+
suffix = "M";
|
|
197
|
+
divisor = 1024 * 1024;
|
|
198
|
+
} else {
|
|
199
|
+
suffix = "G";
|
|
200
|
+
divisor = 1024 * 1024 * 1024;
|
|
201
|
+
}
|
|
202
|
+
count /= divisor;
|
|
203
|
+
maxAbsoluteValue /= divisor;
|
|
204
|
+
|
|
205
|
+
let maxDecimals = noDecimal ? 0 : 3;
|
|
206
|
+
|
|
165
207
|
return formatMaxDecimals(count, maxDecimals, maxAbsoluteValue, currencyDecimalsNeeded ? 2 : undefined) + suffix;
|
|
166
208
|
}
|
package/src/misc.ts
CHANGED
|
@@ -42,7 +42,7 @@ export async function sha256BufferPromise(buffer: Buffer): Promise<Buffer> {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
export function arrayEqual(a:
|
|
45
|
+
export function arrayEqual(a: { [key: number]: unknown; length: number }, b: { [key: number]: unknown; length: number },) {
|
|
46
46
|
if (a.length !== b.length) return false;
|
|
47
47
|
for (let i = 0; i < a.length; i++) {
|
|
48
48
|
if (a[i] !== b[i]) return false;
|
package/src/profiling/measure.ts
CHANGED
|
@@ -115,6 +115,8 @@ export interface LogMeasureTableConfig {
|
|
|
115
115
|
thresholdInTable?: number;
|
|
116
116
|
// Details to 50
|
|
117
117
|
minTimeToLog?: number;
|
|
118
|
+
// Defaults to 2
|
|
119
|
+
mergeDepth?: number;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
export function logMeasureTable(
|
|
@@ -136,6 +138,25 @@ export function logMeasureTable(
|
|
|
136
138
|
let totalTime = entries.map(x => getTime(x).sum).reduce((a, b) => a + b, 0);
|
|
137
139
|
if (totalTime < minTimeToLog) return;
|
|
138
140
|
|
|
141
|
+
let mergeDepth = config?.mergeDepth ?? 2;
|
|
142
|
+
{
|
|
143
|
+
let merged = new Map<string, ProfileEntry>();
|
|
144
|
+
for (let entry of entries) {
|
|
145
|
+
let parts = entry.name.split("|");
|
|
146
|
+
let key = parts.slice(0, mergeDepth).join("|");
|
|
147
|
+
let existing = merged.get(key);
|
|
148
|
+
if (!existing) {
|
|
149
|
+
existing = { name: key, ownTime: createStatsValue(), totalTime: createStatsValue(), stillOpenCount: 0 };
|
|
150
|
+
merged.set(key, existing);
|
|
151
|
+
}
|
|
152
|
+
addToStats(existing.ownTime, entry.ownTime);
|
|
153
|
+
addToStats(existing.totalTime, entry.totalTime);
|
|
154
|
+
existing.stillOpenCount += entry.stillOpenCount;
|
|
155
|
+
}
|
|
156
|
+
entries = Array.from(merged.values());
|
|
157
|
+
entries.sort((a, b) => getTime(b).sum - getTime(a).sum);
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
console.log();
|
|
140
161
|
let title = yellow(`Profiled ${formatTime(totalTime)} (logged at ${new Date().toISOString()})`);
|
|
141
162
|
if (name) {
|