@naturalcycles/backend-lib 9.86.0 → 9.88.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/dist/deploy/deployHealthCheck.js +2 -2
- package/dist/server/compressionMiddleware.d.ts +1 -1
- package/dist/server/compressionMiddleware.js +24 -25
- package/dist/server/requestLoggerMiddleware.js +2 -2
- package/dist/server/serverStatsMiddleware.js +4 -4
- package/dist/server/simpleRequestLoggerMiddleware.js +2 -2
- package/package.json +1 -1
- package/src/deploy/deployHealthCheck.ts +2 -3
- package/src/server/compressionMiddleware.ts +19 -21
- package/src/server/requestLoggerMiddleware.ts +2 -3
- package/src/server/serverStatsMiddleware.ts +4 -4
- package/src/server/simpleRequestLoggerMiddleware.ts +2 -3
|
@@ -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 =
|
|
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(
|
|
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(
|
|
31
|
-
const opts = options || {};
|
|
30
|
+
export function compressionMiddleware(opt = {}) {
|
|
32
31
|
// Brotli options
|
|
33
32
|
const optsBrotli = {
|
|
34
|
-
...
|
|
33
|
+
...opt.brotli,
|
|
35
34
|
params: {
|
|
36
35
|
[zlib.constants.BROTLI_PARAM_QUALITY]: 4,
|
|
37
|
-
...
|
|
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
|
-
...
|
|
45
|
+
...opt.zstd,
|
|
47
46
|
params: {
|
|
48
47
|
[zlib.constants.ZSTD_c_compressionLevel]: 1,
|
|
49
|
-
...
|
|
48
|
+
...opt.zstd?.params,
|
|
50
49
|
},
|
|
51
50
|
};
|
|
52
51
|
// General zlib options for gzip/deflate
|
|
53
52
|
const zlibOpts = {};
|
|
54
|
-
if (
|
|
55
|
-
zlibOpts.level =
|
|
56
|
-
if (
|
|
57
|
-
zlibOpts.memLevel =
|
|
58
|
-
if (
|
|
59
|
-
zlibOpts.strategy =
|
|
60
|
-
if (
|
|
61
|
-
zlibOpts.windowBits =
|
|
62
|
-
if (
|
|
63
|
-
zlibOpts.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 =
|
|
66
|
-
const threshold = parseBytes(
|
|
67
|
-
const syncMaxBytes = parseBytes(
|
|
68
|
-
const enforceEncoding =
|
|
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,
|
|
283
|
+
function compressSync(method, body, opt) {
|
|
285
284
|
if (method === 'zstd')
|
|
286
|
-
return zlib.zstdCompressSync(body,
|
|
285
|
+
return zlib.zstdCompressSync(body, opt.zstd);
|
|
287
286
|
if (method === 'br')
|
|
288
|
-
return zlib.brotliCompressSync(body,
|
|
287
|
+
return zlib.brotliCompressSync(body, opt.brotli);
|
|
289
288
|
if (method === 'gzip')
|
|
290
|
-
return zlib.gzipSync(body,
|
|
291
|
-
return zlib.deflateSync(body,
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
1
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
2
2
|
import { onFinished } from '../onFinished.js';
|
|
3
3
|
/**
|
|
4
4
|
* Experimental request logger for Cloud Run.
|
|
@@ -12,7 +12,7 @@ export function requestLoggerMiddleware(cfg = {}) {
|
|
|
12
12
|
if (predicate && !predicate(req)) {
|
|
13
13
|
return next();
|
|
14
14
|
}
|
|
15
|
-
const started =
|
|
15
|
+
const started = nowUnixMillis();
|
|
16
16
|
let url = req.originalUrl.split('?')[0];
|
|
17
17
|
if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
|
|
18
18
|
url = url.slice(removeUrlPrefixLength);
|
|
@@ -1,7 +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 { _ms } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
4
|
+
import { _ms, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
5
5
|
import { _percentiles } from '@naturalcycles/js-lib/math/math.util.js';
|
|
6
6
|
import { NumberStack } from '@naturalcycles/js-lib/math/stack.util.js';
|
|
7
7
|
import { _get, _mapValues } from '@naturalcycles/js-lib/object/object.util.js';
|
|
@@ -79,11 +79,11 @@ export const serverStatsHTMLHandler = (req, res) => {
|
|
|
79
79
|
* Unlocks serverStatsHTMLHandler
|
|
80
80
|
*/
|
|
81
81
|
export function serverStatsMiddleware() {
|
|
82
|
-
let lastCleanup =
|
|
82
|
+
let lastCleanup = nowUnixMillis();
|
|
83
83
|
return function serverStatsHandler(req, res, next) {
|
|
84
|
-
const started =
|
|
84
|
+
const started = nowUnixMillis();
|
|
85
85
|
onFinished(res, () => {
|
|
86
|
-
const now =
|
|
86
|
+
const now = nowUnixMillis();
|
|
87
87
|
const latency = now - started;
|
|
88
88
|
// Only requests that matched a route are recorded by their route pattern.
|
|
89
89
|
// Unmatched requests (404s, requests rejected by router-level middleware) carry an
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
1
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
2
2
|
import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib/colors';
|
|
3
3
|
import { onFinished } from '../onFinished.js';
|
|
4
4
|
import { logRequestWithColors } from './request.log.util.js';
|
|
5
5
|
export function simpleRequestLoggerMiddleware(cfg = {}) {
|
|
6
6
|
const { logStart = false, logFinish = true } = cfg;
|
|
7
7
|
return (req, res, next) => {
|
|
8
|
-
const started =
|
|
8
|
+
const started = nowUnixMillis();
|
|
9
9
|
if (logStart) {
|
|
10
10
|
req.log(['>>', req.method, boldGrey(req.url)].join(' '));
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -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 =
|
|
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(
|
|
93
|
-
const opts = options || {}
|
|
94
|
-
|
|
92
|
+
export function compressionMiddleware(opt: CompressionOptions = {}): BackendRequestHandler {
|
|
95
93
|
// Brotli options
|
|
96
94
|
const optsBrotli: zlib.BrotliOptions = {
|
|
97
|
-
...
|
|
95
|
+
...opt.brotli,
|
|
98
96
|
params: {
|
|
99
97
|
[zlib.constants.BROTLI_PARAM_QUALITY]: 4,
|
|
100
|
-
...
|
|
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
|
-
...
|
|
108
|
+
...opt.zstd,
|
|
111
109
|
params: {
|
|
112
110
|
[zlib.constants.ZSTD_c_compressionLevel]: 1,
|
|
113
|
-
...
|
|
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 (
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
122
|
-
if (
|
|
123
|
-
if (
|
|
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 =
|
|
128
|
-
const threshold = parseBytes(
|
|
129
|
-
const syncMaxBytes = parseBytes(
|
|
130
|
-
const enforceEncoding =
|
|
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,
|
|
407
|
-
if (method === 'zstd') return zlib.zstdCompressSync(body,
|
|
408
|
-
if (method === 'br') return zlib.brotliCompressSync(body,
|
|
409
|
-
if (method === 'gzip') return zlib.gzipSync(body,
|
|
410
|
-
return zlib.deflateSync(body,
|
|
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,4 @@
|
|
|
1
|
-
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
2
|
-
import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
|
|
1
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
3
2
|
import { onFinished } from '../onFinished.js'
|
|
4
3
|
import type { BackendRequest, BackendRequestHandler } from './server.model.js'
|
|
5
4
|
|
|
@@ -32,7 +31,7 @@ export function requestLoggerMiddleware(
|
|
|
32
31
|
return next()
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const started =
|
|
34
|
+
const started = nowUnixMillis()
|
|
36
35
|
|
|
37
36
|
let url = req.originalUrl.split('?')[0]!
|
|
38
37
|
if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
|
|
@@ -1,7 +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 { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
4
|
+
import { _ms, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
5
5
|
import { _percentiles } from '@naturalcycles/js-lib/math/math.util.js'
|
|
6
6
|
import { NumberStack } from '@naturalcycles/js-lib/math/stack.util.js'
|
|
7
7
|
import { _get, _mapValues } from '@naturalcycles/js-lib/object/object.util.js'
|
|
@@ -106,13 +106,13 @@ export const serverStatsHTMLHandler: BackendRequestHandler = (req, res) => {
|
|
|
106
106
|
* Unlocks serverStatsHTMLHandler
|
|
107
107
|
*/
|
|
108
108
|
export function serverStatsMiddleware(): BackendRequestHandler {
|
|
109
|
-
let lastCleanup =
|
|
109
|
+
let lastCleanup = nowUnixMillis()
|
|
110
110
|
|
|
111
111
|
return function serverStatsHandler(req, res, next) {
|
|
112
|
-
const started =
|
|
112
|
+
const started = nowUnixMillis()
|
|
113
113
|
|
|
114
114
|
onFinished(res, () => {
|
|
115
|
-
const now =
|
|
115
|
+
const now = nowUnixMillis()
|
|
116
116
|
const latency = now - started
|
|
117
117
|
|
|
118
118
|
// Only requests that matched a route are recorded by their route pattern.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
2
|
-
import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
|
|
1
|
+
import { _since, nowUnixMillis } from '@naturalcycles/js-lib/datetime/time.util.js'
|
|
3
2
|
import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib/colors'
|
|
4
3
|
import { onFinished } from '../onFinished.js'
|
|
5
4
|
import { logRequestWithColors } from './request.log.util.js'
|
|
@@ -23,7 +22,7 @@ export function simpleRequestLoggerMiddleware(
|
|
|
23
22
|
const { logStart = false, logFinish = true } = cfg
|
|
24
23
|
|
|
25
24
|
return (req, res, next) => {
|
|
26
|
-
const started =
|
|
25
|
+
const started = nowUnixMillis()
|
|
27
26
|
|
|
28
27
|
if (logStart) {
|
|
29
28
|
req.log(['>>', req.method, boldGrey(req.url)].join(' '))
|