@naturalcycles/backend-lib 9.81.0 → 9.83.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.
@@ -31,7 +31,7 @@ export interface RequireAdminCfg {
31
31
  /**
32
32
  * @deprecated Use `AuthMiddleware`.
33
33
  */
34
- export type AdminMiddleware = (reqPermissions?: string[], cfg?: RequireAdminCfg) => BackendRequestHandler;
34
+ export type AdminMiddleware = (reqPermissions?: readonly string[], cfg?: RequireAdminCfg) => BackendRequestHandler;
35
35
  /**
36
36
  * @deprecated Use `AuthService.getLoginRedirectMiddleware`.
37
37
  */
@@ -44,7 +44,7 @@ export declare function createAdminMiddleware(adminService: BaseAdminService, cf
44
44
  *
45
45
  * @deprecated Use `AuthService.getLoginRedirectMiddleware`.
46
46
  */
47
- export declare function requireAdminPermissions(adminService: BaseAdminService, reqPermissions?: string[], cfg?: RequireAdminCfg): BackendRequestHandler;
47
+ export declare function requireAdminPermissions(adminService: BaseAdminService, reqPermissions?: readonly string[], cfg?: RequireAdminCfg): BackendRequestHandler;
48
48
  /**
49
49
  * @deprecated Use `loginHtmlHandler`.
50
50
  */
@@ -19,8 +19,8 @@ export declare class BaseAdminService {
19
19
  * Returns AdminInfo if it has all required permissions.
20
20
  * Otherwise returns undefined
21
21
  */
22
- hasPermissions(req: BackendRequest, reqPermissions?: string[], meta?: AnyObject): Promise<AdminInfo | undefined>;
23
- requirePermissions(req: BackendRequest, reqPermissions?: string[], meta?: AnyObject, andComparison?: boolean): Promise<AdminInfo>;
22
+ hasPermissions(req: BackendRequest, reqPermissions?: readonly string[], meta?: AnyObject): Promise<AdminInfo | undefined>;
23
+ requirePermissions(req: BackendRequest, reqPermissions?: readonly string[], meta?: AnyObject, andComparison?: boolean): Promise<AdminInfo>;
24
24
  /**
25
25
  * Doesn't check permissions, nor `authEnabled`. See `checkPermissions`.
26
26
  */
@@ -28,7 +28,7 @@ export declare class BaseAdminService {
28
28
  /**
29
29
  * Doesn't log or throw. See `requireGranted`.
30
30
  */
31
- checkPermissions(admin: ResolvedAdmin, reqPermissions?: string[], opt?: CheckPermissionsOptions): CheckPermissionsResult;
31
+ checkPermissions(admin: ResolvedAdmin, reqPermissions?: readonly string[], opt?: CheckPermissionsOptions): CheckPermissionsResult;
32
32
  /**
33
33
  * Throws 401 if there's no email, 403 if not granted.
34
34
  */
@@ -51,7 +51,7 @@ export declare class BaseAdminService {
51
51
  /**
52
52
  * To be extended.
53
53
  */
54
- protected onPermissionCheck(req: BackendRequest, email: string, reqPermissions: string[], required: boolean, granted: boolean, meta?: AnyObject): Promise<void>;
54
+ protected onPermissionCheck(req: BackendRequest, email: string, reqPermissions: readonly string[], required: boolean, granted: boolean, meta?: AnyObject): Promise<void>;
55
55
  /**
56
56
  * Install it on POST /admin/login url
57
57
  *
@@ -113,7 +113,7 @@ export interface CheckPermissionsResult {
113
113
  email?: string;
114
114
  isAdmin: boolean;
115
115
  granted: boolean;
116
- reqPermissions: string[];
117
- grantedPermissions: string[];
116
+ reqPermissions: readonly string[];
117
+ grantedPermissions: readonly string[];
118
118
  authDisabled: boolean;
119
119
  }
@@ -27,7 +27,7 @@ export declare class AuthService<CTX = AnyObject> {
27
27
  * Returns the granted permissions (all of the subject's if alwaysAuthorized),
28
28
  * or `{ subject: 'authDisabled', permissions: [] }` if auth is disabled.
29
29
  */
30
- require(resolved: ResolvedAuth | undefined, reqPermissions?: string[], opt?: AuthCheckOptions<CTX>): PermissionInfo;
30
+ require(resolved: ResolvedAuth | undefined, reqPermissions?: readonly string[], opt?: AuthCheckOptions<CTX>): PermissionInfo;
31
31
  private checkSubject;
32
32
  private assertAuthenticated;
33
33
  private authRequiredData;
@@ -67,9 +67,9 @@ export interface AuthCheck {
67
67
  subject: string;
68
68
  granted: boolean;
69
69
  alwaysAuthorized: boolean;
70
- reqPermissions: string[];
70
+ reqPermissions: readonly string[];
71
71
  /** The granted permissions, or reqPermissions if denied */
72
- checkedPermissions: string[];
72
+ checkedPermissions: readonly string[];
73
73
  }
74
74
  export interface AuthLoginRedirectMiddlewareCfg<CTX> extends AuthMiddlewareCfg<CTX> {
75
75
  loginHtmlPath: string;
@@ -19,10 +19,10 @@ export declare class HttpDB extends BaseCommonDB implements CommonDB {
19
19
  getTables(): Promise<string[]>;
20
20
  getTableSchema<ROW extends ObjectWithId>(table: string): Promise<JsonSchema<ROW>>;
21
21
  resetCache(table?: string): Promise<void>;
22
- getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: CommonDBOptions): Promise<ROW[]>;
22
+ getByIds<ROW extends ObjectWithId>(table: string, ids: readonly ROW['id'][], opt?: CommonDBOptions): Promise<ROW[]>;
23
23
  runQuery<ROW extends ObjectWithId>(query: DBQuery<ROW>, opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
24
24
  runQueryCount<ROW extends ObjectWithId>(query: DBQuery<ROW>, opt?: CommonDBOptions): Promise<number>;
25
- saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
26
- deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number>;
25
+ saveBatch<ROW extends ObjectWithId>(table: string, rows: readonly ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
26
+ deleteByIds(table: string, ids: readonly string[], opt?: CommonDBOptions): Promise<number>;
27
27
  deleteByQuery<ROW extends ObjectWithId>(query: DBQuery<ROW>, opt?: CommonDBOptions): Promise<number>;
28
28
  }
package/dist/db/httpDB.js CHANGED
@@ -15,6 +15,8 @@ export class HttpDB extends BaseCommonDB {
15
15
  transactions: false,
16
16
  patchByQuery: false,
17
17
  increment: false,
18
+ // httpDBRequestHandler's options schema strips readAt
19
+ timeMachine: false,
18
20
  };
19
21
  constructor(cfg) {
20
22
  super();
@@ -2,4 +2,4 @@ import type { BackendRequestHandler, BackendRouter } from '../server/server.mode
2
2
  /**
3
3
  * Convenience method.
4
4
  */
5
- export declare function getDefaultRouter(defaultHandlers?: BackendRequestHandler[]): BackendRouter;
5
+ export declare function getDefaultRouter(defaultHandlers?: readonly BackendRequestHandler[]): BackendRouter;
@@ -2,7 +2,7 @@ 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
4
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js';
5
- import { _percentile } from '@naturalcycles/js-lib/math/math.util.js';
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';
8
8
  import { htmlEscape } from '@naturalcycles/js-lib/string/escape.js';
@@ -31,6 +31,7 @@ export const serverStatsHTMLHandler = (req, res) => {
31
31
  s.pc = _mapValues(s.stack.percentiles(percentiles), (_k, v) => Math.round(v), { mutate: true });
32
32
  });
33
33
  const allLatencies = _stringMapValues(serverStatsMap).flatMap(s => s.stack.items);
34
+ const allPercentiles = _percentiles(allLatencies, percentiles);
34
35
  const all2xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['2xx']));
35
36
  const all4xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['4xx']));
36
37
  const all5xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['5xx']));
@@ -55,7 +56,7 @@ export const serverStatsHTMLHandler = (req, res) => {
55
56
  `<td align="right"><pre>${all2xx}</pre></td>`,
56
57
  `<td align="right"><pre>${all4xx}</pre></td>`,
57
58
  `<td align="right"><pre>${all5xx}</pre></td>`,
58
- ...percentiles.map(pc => `<td align="right"><pre>${Math.round(_percentile(allLatencies, pc))}</pre></td>`),
59
+ ...percentiles.map(pc => `<td align="right"><pre>${Math.round(allPercentiles[pc])}</pre></td>`),
59
60
  `</tr>`,
60
61
  ..._sortBy(_stringMapEntries(serverStatsMap), ([_, stat]) => _get(stat, sortBy), {
61
62
  dir: asc ? 'asc' : 'desc',
@@ -37,7 +37,7 @@ export interface UploadedFile {
37
37
  /** Content-Transfer-Encoding of the file part. */
38
38
  encoding: string;
39
39
  /** File contents. */
40
- data: Buffer;
40
+ data: Buffer<ArrayBuffer>;
41
41
  /** Size of `data` in bytes. */
42
42
  size: NumberOfBytes;
43
43
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.81.0",
4
+ "version": "9.83.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/db-lib": "^10",
7
7
  "@naturalcycles/js-lib": "^15",
@@ -76,7 +76,7 @@
76
76
  "directory": "packages/backend-lib"
77
77
  },
78
78
  "engines": {
79
- "node": ">=24.10.0"
79
+ "node": ">=26.12.1"
80
80
  },
81
81
  "description": "Standard library for making Express.js based backend services",
82
82
  "author": "Natural Cycles Team",
@@ -42,7 +42,7 @@ export interface RequireAdminCfg {
42
42
  * @deprecated Use `AuthMiddleware`.
43
43
  */
44
44
  export type AdminMiddleware = (
45
- reqPermissions?: string[],
45
+ reqPermissions?: readonly string[],
46
46
  cfg?: RequireAdminCfg,
47
47
  ) => BackendRequestHandler
48
48
 
@@ -70,7 +70,7 @@ export function createAdminMiddleware(
70
70
  */
71
71
  export function requireAdminPermissions(
72
72
  adminService: BaseAdminService,
73
- reqPermissions: string[] = [],
73
+ reqPermissions: readonly string[] = [],
74
74
  cfg: RequireAdminCfg = {},
75
75
  ): BackendRequestHandler {
76
76
  const {
@@ -70,7 +70,7 @@ export class BaseAdminService {
70
70
  */
71
71
  async hasPermissions(
72
72
  req: BackendRequest,
73
- reqPermissions: string[] = [],
73
+ reqPermissions: readonly string[] = [],
74
74
  meta: AnyObject = {},
75
75
  ): Promise<AdminInfo | undefined> {
76
76
  if (!this.cfg.authEnabled) return adminInfoDisabled()
@@ -93,7 +93,7 @@ export class BaseAdminService {
93
93
 
94
94
  async requirePermissions(
95
95
  req: BackendRequest,
96
- reqPermissions: string[] = [],
96
+ reqPermissions: readonly string[] = [],
97
97
  meta: AnyObject = {},
98
98
  andComparison = true,
99
99
  ): Promise<AdminInfo> {
@@ -128,7 +128,7 @@ export class BaseAdminService {
128
128
  */
129
129
  checkPermissions(
130
130
  admin: ResolvedAdmin,
131
- reqPermissions: string[] = [],
131
+ reqPermissions: readonly string[] = [],
132
132
  opt: CheckPermissionsOptions = {},
133
133
  ): CheckPermissionsResult {
134
134
  const { andComparison = true } = opt
@@ -254,7 +254,7 @@ export class BaseAdminService {
254
254
  protected async onPermissionCheck(
255
255
  req: BackendRequest,
256
256
  email: string,
257
- reqPermissions: string[],
257
+ reqPermissions: readonly string[],
258
258
  required: boolean,
259
259
  granted: boolean,
260
260
  meta: AnyObject = {},
@@ -366,7 +366,7 @@ export interface CheckPermissionsResult {
366
366
  email?: string
367
367
  isAdmin: boolean
368
368
  granted: boolean
369
- reqPermissions: string[]
370
- grantedPermissions: string[]
369
+ reqPermissions: readonly string[]
370
+ grantedPermissions: readonly string[]
371
371
  authDisabled: boolean
372
372
  }
@@ -34,7 +34,7 @@ export function createSecureHeaderMiddleware(cfg: SecureHeaderMiddlewareCfg): Ad
34
34
 
35
35
  function requireSecureHeaderOrAdmin(
36
36
  cfg: SecureHeaderMiddlewareCfg,
37
- reqPermissions?: string[],
37
+ reqPermissions?: readonly string[],
38
38
  ): BackendRequestHandler {
39
39
  const { secureHeaderKey = 'Authorization', secureHeaderValue } = cfg
40
40
 
@@ -34,7 +34,7 @@ export class AuthService<CTX = AnyObject> {
34
34
 
35
35
  private createHandler(
36
36
  cfg: AuthMiddlewareCfg<CTX>,
37
- reqPermissions: string[] = [],
37
+ reqPermissions: readonly string[] = [],
38
38
  loginHref?: string,
39
39
  ): BackendRequestHandler {
40
40
  const {
@@ -100,7 +100,7 @@ export class AuthService<CTX = AnyObject> {
100
100
  */
101
101
  require(
102
102
  resolved: ResolvedAuth | undefined,
103
- reqPermissions: string[] = [],
103
+ reqPermissions: readonly string[] = [],
104
104
  opt: AuthCheckOptions<CTX> = {},
105
105
  ): PermissionInfo {
106
106
  if (!this.cfg.authEnabled) return { subject: 'authDisabled', permissions: [] }
@@ -120,9 +120,9 @@ export class AuthService<CTX = AnyObject> {
120
120
 
121
121
  private checkSubject(
122
122
  resolved: AuthenticatedAuth,
123
- reqPermissions: string[],
123
+ reqPermissions: readonly string[],
124
124
  opt: AuthCheckOptions<CTX>,
125
- ): string[] | undefined {
125
+ ): readonly string[] | undefined {
126
126
  const { subject, permissions, alwaysAuthorized = false } = resolved
127
127
  const { andComparison = true, ctx } = opt
128
128
 
@@ -160,8 +160,8 @@ export class AuthService<CTX = AnyObject> {
160
160
 
161
161
  private assertGranted(
162
162
  subject: string,
163
- grantedPermissions: string[] | undefined,
164
- reqPermissions: string[],
163
+ grantedPermissions: readonly string[] | undefined,
164
+ reqPermissions: readonly string[],
165
165
  ): asserts grantedPermissions is string[] {
166
166
  _assert(grantedPermissions, `Permissions required: [${reqPermissions.join(', ')}]`, {
167
167
  permissionsRequired: reqPermissions,
@@ -175,8 +175,8 @@ export class AuthService<CTX = AnyObject> {
175
175
  * undefined - not granted.
176
176
  */
177
177
  private getGrantedPermissions(
178
- permissions: string[] | undefined,
179
- reqPermissions: string[],
178
+ permissions: readonly string[] | undefined,
179
+ reqPermissions: readonly string[],
180
180
  andComparison: boolean,
181
181
  ): string[] | undefined {
182
182
  if (!permissions) return
@@ -242,9 +242,9 @@ export interface AuthCheck {
242
242
  subject: string
243
243
  granted: boolean
244
244
  alwaysAuthorized: boolean
245
- reqPermissions: string[]
245
+ reqPermissions: readonly string[]
246
246
  /** The granted permissions, or reqPermissions if denied */
247
- checkedPermissions: string[]
247
+ checkedPermissions: readonly string[]
248
248
  }
249
249
 
250
250
  export interface AuthLoginRedirectMiddlewareCfg<CTX> extends AuthMiddlewareCfg<CTX> {
package/src/db/httpDB.ts CHANGED
@@ -30,6 +30,8 @@ export class HttpDB extends BaseCommonDB implements CommonDB {
30
30
  transactions: false,
31
31
  patchByQuery: false,
32
32
  increment: false,
33
+ // httpDBRequestHandler's options schema strips readAt
34
+ timeMachine: false,
33
35
  }
34
36
 
35
37
  constructor(public cfg: HttpDBCfg) {
@@ -61,7 +63,7 @@ export class HttpDB extends BaseCommonDB implements CommonDB {
61
63
 
62
64
  override async getByIds<ROW extends ObjectWithId>(
63
65
  table: string,
64
- ids: ROW['id'][],
66
+ ids: readonly ROW['id'][],
65
67
  opt?: CommonDBOptions,
66
68
  ): Promise<ROW[]> {
67
69
  return await this.fetcher.put(`getByIds`, {
@@ -99,7 +101,7 @@ export class HttpDB extends BaseCommonDB implements CommonDB {
99
101
 
100
102
  override async saveBatch<ROW extends ObjectWithId>(
101
103
  table: string,
102
- rows: ROW[],
104
+ rows: readonly ROW[],
103
105
  opt?: CommonDBSaveOptions<ROW>,
104
106
  ): Promise<void> {
105
107
  await this.fetcher.putVoid(`saveBatch`, {
@@ -111,7 +113,11 @@ export class HttpDB extends BaseCommonDB implements CommonDB {
111
113
  })
112
114
  }
113
115
 
114
- override async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
116
+ override async deleteByIds(
117
+ table: string,
118
+ ids: readonly string[],
119
+ opt?: CommonDBOptions,
120
+ ): Promise<number> {
115
121
  return await this.fetcher.put(`deleteByIds`, {
116
122
  json: {
117
123
  table,
@@ -147,7 +147,10 @@ export async function createDefaultApp(cfg: DefaultAppCfg): Promise<BackendAppli
147
147
  return app
148
148
  }
149
149
 
150
- function useHandlers(app: BackendApplication, handlers: BackendRequestHandlerCfg[] = []): void {
150
+ function useHandlers(
151
+ app: BackendApplication,
152
+ handlers: readonly BackendRequestHandlerCfg[] = [],
153
+ ): void {
151
154
  handlers
152
155
  .map<BackendRequestHandlerWithPath>(cfg => {
153
156
  if (typeof cfg === 'function') {
@@ -4,7 +4,9 @@ import type { BackendRequestHandler, BackendRouter } from '../server/server.mode
4
4
  /**
5
5
  * Convenience method.
6
6
  */
7
- export function getDefaultRouter(defaultHandlers: BackendRequestHandler[] = []): BackendRouter {
7
+ export function getDefaultRouter(
8
+ defaultHandlers: readonly BackendRequestHandler[] = [],
9
+ ): BackendRouter {
8
10
  const router = Router()
9
11
 
10
12
  // Use default handlers
@@ -413,7 +413,7 @@ function compressSync(method: string, body: Buffer, opts: CodecOptions): Buffer
413
413
  function addListeners(
414
414
  stream: any,
415
415
  on: (type: string, listener: (...args: any[]) => void) => void,
416
- listeners: [string, (...args: any[]) => void][],
416
+ listeners: readonly [string, (...args: any[]) => void][],
417
417
  ): void {
418
418
  for (const [type, listener] of listeners) {
419
419
  on.call(stream, type, listener)
@@ -2,7 +2,7 @@ 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
4
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
5
- import { _percentile } from '@naturalcycles/js-lib/math/math.util.js'
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'
8
8
  import { htmlEscape } from '@naturalcycles/js-lib/string/escape.js'
@@ -51,6 +51,7 @@ export const serverStatsHTMLHandler: BackendRequestHandler = (req, res) => {
51
51
  s.pc = _mapValues(s.stack.percentiles(percentiles), (_k, v) => Math.round(v), { mutate: true })
52
52
  })
53
53
  const allLatencies = _stringMapValues(serverStatsMap).flatMap(s => s.stack.items)
54
+ const allPercentiles = _percentiles(allLatencies, percentiles)
54
55
  const all2xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['2xx']))
55
56
  const all4xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['4xx']))
56
57
  const all5xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['5xx']))
@@ -79,7 +80,7 @@ export const serverStatsHTMLHandler: BackendRequestHandler = (req, res) => {
79
80
  `<td align="right"><pre>${all4xx}</pre></td>`,
80
81
  `<td align="right"><pre>${all5xx}</pre></td>`,
81
82
  ...percentiles.map(
82
- pc => `<td align="right"><pre>${Math.round(_percentile(allLatencies, pc))}</pre></td>`,
83
+ pc => `<td align="right"><pre>${Math.round(allPercentiles[pc]!)}</pre></td>`,
83
84
  ),
84
85
  `</tr>`,
85
86
  ..._sortBy(_stringMapEntries(serverStatsMap), ([_, stat]) => _get(stat, sortBy) as string, {
@@ -136,7 +136,7 @@ export interface UploadedFile {
136
136
  /** Content-Transfer-Encoding of the file part. */
137
137
  encoding: string
138
138
  /** File contents. */
139
- data: Buffer
139
+ data: Buffer<ArrayBuffer>
140
140
  /** Size of `data` in bytes. */
141
141
  size: NumberOfBytes
142
142
  }
@@ -31,7 +31,7 @@ const REDACTED = 'REDACTED'
31
31
  /**
32
32
  * Mutates error
33
33
  */
34
- function redact(redactPaths: string[], obj: any, error: Error): void {
34
+ function redact(redactPaths: readonly string[], obj: any, error: Error): void {
35
35
  redactPaths
36
36
  .map(path => _get(obj, path) as string)
37
37
  .filter(Boolean)