@naturalcycles/backend-lib 9.85.1 → 9.87.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.
@@ -1,5 +1,5 @@
1
1
  import { inspect } from 'node:util';
2
- import { _ms, _since } from '@naturalcycles/js-lib/datetime';
2
+ import { _ms, _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime';
3
3
  import { getFetcher } from '@naturalcycles/js-lib/http';
4
4
  import { _filterFalsyValues, _filterUndefinedValues, } from '@naturalcycles/js-lib/object/object.util.js';
5
5
  import { pDelay } from '@naturalcycles/js-lib/promise/pDelay.js';
@@ -60,7 +60,7 @@ export async function deployHealthCheck(url, opt = {}) {
60
60
  async function makeAttempt() {
61
61
  attempt++;
62
62
  console.log([`>>`, dimGrey(url), inspect({ attempt }, inspectOpt)].join(' '));
63
- const started = Date.now();
63
+ const started = nowUnixMillis();
64
64
  const { err, statusCode = 0 } = await fetcher.doFetch(_filterUndefinedValues({
65
65
  url,
66
66
  responseType: 'json',
@@ -56,7 +56,7 @@ export interface CompressionOptions {
56
56
  *
57
57
  * Encoding preference order: zstd > br > gzip > deflate
58
58
  */
59
- export declare function compressionMiddleware(options?: CompressionOptions): BackendRequestHandler;
59
+ export declare function compressionMiddleware(opt?: CompressionOptions): BackendRequestHandler;
60
60
  /**
61
61
  * Default filter function.
62
62
  * Returns true if the Content-Type is compressible.
@@ -27,14 +27,13 @@ const cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i;
27
27
  *
28
28
  * Encoding preference order: zstd > br > gzip > deflate
29
29
  */
30
- export function compressionMiddleware(options) {
31
- const opts = options || {};
30
+ export function compressionMiddleware(opt = {}) {
32
31
  // Brotli options
33
32
  const optsBrotli = {
34
- ...opts.brotli,
33
+ ...opt.brotli,
35
34
  params: {
36
35
  [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
37
- ...opts.brotli?.params,
36
+ ...opt.brotli?.params,
38
37
  },
39
38
  };
40
39
  // Zstd options
@@ -43,29 +42,29 @@ export function compressionMiddleware(options) {
43
42
  // stream holds ~850 KB of native memory per in-flight response vs ~150 KB at level 1.
44
43
  // That per-response allocation churn is what MALLOC_ARENA_MAX=2 works around on Cloud Run.
45
44
  const optsZstd = {
46
- ...opts.zstd,
45
+ ...opt.zstd,
47
46
  params: {
48
47
  [zlib.constants.ZSTD_c_compressionLevel]: 1,
49
- ...opts.zstd?.params,
48
+ ...opt.zstd?.params,
50
49
  },
51
50
  };
52
51
  // General zlib options for gzip/deflate
53
52
  const zlibOpts = {};
54
- if (opts.level !== undefined)
55
- zlibOpts.level = opts.level;
56
- if (opts.memLevel !== undefined)
57
- zlibOpts.memLevel = opts.memLevel;
58
- if (opts.strategy !== undefined)
59
- zlibOpts.strategy = opts.strategy;
60
- if (opts.windowBits !== undefined)
61
- zlibOpts.windowBits = opts.windowBits;
62
- if (opts.chunkSize !== undefined)
63
- zlibOpts.chunkSize = opts.chunkSize;
53
+ if (opt.level !== undefined)
54
+ zlibOpts.level = opt.level;
55
+ if (opt.memLevel !== undefined)
56
+ zlibOpts.memLevel = opt.memLevel;
57
+ if (opt.strategy !== undefined)
58
+ zlibOpts.strategy = opt.strategy;
59
+ if (opt.windowBits !== undefined)
60
+ zlibOpts.windowBits = opt.windowBits;
61
+ if (opt.chunkSize !== undefined)
62
+ zlibOpts.chunkSize = opt.chunkSize;
64
63
  const codecOpts = { zstd: optsZstd, brotli: optsBrotli, zlib: zlibOpts };
65
- const filter = opts.filter || shouldCompress;
66
- const threshold = parseBytes(opts.threshold) ?? 1024;
67
- const syncMaxBytes = parseBytes(opts.syncMaxBytes) ?? 128 * 1024;
68
- const enforceEncoding = opts.enforceEncoding || 'identity';
64
+ const filter = opt.filter || shouldCompress;
65
+ const threshold = parseBytes(opt.threshold) ?? 1024;
66
+ const syncMaxBytes = parseBytes(opt.syncMaxBytes) ?? 128 * 1024;
67
+ const enforceEncoding = opt.enforceEncoding || 'identity';
69
68
  return function compression(req, res, next) {
70
69
  let ended = false;
71
70
  // Set by the first `res.write`: the body comes in several chunks, so only the stream path applies
@@ -281,14 +280,14 @@ export function shouldCompress(_req, res) {
281
280
  * Compresses a whole body in one call, on the event loop.
282
281
  * Same codec options as the streams, so the output is identical to the streamed one.
283
282
  */
284
- function compressSync(method, body, opts) {
283
+ function compressSync(method, body, opt) {
285
284
  if (method === 'zstd')
286
- return zlib.zstdCompressSync(body, opts.zstd);
285
+ return zlib.zstdCompressSync(body, opt.zstd);
287
286
  if (method === 'br')
288
- return zlib.brotliCompressSync(body, opts.brotli);
287
+ return zlib.brotliCompressSync(body, opt.brotli);
289
288
  if (method === 'gzip')
290
- return zlib.gzipSync(body, opts.zlib);
291
- return zlib.deflateSync(body, opts.zlib);
289
+ return zlib.gzipSync(body, opt.zlib);
290
+ return zlib.deflateSync(body, opt.zlib);
292
291
  }
293
292
  function addListeners(stream, on, listeners) {
294
293
  for (const [type, listener] of listeners) {
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js';
1
2
  import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
2
3
  import { onFinished } from '../onFinished.js';
3
4
  /**
@@ -12,7 +13,7 @@ export function requestLoggerMiddleware(cfg = {}) {
12
13
  if (predicate && !predicate(req)) {
13
14
  return next();
14
15
  }
15
- const started = Date.now();
16
+ const started = nowUnixMillis();
16
17
  let url = req.originalUrl.split('?')[0];
17
18
  if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
18
19
  url = url.slice(removeUrlPrefixLength);
@@ -1,6 +1,7 @@
1
1
  import { _mb } from '@naturalcycles/js-lib';
2
2
  import { _sum } from '@naturalcycles/js-lib/array/array.util.js';
3
3
  import { _sortBy } from '@naturalcycles/js-lib/array/sort.js';
4
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js';
4
5
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js';
5
6
  import { _percentiles } from '@naturalcycles/js-lib/math/math.util.js';
6
7
  import { NumberStack } from '@naturalcycles/js-lib/math/stack.util.js';
@@ -79,11 +80,11 @@ export const serverStatsHTMLHandler = (req, res) => {
79
80
  * Unlocks serverStatsHTMLHandler
80
81
  */
81
82
  export function serverStatsMiddleware() {
82
- let lastCleanup = Date.now();
83
+ let lastCleanup = nowUnixMillis();
83
84
  return function serverStatsHandler(req, res, next) {
84
- const started = Date.now();
85
+ const started = nowUnixMillis();
85
86
  onFinished(res, () => {
86
- const now = Date.now();
87
+ const now = nowUnixMillis();
87
88
  const latency = now - started;
88
89
  // Only requests that matched a route are recorded by their route pattern.
89
90
  // Unmatched requests (404s, requests rejected by router-level middleware) carry an
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js';
1
2
  import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
2
3
  import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib/colors';
3
4
  import { onFinished } from '../onFinished.js';
@@ -5,7 +6,7 @@ import { logRequestWithColors } from './request.log.util.js';
5
6
  export function simpleRequestLoggerMiddleware(cfg = {}) {
6
7
  const { logStart = false, logFinish = true } = cfg;
7
8
  return (req, res, next) => {
8
- const started = Date.now();
9
+ const started = nowUnixMillis();
9
10
  if (logStart) {
10
11
  req.log(['>>', req.method, boldGrey(req.url)].join(' '));
11
12
  }
@@ -10,12 +10,11 @@ const traceIdByRequest = new WeakMap();
10
10
  * every log line.
11
11
  */
12
12
  export function getRequestTraceId(req) {
13
- let traceId = traceIdByRequest.get(req);
14
- if (traceId === undefined) {
15
- traceId = getRequestTraceContext(req)?.traceId ?? null;
16
- traceIdByRequest.set(req, traceId);
17
- }
18
- return traceId ?? undefined;
13
+ return traceIdByRequest.getOrInsertComputed(req, getRequestTraceIdOrNull) ?? undefined;
14
+ }
15
+ // Passed by reference, so the per-log-line call allocates no closure
16
+ function getRequestTraceIdOrNull(req) {
17
+ return getRequestTraceContext(req)?.traceId ?? null;
19
18
  }
20
19
  export function getRequestTraceContext(req) {
21
20
  // Reads from `req.headers` and not `req.header()`, so a bare request object (as in tests) works too
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.85.1",
4
+ "version": "9.87.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/db-lib": "^10",
7
7
  "@naturalcycles/js-lib": "^15",
@@ -1,13 +1,12 @@
1
1
  import type { InspectOptions } from 'node:util'
2
2
  import { inspect } from 'node:util'
3
- import { _ms, _since } from '@naturalcycles/js-lib/datetime'
3
+ import { _ms, _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime'
4
4
  import { getFetcher } from '@naturalcycles/js-lib/http'
5
5
  import {
6
6
  _filterFalsyValues,
7
7
  _filterUndefinedValues,
8
8
  } from '@naturalcycles/js-lib/object/object.util.js'
9
9
  import { pDelay } from '@naturalcycles/js-lib/promise/pDelay.js'
10
- import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
11
10
  import type { CliOptions } from '@naturalcycles/nodejs-lib/args'
12
11
  import { dimGrey, red } from '@naturalcycles/nodejs-lib/colors'
13
12
  import { coloredHttpCode } from '../server/request.log.util.js'
@@ -95,7 +94,7 @@ export async function deployHealthCheck(
95
94
 
96
95
  console.log([`>>`, dimGrey(url), inspect({ attempt }, inspectOpt)].join(' '))
97
96
 
98
- const started = Date.now() as UnixTimestampMillis
97
+ const started = nowUnixMillis()
99
98
 
100
99
  const { err, statusCode = 0 } = await fetcher.doFetch(
101
100
  _filterUndefinedValues({
@@ -89,15 +89,13 @@ const cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i
89
89
  *
90
90
  * Encoding preference order: zstd > br > gzip > deflate
91
91
  */
92
- export function compressionMiddleware(options?: CompressionOptions): BackendRequestHandler {
93
- const opts = options || {}
94
-
92
+ export function compressionMiddleware(opt: CompressionOptions = {}): BackendRequestHandler {
95
93
  // Brotli options
96
94
  const optsBrotli: zlib.BrotliOptions = {
97
- ...opts.brotli,
95
+ ...opt.brotli,
98
96
  params: {
99
97
  [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
100
- ...opts.brotli?.params,
98
+ ...opt.brotli?.params,
101
99
  },
102
100
  }
103
101
 
@@ -107,27 +105,27 @@ export function compressionMiddleware(options?: CompressionOptions): BackendRequ
107
105
  // stream holds ~850 KB of native memory per in-flight response vs ~150 KB at level 1.
108
106
  // That per-response allocation churn is what MALLOC_ARENA_MAX=2 works around on Cloud Run.
109
107
  const optsZstd: zlib.ZstdOptions = {
110
- ...opts.zstd,
108
+ ...opt.zstd,
111
109
  params: {
112
110
  [zlib.constants.ZSTD_c_compressionLevel]: 1,
113
- ...opts.zstd?.params,
111
+ ...opt.zstd?.params,
114
112
  },
115
113
  }
116
114
 
117
115
  // General zlib options for gzip/deflate
118
116
  const zlibOpts: zlib.ZlibOptions = {}
119
- if (opts.level !== undefined) zlibOpts.level = opts.level
120
- if (opts.memLevel !== undefined) zlibOpts.memLevel = opts.memLevel
121
- if (opts.strategy !== undefined) zlibOpts.strategy = opts.strategy
122
- if (opts.windowBits !== undefined) zlibOpts.windowBits = opts.windowBits
123
- if (opts.chunkSize !== undefined) zlibOpts.chunkSize = opts.chunkSize
117
+ if (opt.level !== undefined) zlibOpts.level = opt.level
118
+ if (opt.memLevel !== undefined) zlibOpts.memLevel = opt.memLevel
119
+ if (opt.strategy !== undefined) zlibOpts.strategy = opt.strategy
120
+ if (opt.windowBits !== undefined) zlibOpts.windowBits = opt.windowBits
121
+ if (opt.chunkSize !== undefined) zlibOpts.chunkSize = opt.chunkSize
124
122
 
125
123
  const codecOpts: CodecOptions = { zstd: optsZstd, brotli: optsBrotli, zlib: zlibOpts }
126
124
 
127
- const filter = opts.filter || shouldCompress
128
- const threshold = parseBytes(opts.threshold) ?? 1024
129
- const syncMaxBytes = parseBytes(opts.syncMaxBytes) ?? 128 * 1024
130
- const enforceEncoding = opts.enforceEncoding || 'identity'
125
+ const filter = opt.filter || shouldCompress
126
+ const threshold = parseBytes(opt.threshold) ?? 1024
127
+ const syncMaxBytes = parseBytes(opt.syncMaxBytes) ?? 128 * 1024
128
+ const enforceEncoding = opt.enforceEncoding || 'identity'
131
129
 
132
130
  return function compression(req, res, next) {
133
131
  let ended = false
@@ -403,11 +401,11 @@ interface CodecOptions {
403
401
  * Compresses a whole body in one call, on the event loop.
404
402
  * Same codec options as the streams, so the output is identical to the streamed one.
405
403
  */
406
- function compressSync(method: string, body: Buffer, opts: CodecOptions): Buffer {
407
- if (method === 'zstd') return zlib.zstdCompressSync(body, opts.zstd)
408
- if (method === 'br') return zlib.brotliCompressSync(body, opts.brotli)
409
- if (method === 'gzip') return zlib.gzipSync(body, opts.zlib)
410
- return zlib.deflateSync(body, opts.zlib)
404
+ function compressSync(method: string, body: Buffer, opt: CodecOptions): Buffer {
405
+ if (method === 'zstd') return zlib.zstdCompressSync(body, opt.zstd)
406
+ if (method === 'br') return zlib.brotliCompressSync(body, opt.brotli)
407
+ if (method === 'gzip') return zlib.gzipSync(body, opt.zlib)
408
+ return zlib.deflateSync(body, opt.zlib)
411
409
  }
412
410
 
413
411
  function addListeners(
@@ -1,5 +1,5 @@
1
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js'
1
2
  import { _since } from '@naturalcycles/js-lib/datetime/time.util.js'
2
- import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
3
3
  import { onFinished } from '../onFinished.js'
4
4
  import type { BackendRequest, BackendRequestHandler } from './server.model.js'
5
5
 
@@ -32,7 +32,7 @@ export function requestLoggerMiddleware(
32
32
  return next()
33
33
  }
34
34
 
35
- const started = Date.now() as UnixTimestampMillis
35
+ const started = nowUnixMillis()
36
36
 
37
37
  let url = req.originalUrl.split('?')[0]!
38
38
  if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
@@ -1,6 +1,7 @@
1
1
  import { _mb } from '@naturalcycles/js-lib'
2
2
  import { _sum } from '@naturalcycles/js-lib/array/array.util.js'
3
3
  import { _sortBy } from '@naturalcycles/js-lib/array/sort.js'
4
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js'
4
5
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
5
6
  import { _percentiles } from '@naturalcycles/js-lib/math/math.util.js'
6
7
  import { NumberStack } from '@naturalcycles/js-lib/math/stack.util.js'
@@ -106,13 +107,13 @@ export const serverStatsHTMLHandler: BackendRequestHandler = (req, res) => {
106
107
  * Unlocks serverStatsHTMLHandler
107
108
  */
108
109
  export function serverStatsMiddleware(): BackendRequestHandler {
109
- let lastCleanup = Date.now()
110
+ let lastCleanup = nowUnixMillis()
110
111
 
111
112
  return function serverStatsHandler(req, res, next) {
112
- const started = Date.now()
113
+ const started = nowUnixMillis()
113
114
 
114
115
  onFinished(res, () => {
115
- const now = Date.now()
116
+ const now = nowUnixMillis()
116
117
  const latency = now - started
117
118
 
118
119
  // Only requests that matched a route are recorded by their route pattern.
@@ -1,5 +1,5 @@
1
+ import { nowUnixMillis } from '@naturalcycles/js-lib/datetime/localTime.js'
1
2
  import { _since } from '@naturalcycles/js-lib/datetime/time.util.js'
2
- import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
3
3
  import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib/colors'
4
4
  import { onFinished } from '../onFinished.js'
5
5
  import { logRequestWithColors } from './request.log.util.js'
@@ -23,7 +23,7 @@ export function simpleRequestLoggerMiddleware(
23
23
  const { logStart = false, logFinish = true } = cfg
24
24
 
25
25
  return (req, res, next) => {
26
- const started = Date.now() as UnixTimestampMillis
26
+ const started = nowUnixMillis()
27
27
 
28
28
  if (logStart) {
29
29
  req.log(['>>', req.method, boldGrey(req.url)].join(' '))
@@ -23,12 +23,12 @@ const traceIdByRequest = new WeakMap<BackendRequest, string | null>()
23
23
  * every log line.
24
24
  */
25
25
  export function getRequestTraceId(req: BackendRequest): string | undefined {
26
- let traceId = traceIdByRequest.get(req)
27
- if (traceId === undefined) {
28
- traceId = getRequestTraceContext(req)?.traceId ?? null
29
- traceIdByRequest.set(req, traceId)
30
- }
31
- return traceId ?? undefined
26
+ return traceIdByRequest.getOrInsertComputed(req, getRequestTraceIdOrNull) ?? undefined
27
+ }
28
+
29
+ // Passed by reference, so the per-log-line call allocates no closure
30
+ function getRequestTraceIdOrNull(req: BackendRequest): string | null {
31
+ return getRequestTraceContext(req)?.traceId ?? null
32
32
  }
33
33
 
34
34
  export function getRequestTraceContext(req: BackendRequest): TraceContext | undefined {