socket-function 0.10.4 → 0.10.6

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 } from "./SocketFunctionTypes";
3
+ import { SocketExposedInterface, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType, CallType } 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";
@@ -32,12 +32,18 @@ type ExtractShape<ClassType, Shape> = {
32
32
 
33
33
  export class SocketFunction {
34
34
  public static logMessages = false;
35
+ public static trackMessageSizes = {
36
+ upload: [] as ((size: number) => void)[],
37
+ download: [] as ((size: number) => void)[],
38
+ };
35
39
 
36
40
  public static MAX_MESSAGE_SIZE = 1024 * 1024 * 32;
37
41
 
38
- public static httpETagCache = false;
42
+ public static HTTP_ETAG_CACHE = false;
39
43
  public static silent = true;
40
44
 
45
+ public static HTTP_COMPRESS = false;
46
+
41
47
  // In retrospect... dynamically changing the wire serializer is a BAD idea. If any calls happen
42
48
  // before it is changed, things just break. Also, it needs to be changed on both sides,
43
49
  // or else things break. Also, it is very hard to detect when the issue is different serializers
@@ -24,6 +24,7 @@ export type SocketExposedInterfaceClass = {
24
24
  };
25
25
  export type SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
26
26
  [functionName in keyof ExposedType]?: {
27
+ compress?: boolean;
27
28
  /** Indicates with the same input, we give the same output, forever,
28
29
  * independent of code changes. This only works for data storage.
29
30
  */
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
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",
7
7
  "dependencies": {
8
+ "@types/pako": "^2.0.3",
8
9
  "@types/ws": "^8.5.3",
9
10
  "cbor-x": "^1.5.6",
10
11
  "cookie": "^0.5.0",
11
12
  "mobx": "^6.6.2",
12
13
  "node-forge": "https://github.com/sliftist/forge#name",
14
+ "pako": "^2.1.0",
13
15
  "preact": "^10.10.6",
14
- "typenode": "^5.3.8",
16
+ "typenode": "^5.3.10",
15
17
  "ws": "^8.8.0"
16
18
  },
17
19
  "optionalDependencies": {
@@ -2,8 +2,13 @@
2
2
  import debugbreak from "debugbreak";
3
3
  import fs from "fs";
4
4
  import { SocketFunction } from "../SocketFunction";
5
- import { setHTTPResultHeaders } from "../src/callHTTPHandler";
6
- import { isNodeTrue } from "../src/misc";
5
+ import { getCurrentHTTPRequest, setHTTPResultHeaders } from "../src/callHTTPHandler";
6
+ import { formatNumberSuffixed, isNodeTrue, sha256Hash, sha256HashPromise } from "../src/misc";
7
+ import zlib from "zlib";
8
+ import { cacheLimited } from "../src/caching";
9
+ import { formatNumber } from "../src/formatting/format";
10
+
11
+ const COMPRESS_CACHE_SIZE = 1024 * 1024 * 128;
7
12
 
8
13
  module.allowclient = true;
9
14
 
@@ -55,6 +60,9 @@ export interface SerializedModule {
55
60
  source?: string;
56
61
 
57
62
  seqNum: number;
63
+
64
+ size?: number;
65
+ version?: number;
58
66
  }
59
67
 
60
68
  let nextModuleSeqNum = 1;
@@ -128,6 +136,8 @@ class RequireControllerBase {
128
136
  };
129
137
  requireSeqNumProcessId: string;
130
138
  }> {
139
+ let httpRequest = getCurrentHTTPRequest();
140
+
131
141
  let seqNums: { [seqNum: number]: 1 } = {};
132
142
  if (alreadyHave?.requireSeqNumProcessId === requireSeqNumProcessId) {
133
143
  for (let { s, e } of alreadyHave.seqNumRanges) {
@@ -168,6 +178,8 @@ class RequireControllerBase {
168
178
  serveronly: module.serveronly,
169
179
  requests: Object.create(null),
170
180
  seqNum: module.requireControllerSeqNum,
181
+ size: module.size,
182
+ version: module.version,
171
183
  };
172
184
  let moduleObj = modules[module.filename];
173
185
  if (moduleObj.allowclient) {
@@ -256,10 +268,53 @@ class RequireControllerBase {
256
268
  for (let remap of mapGetModules) {
257
269
  result = await remap.remap(result, [pathRequests, alreadyHave, config]);
258
270
  }
271
+
272
+ // NOTE: Handling compression ourself allows us to efficiently cache (otherwise caching would require
273
+ // hashing the output, which takes almost as long as compression!)
274
+ if (httpRequest && SocketFunction.HTTP_COMPRESS && httpRequest.headers["accept-encoding"]?.includes("gzip")) {
275
+ let simplifiedResult = {
276
+ ...result,
277
+ modules: Object.entries(result.modules).map(x => [x[0], {
278
+ filename: x[1].filename,
279
+ version: x[1].version,
280
+ sourceLength: x[1].source?.length,
281
+ }]),
282
+ };
283
+ let key = sha256Hash(JSON.stringify(simplifiedResult));
284
+ let buffer = await compressCached(key, () => Buffer.from(JSON.stringify(result)));
285
+ setHTTPResultHeaders(buffer, { "Content-Type": "application/json", "Content-Encoding": "gzip" });
286
+ return buffer as any;
287
+ }
288
+
259
289
  return result;
260
290
  }
261
291
  }
262
292
 
293
+ let compressCacheSize = 0;
294
+ let compressCache = new Map<string, Buffer>();
295
+ async function compressCached(bufferKey: string, buffer: () => Buffer): Promise<Buffer> {
296
+ let cached = compressCache.get(bufferKey);
297
+ if (!cached) {
298
+ cached = await new Promise<Buffer>((resolve, reject) => {
299
+ zlib.gzip(buffer(), {}, (err, result) => {
300
+ if (err) {
301
+ reject(err);
302
+ } else {
303
+ resolve(result);
304
+ }
305
+ });
306
+ });
307
+ compressCacheSize += cached.length;
308
+ // TODO: Make the cache LRU eviction, instead of just resetting it
309
+ if (compressCacheSize > COMPRESS_CACHE_SIZE) {
310
+ compressCache.clear();
311
+ compressCacheSize = cached.length;
312
+ }
313
+ compressCache.set(bufferKey, cached);
314
+ }
315
+ return cached;
316
+ }
317
+
263
318
  type ClientRemapCallback = (args: GetModulesArgs) => Promise<GetModulesArgs>;
264
319
  declare global {
265
320
  /** Must be set clientside BEFORE requests are made (so you likely want to use RequireController.addMapGetModules
@@ -416,6 +416,7 @@
416
416
  let time = Date.now();
417
417
  currentModuleEvaluationStack.push(module.filename);
418
418
  try {
419
+ module.isPreloading = true;
419
420
  moduleFnc.call(
420
421
  {
421
422
  // NOTE: Adding __importStar to the module causes typescript to use our implementation,
@@ -440,6 +441,7 @@
440
441
  // As in, adding about 500ms to our load time, which is annoying when debugging.
441
442
  //console.debug(`Evaluated module ${module.filename} ${Math.ceil(source.length / 1024)}KB`);
442
443
  } finally {
444
+ module.isPreloading = false;
443
445
  currentModuleEvaluationStack.pop();
444
446
  }
445
447
 
@@ -1,10 +1,9 @@
1
1
  import { CallerContext, CallerContextBase, CallType, FullCallType } from "../SocketFunctionTypes";
2
2
  import * as ws from "ws";
3
- import { performLocalCall } from "./callManager";
3
+ import { performLocalCall, shouldCompressCall } from "./callManager";
4
4
  import { convertErrorStackToError, formatNumberSuffixed, isNode, list } from "./misc";
5
5
  import { createWebsocketFactory, getTLSSocket } from "./websocketFactory";
6
6
  import { SocketFunction } from "../SocketFunction";
7
- import { gzip } from "zlib";
8
7
  import * as tls from "tls";
9
8
  import { getClientNodeId, getNodeIdLocation, registerNodeClient } from "./nodeCache";
10
9
  import debugbreak from "debugbreak";
@@ -13,12 +12,16 @@ import { red, yellow } from "./formatting/logColors";
13
12
  import { isSplitableArray, markArrayAsSplitable } from "./fixLargeNetworkCalls";
14
13
  import { delay, runInSerial } from "./batching";
15
14
  import { formatNumber, formatTime } from "./formatting/format";
15
+ import pako from "pako";
16
+ import { setFlag } from "../require/compileFlags";
17
+ setFlag(require, "pako", "allowclient", true);
16
18
 
17
19
  const MIN_RETRY_DELAY = 1000;
18
20
 
19
21
  type InternalCallType = FullCallType & {
20
22
  seqNum: number;
21
23
  isReturn: false;
24
+ isArgsCompressed?: boolean;
22
25
  }
23
26
 
24
27
  type InternalReturnType = {
@@ -27,7 +30,7 @@ type InternalReturnType = {
27
30
  error?: string;
28
31
  seqNum: number;
29
32
  resultSize: number;
30
- compressed: boolean;
33
+ isResultCompressed?: boolean;
31
34
  };
32
35
 
33
36
 
@@ -123,6 +126,11 @@ export async function createCallFactory(
123
126
  functionName: call.functionName,
124
127
  seqNum,
125
128
  };
129
+ let originalArgs = call.args;
130
+ if (shouldCompressCall(fullCall)) {
131
+ fullCall.args = await compressObj(fullCall.args) as any;
132
+ fullCall.isArgsCompressed = true;
133
+ }
126
134
  let time = Date.now();
127
135
  let data: Buffer[];
128
136
  let dataMaybePromise = SocketFunction.WIRE_SERIALIZER.serialize(fullCall);
@@ -138,11 +146,11 @@ export async function createCallFactory(
138
146
  }
139
147
 
140
148
  if (size > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
141
- let splitArgIndex = call.args.findIndex(isSplitableArray);
149
+ let splitArgIndex = originalArgs.findIndex(isSplitableArray);
142
150
  if (splitArgIndex >= 0) {
143
151
  console.log(yellow(`Splitting large call due to large args: ${call.classGuid}.${call.functionName}`));
144
152
  let SPLIT_GROUPS = 10;
145
- let splitArg = call.args[splitArgIndex] as unknown[];
153
+ let splitArg = originalArgs[splitArgIndex] as unknown[];
146
154
  let subCalls = list(SPLIT_GROUPS).map(index => {
147
155
  let start = Math.floor(index / SPLIT_GROUPS * splitArg.length);
148
156
  let end = Math.floor((index + 1) / SPLIT_GROUPS * splitArg.length);
@@ -167,9 +175,6 @@ export async function createCallFactory(
167
175
 
168
176
  let resultPromise = new Promise((resolve, reject) => {
169
177
  let callback = (result: InternalReturnType) => {
170
- if (SocketFunction.logMessages) {
171
- console.log(`SIZE\t${(formatNumberSuffixed(result.resultSize) + "B").padEnd(4, " ")}\t${call.classGuid}.${call.functionName} at ${Date.now()}`);
172
- }
173
178
  pendingCalls.delete(seqNum);
174
179
  if (result.error) {
175
180
  reject(convertErrorStackToError(result.error));
@@ -180,6 +185,20 @@ export async function createCallFactory(
180
185
  pendingCalls.set(seqNum, { callback, data, call: fullCall });
181
186
  });
182
187
 
188
+ {
189
+ let resultSize = data.map(x => x.length).reduce((a, b) => a + b, 0);
190
+ for (let callback of SocketFunction.trackMessageSizes.upload) {
191
+ callback(resultSize);
192
+ }
193
+ if (SocketFunction.logMessages) {
194
+ let fncHack = "";
195
+ if (call.functionName === "addCall") {
196
+ let arg = originalArgs[0] as any;
197
+ fncHack = `.${arg.DomainName}.${arg.ModuleId}.${arg.FunctionId}`;
198
+ }
199
+ console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\t${call.classGuid}.${call.functionName}${fncHack} at ${Date.now()}`);
200
+ }
201
+ }
183
202
  await send(data);
184
203
 
185
204
  return await resultPromise;
@@ -210,7 +229,6 @@ export async function createCallFactory(
210
229
  error: error,
211
230
  seqNum: call.call.seqNum,
212
231
  resultSize: 0,
213
- compressed: false,
214
232
  });
215
233
  }
216
234
 
@@ -337,6 +355,9 @@ export async function createCallFactory(
337
355
  let time = Date.now();
338
356
  let call = await SocketFunction.WIRE_SERIALIZER.deserialize(currentBuffers) as InternalCallType | InternalReturnType;
339
357
  time = Date.now() - time;
358
+ for (let callback of SocketFunction.trackMessageSizes.download) {
359
+ callback(resultSize);
360
+ }
340
361
 
341
362
  if (call.isReturn) {
342
363
  let callbackObj = pendingCalls.get(call.seqNum);
@@ -347,9 +368,24 @@ export async function createCallFactory(
347
368
  console.log(`Got return for unknown call ${call.seqNum}`);
348
369
  return;
349
370
  }
371
+ if (SocketFunction.logMessages) {
372
+ let call = callbackObj.call;
373
+ console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\t${call.classGuid}.${call.functionName} at ${Date.now()}`);
374
+ }
375
+ if (call.isResultCompressed) {
376
+ call.result = await decompressObj(call.result as Buffer);
377
+ call.isResultCompressed = false;
378
+ }
350
379
  call.resultSize = resultSize;
351
380
  callbackObj.callback(call);
352
381
  } else {
382
+ if (call.isArgsCompressed) {
383
+ call.args = await decompressObj(call.args as any as Buffer) as any;
384
+ call.isArgsCompressed = false;
385
+ }
386
+ if (SocketFunction.logMessages) {
387
+ console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\t${call.classGuid}.${call.functionName} at ${Date.now()}`);
388
+ }
353
389
  if (time > SocketFunction.WIRE_WARN_TIME) {
354
390
  console.log(red(`Slow parse, took ${time}ms to parse ${resultSize} bytes, for call to ${call.classGuid}.${call.functionName}`));
355
391
  }
@@ -362,8 +398,11 @@ export async function createCallFactory(
362
398
  result,
363
399
  seqNum: call.seqNum,
364
400
  resultSize: resultSize,
365
- compressed: false,
366
401
  };
402
+ if (shouldCompressCall(call)) {
403
+ response.result = await compressObj(response.result) as any;
404
+ response.isResultCompressed = true;
405
+ }
367
406
  } catch (e: any) {
368
407
  response = {
369
408
  isReturn: true,
@@ -371,7 +410,6 @@ export async function createCallFactory(
371
410
  seqNum: call.seqNum,
372
411
  error: e.stack,
373
412
  resultSize: resultSize,
374
- compressed: false,
375
413
  };
376
414
  }
377
415
 
@@ -384,7 +422,6 @@ export async function createCallFactory(
384
422
  seqNum: call.seqNum,
385
423
  error: new Error(`Response too large to send (${call.classGuid}.${call.functionName}, size: ${formatNumber(totalResultSize)} > ${formatNumber(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.`).stack,
386
424
  resultSize: resultSize,
387
- compressed: false,
388
425
  };
389
426
  result = await SocketFunction.WIRE_SERIALIZER.serialize(response);
390
427
  }
@@ -394,15 +431,47 @@ export async function createCallFactory(
394
431
  }
395
432
  throw new Error(`Unhandled data type ${typeof message}`);
396
433
  } catch (e: any) {
434
+ let message = e.stack || e.message || e;
397
435
  // NOTE: I'm looking for all types of errors here (specifically, .send errors), in case
398
436
  // there are errors I should be handling.
399
- if (e.stack.startsWith("Error: Cannot send data to") && e.stack.includes("as the connection has closed")) {
437
+ if (message.startsWith("Error: Cannot send data to") && message.includes("as the connection has closed")) {
400
438
  // This is fine, just ignore it
401
439
  } else {
440
+ debugbreak(2);
441
+ debugger;
402
442
  console.error(e.stack);
403
443
  }
404
444
  }
405
445
  }
406
446
 
407
447
  return callFactory;
448
+ }
449
+
450
+
451
+ async function compressObj(obj: unknown): Promise<Buffer> {
452
+ let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
453
+ let lengthBuffer = Buffer.from((new Float64Array(buffers.map(x => x.length))).buffer);
454
+ let buffer = Buffer.concat([lengthBuffer, ...buffers]);
455
+ return Buffer.from(pako.gzip(buffer));
456
+ }
457
+ async function decompressObj(obj: Buffer): Promise<unknown> {
458
+ try {
459
+ let buffer = Buffer.from(pako.ungzip(obj));
460
+ let lengthBuffer = buffer.slice(0, 8);
461
+ let lengths = new Float64Array(lengthBuffer.buffer, lengthBuffer.byteOffset, lengthBuffer.byteLength / 8);
462
+ let buffers: Buffer[] = [];
463
+ let offset = 8;
464
+ for (let length of lengths) {
465
+ buffers.push(buffer.slice(offset, offset + length));
466
+ offset += length;
467
+ }
468
+
469
+ return await SocketFunction.WIRE_SERIALIZER.deserialize(buffers);
470
+ } catch (e) {
471
+ // We were encountering issues with the checksum failing when unzipping. Presumably if the data
472
+ // is bad deserialize will also fail. I can't repro it anymore though...
473
+ debugbreak(2);
474
+ debugger;
475
+ throw e;
476
+ }
408
477
  }
@@ -4,6 +4,7 @@ import { CallerContext, CallType, FullCallType } from "../SocketFunctionTypes";
4
4
  import { isDataImmutable, performLocalCall } from "./callManager";
5
5
  import { SocketFunction } from "../SocketFunction";
6
6
  import { gzip } from "zlib";
7
+ import zlib from "zlib";
7
8
  import { formatNumberSuffixed, sha256Hash } from "./misc";
8
9
  import { getClientNodeId, getNodeId } from "./nodeCache";
9
10
 
@@ -48,6 +49,11 @@ export function getNodeIdsFromRequest(request: http.IncomingMessage) {
48
49
  return { nodeId, localNodeId };
49
50
  }
50
51
 
52
+ let requests = new Map<CallerContext, http.IncomingMessage>();
53
+ export function getCurrentHTTPRequest(): http.IncomingMessage | undefined {
54
+ return requests.get(SocketFunction.getCaller());
55
+ }
56
+
51
57
  export async function httpCallHandler(request: http.IncomingMessage, response: http.ServerResponse) {
52
58
  try {
53
59
  // Always set x-frame-options, to prevent iframe embedding click hijacking
@@ -65,7 +71,9 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
65
71
  let protocol = "https";
66
72
  let url = protocol + "://" + request.headers.host + request.url;
67
73
 
68
- console.log(`HTTP request (${request.method}) ${url}`);
74
+ if (SocketFunction.logMessages) {
75
+ console.log(`HTTP request (${request.method}) ${url}`);
76
+ }
69
77
  let urlObj = new URL(url);
70
78
 
71
79
  let payload = await new Promise<Buffer>((resolve, reject) => {
@@ -136,10 +144,16 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
136
144
  }
137
145
  }
138
146
 
139
- let result = await performLocalCall({
140
- caller,
141
- call
142
- });
147
+ requests.set(caller, request);
148
+ let result: unknown;
149
+ try {
150
+ result = await performLocalCall({
151
+ caller,
152
+ call
153
+ });
154
+ } finally {
155
+ requests.delete(caller);
156
+ }
143
157
 
144
158
  let resultBuffer: Buffer;
145
159
  if (typeof result === "object" && result && result instanceof Buffer) {
@@ -152,7 +166,7 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
152
166
 
153
167
  // NOTE: Our ETag caching is only to reduce data sent on the wire, we evaluate the calls
154
168
  // every time (so it is strictly a wire cache for HTTP, not a computation cache)
155
- if (SocketFunction.httpETagCache) {
169
+ if (SocketFunction.HTTP_ETAG_CACHE) {
156
170
  response.setHeader("cache-control", "private, max-age=0, must-revalidate");
157
171
  let hash = sha256Hash(resultBuffer);
158
172
  response.setHeader("ETag", hash);
@@ -173,8 +187,26 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
173
187
  return;
174
188
  }
175
189
  }
190
+ if (SocketFunction.HTTP_COMPRESS && request.headers["accept-encoding"]?.includes("gzip") && !headers?.["Content-Encoding"]) {
191
+ // NOTE: This is a BIT slow. To speed it up, functions can use an internal cache, according to their function,
192
+ // and return a Buffer (which they can as any cast to make the returned type allowed, as returned Buffers will
193
+ // just be treated like a buffer of JSON data).
194
+ // - The caller should use getCurrentHTTPRequest first though, to check if gzip is allowed
195
+ response.setHeader("Content-Encoding", "gzip");
196
+ resultBuffer = await new Promise<Buffer>((resolve, reject) => {
197
+ zlib.gzip(resultBuffer, {}, (err, result) => {
198
+ if (err) {
199
+ reject(err);
200
+ } else {
201
+ resolve(result);
202
+ }
203
+ });
204
+ });
205
+ }
176
206
  response.write(resultBuffer);
177
- console.log(`HTTP response ${formatNumberSuffixed(resultBuffer.length)}B (${request.method}) ${url}`);
207
+ if (SocketFunction.logMessages) {
208
+ console.log(`HTTP response ${formatNumberSuffixed(resultBuffer.length)}B (${request.method}) ${url}`);
209
+ }
178
210
 
179
211
  } catch (e: any) {
180
212
  console.log(`HTTP error (${request.method}) ${e.stack}`);
@@ -15,6 +15,10 @@ let exposedClasses = new Set<string>();
15
15
  let globalHooks: SocketFunctionHook[] = [];
16
16
  let globalClientHooks: SocketFunctionClientHook[] = [];
17
17
 
18
+ export function shouldCompressCall(call: CallType) {
19
+ return !!classes[call.classGuid]?.shape[call.functionName]?.compress;
20
+ }
21
+
18
22
  export async function performLocalCall(
19
23
  config: {
20
24
  call: FullCallType;
@@ -5,14 +5,16 @@ function ansiHSL(h: number, s: number, l: number, text: string): string {
5
5
  return ansiRGB(r, g, b, text);
6
6
  }
7
7
  function ansiRGB(r: number, g: number, b: number, text: string): string {
8
- return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
8
+ return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
9
9
  }
10
10
 
11
11
  const lightness = 68;
12
- export const blue = ansiHSL.bind(null, 235, 100, lightness);
12
+ export const blue = (text: string) => `\x1b[34m${text}\x1b[0m`;
13
13
  export const red = ansiHSL.bind(null, 0, 100, lightness);
14
- export const green = (text: string) => `\x1b[32m${text}\x1b[39m`; // chalk.hsl(120, 100, lightness).bind(chalk);
15
- export const yellow = (text: string) => `\x1b[33m${text}\x1b[39m`;
14
+ export const green = (text: string) => `\x1b[32m${text}\x1b[0m`;
15
+ export const yellow = (text: string) => `\x1b[33m${text}\x1b[0m`;
16
16
  export const white = ansiHSL.bind(null, 0, 0, 80);
17
17
 
18
- export const magenta = (text: string) => `\x1b[35m${text}\x1b[39m`;
18
+ export const magenta = (text: string) => `\x1b[35m${text}\x1b[0m`;
19
+
20
+ console.log(blue('blue'));
package/src/misc.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as crypto from "crypto";
2
2
  import { canHaveChildren, MaybePromise } from "./types";
3
+ import { formatNumber } from "./formatting/format";
3
4
 
4
5
  export const timeInSecond = 1000;
5
6
  export const timeInMinute = timeInSecond * 60;
@@ -58,32 +59,7 @@ export function isNodeTrue() {
58
59
  }
59
60
 
60
61
  export function formatNumberSuffixed(count: number): string {
61
- if (typeof count !== "number") return "0";
62
- if (count < 0) {
63
- return "-" + formatNumberSuffixed(-count);
64
- }
65
-
66
- let absValue = Math.abs(count);
67
-
68
- const extraFactor = 10;
69
- let divisor = 1;
70
- let suffix = "";
71
- if (absValue < 1000 * extraFactor) {
72
-
73
- } else if (absValue < 1000 * 1000 * extraFactor) {
74
- suffix = "K";
75
- divisor = 1000;
76
- } else if (absValue < 1000 * 1000 * 1000 * extraFactor) {
77
- suffix = "M";
78
- divisor = 1000 * 1000;
79
- } else {
80
- suffix = "B";
81
- divisor = 1000 * 1000 * 1000;
82
- }
83
- count /= divisor;
84
- absValue /= divisor;
85
-
86
- return Math.round(count).toString() + suffix;
62
+ return formatNumber(count);
87
63
  }
88
64
 
89
65
  export function list(count: number) {