@technomoron/api-server-base 1.1.5 → 1.1.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.
@@ -325,7 +325,7 @@ function fillConfig(config) {
325
325
  accessCookie: config.accessCookie ?? 'dat',
326
326
  refreshCookie: config.refreshCookie ?? 'drt',
327
327
  accessExpiry: config.accessExpiry ?? 60 * 15,
328
- refreshExpiry: config.refreshExpiry ?? 30 * 24 * 60 * 60 * 1000,
328
+ refreshExpiry: config.refreshExpiry ?? 30 * 24 * 60 * 60,
329
329
  authApi: config.authApi ?? false,
330
330
  devMode: config.devMode ?? false,
331
331
  hydrateGetBody: config.hydrateGetBody ?? true,
@@ -628,18 +628,18 @@ class ApiServer {
628
628
  handle_request(handler, auth) {
629
629
  return async (req, res, next) => {
630
630
  void next;
631
+ const apiReq = {
632
+ server: this,
633
+ req,
634
+ res,
635
+ token: '',
636
+ tokenData: null,
637
+ getClientInfo: () => ensureClientInfo(apiReq),
638
+ getClientIp: () => ensureClientInfo(apiReq).ip,
639
+ getClientIpChain: () => ensureClientInfo(apiReq).ipchain
640
+ };
641
+ this.currReq = apiReq;
631
642
  try {
632
- const apiReq = {
633
- server: this,
634
- req,
635
- res,
636
- token: '',
637
- tokenData: null,
638
- getClientInfo: () => ensureClientInfo(apiReq),
639
- getClientIp: () => ensureClientInfo(apiReq).ip,
640
- getClientIpChain: () => ensureClientInfo(apiReq).ipchain
641
- };
642
- this.currReq = apiReq;
643
643
  if (this.config.hydrateGetBody) {
644
644
  hydrateGetBody(apiReq.req);
645
645
  }
@@ -660,7 +660,11 @@ class ApiServer {
660
660
  throw new ApiError({ code: 500, message: 'Handler result must start with a numeric status code' });
661
661
  }
662
662
  const message = typeof rawMessage === 'string' ? rawMessage : 'Success';
663
- res.status(code).json({ code, message, data });
663
+ const responsePayload = { code, message, data };
664
+ if (this.config.debug) {
665
+ this.dumpResponse(apiReq, responsePayload, code);
666
+ }
667
+ res.status(code).json(responsePayload);
664
668
  }
665
669
  catch (error) {
666
670
  if (error instanceof ApiError || isApiErrorLike(error)) {
@@ -668,20 +672,28 @@ class ApiServer {
668
672
  const normalizedErrors = apiError.errors && typeof apiError.errors === 'object' && !Array.isArray(apiError.errors)
669
673
  ? apiError.errors
670
674
  : {};
671
- res.status(apiError.code).json({
675
+ const errorPayload = {
672
676
  code: apiError.code,
673
677
  message: apiError.message,
674
678
  data: apiError.data ?? null,
675
679
  errors: normalizedErrors
676
- });
680
+ };
681
+ if (this.config.debug) {
682
+ this.dumpResponse(apiReq, errorPayload, apiError.code);
683
+ }
684
+ res.status(apiError.code).json(errorPayload);
677
685
  }
678
686
  else {
679
- res.status(500).json({
687
+ const errorPayload = {
680
688
  code: 500,
681
689
  message: this.guessExceptionText(error),
682
690
  data: null,
683
691
  errors: {}
684
- });
692
+ };
693
+ if (this.config.debug) {
694
+ this.dumpResponse(apiReq, errorPayload, 500);
695
+ }
696
+ res.status(500).json(errorPayload);
685
697
  }
686
698
  }
687
699
  };
@@ -719,6 +731,59 @@ class ApiServer {
719
731
  console.log('Headers:', req.headers);
720
732
  console.log('------------------------');
721
733
  }
734
+ formatDebugValue(value, maxLength = 50, seen = new WeakSet()) {
735
+ if (value === null || value === undefined) {
736
+ return value;
737
+ }
738
+ if (typeof value === 'string') {
739
+ return value.length <= maxLength
740
+ ? value
741
+ : `${value.slice(0, maxLength)}… [truncated ${value.length - maxLength} chars]`;
742
+ }
743
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
744
+ return value;
745
+ }
746
+ if (typeof value === 'symbol') {
747
+ return value.toString();
748
+ }
749
+ if (value instanceof Date) {
750
+ return value.toISOString();
751
+ }
752
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
753
+ return `<Buffer length=${value.length}>`;
754
+ }
755
+ if (typeof value === 'function') {
756
+ return `[Function ${value.name || 'anonymous'}]`;
757
+ }
758
+ if (typeof value === 'object') {
759
+ const obj = value;
760
+ if (seen.has(obj)) {
761
+ return '[Circular]';
762
+ }
763
+ seen.add(obj);
764
+ if (Array.isArray(value)) {
765
+ const arr = value.map((item) => this.formatDebugValue(item, maxLength, seen));
766
+ seen.delete(obj);
767
+ return arr;
768
+ }
769
+ const recordValue = value;
770
+ const entries = Object.entries(recordValue).reduce((acc, [key, val]) => {
771
+ acc[key] = this.formatDebugValue(val, maxLength, seen);
772
+ return acc;
773
+ }, {});
774
+ seen.delete(obj);
775
+ return entries;
776
+ }
777
+ return value;
778
+ }
779
+ dumpResponse(apiReq, payload, status) {
780
+ const url = apiReq.req.originalUrl || apiReq.req.url;
781
+ console.log('--- Outgoing Response! ---');
782
+ console.log('URL:', url);
783
+ console.log('Status:', status);
784
+ console.log('Payload:', this.formatDebugValue(payload));
785
+ console.log('--------------------------');
786
+ }
722
787
  }
723
788
  exports.ApiServer = ApiServer;
724
789
  exports.default = ApiServer;
@@ -9,7 +9,7 @@ import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
9
9
  import { ApiModule } from './api-module.js';
10
10
  import type { ApiAuthClass, ApiKey } from './api-module.js';
11
11
  import type { AuthProviderModule } from './auth-module.js';
12
- import type { AuthStorage, AuthTokenData } from './auth-storage.js';
12
+ import type { AuthStorage, AuthTokenData, AuthTokenMetadata } from './auth-storage.js';
13
13
  export type { Application, Request, Response, NextFunction, Router } from 'express';
14
14
  export type { Multer } from 'multer';
15
15
  export type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
@@ -35,7 +35,7 @@ interface JwtDecodeResult<T> {
35
35
  data?: T;
36
36
  error?: string;
37
37
  }
38
- export interface ApiTokenData extends JwtPayload {
38
+ export interface ApiTokenData extends JwtPayload, AuthTokenMetadata {
39
39
  uid: unknown;
40
40
  iat?: number;
41
41
  exp?: number;
@@ -144,5 +144,7 @@ export declare class ApiServer {
144
144
  private handle_request;
145
145
  api<T extends ApiModule<any>>(module: T): this;
146
146
  dumpRequest(apiReq: ApiRequest): void;
147
+ private formatDebugValue;
148
+ dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
147
149
  }
148
150
  export default ApiServer;
@@ -9,6 +9,7 @@ export interface AuthTokenMetadata {
9
9
  ip?: string;
10
10
  os?: string;
11
11
  scope?: string | string[];
12
+ loginType?: string;
12
13
  revokeSessions?: 'device' | 'domain' | 'client' | 'user';
13
14
  }
14
15
  export interface AuthTokenData extends AuthTokenMetadata {
@@ -9,7 +9,7 @@ import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
9
9
  import { ApiModule } from './api-module.js';
10
10
  import type { ApiAuthClass, ApiKey } from './api-module.js';
11
11
  import type { AuthProviderModule } from './auth-module.js';
12
- import type { AuthStorage, AuthTokenData } from './auth-storage.js';
12
+ import type { AuthStorage, AuthTokenData, AuthTokenMetadata } from './auth-storage.js';
13
13
  export type { Application, Request, Response, NextFunction, Router } from 'express';
14
14
  export type { Multer } from 'multer';
15
15
  export type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
@@ -35,7 +35,7 @@ interface JwtDecodeResult<T> {
35
35
  data?: T;
36
36
  error?: string;
37
37
  }
38
- export interface ApiTokenData extends JwtPayload {
38
+ export interface ApiTokenData extends JwtPayload, AuthTokenMetadata {
39
39
  uid: unknown;
40
40
  iat?: number;
41
41
  exp?: number;
@@ -144,5 +144,7 @@ export declare class ApiServer {
144
144
  private handle_request;
145
145
  api<T extends ApiModule<any>>(module: T): this;
146
146
  dumpRequest(apiReq: ApiRequest): void;
147
+ private formatDebugValue;
148
+ dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
147
149
  }
148
150
  export default ApiServer;
@@ -317,7 +317,7 @@ function fillConfig(config) {
317
317
  accessCookie: config.accessCookie ?? 'dat',
318
318
  refreshCookie: config.refreshCookie ?? 'drt',
319
319
  accessExpiry: config.accessExpiry ?? 60 * 15,
320
- refreshExpiry: config.refreshExpiry ?? 30 * 24 * 60 * 60 * 1000,
320
+ refreshExpiry: config.refreshExpiry ?? 30 * 24 * 60 * 60,
321
321
  authApi: config.authApi ?? false,
322
322
  devMode: config.devMode ?? false,
323
323
  hydrateGetBody: config.hydrateGetBody ?? true,
@@ -620,18 +620,18 @@ export class ApiServer {
620
620
  handle_request(handler, auth) {
621
621
  return async (req, res, next) => {
622
622
  void next;
623
+ const apiReq = {
624
+ server: this,
625
+ req,
626
+ res,
627
+ token: '',
628
+ tokenData: null,
629
+ getClientInfo: () => ensureClientInfo(apiReq),
630
+ getClientIp: () => ensureClientInfo(apiReq).ip,
631
+ getClientIpChain: () => ensureClientInfo(apiReq).ipchain
632
+ };
633
+ this.currReq = apiReq;
623
634
  try {
624
- const apiReq = {
625
- server: this,
626
- req,
627
- res,
628
- token: '',
629
- tokenData: null,
630
- getClientInfo: () => ensureClientInfo(apiReq),
631
- getClientIp: () => ensureClientInfo(apiReq).ip,
632
- getClientIpChain: () => ensureClientInfo(apiReq).ipchain
633
- };
634
- this.currReq = apiReq;
635
635
  if (this.config.hydrateGetBody) {
636
636
  hydrateGetBody(apiReq.req);
637
637
  }
@@ -652,7 +652,11 @@ export class ApiServer {
652
652
  throw new ApiError({ code: 500, message: 'Handler result must start with a numeric status code' });
653
653
  }
654
654
  const message = typeof rawMessage === 'string' ? rawMessage : 'Success';
655
- res.status(code).json({ code, message, data });
655
+ const responsePayload = { code, message, data };
656
+ if (this.config.debug) {
657
+ this.dumpResponse(apiReq, responsePayload, code);
658
+ }
659
+ res.status(code).json(responsePayload);
656
660
  }
657
661
  catch (error) {
658
662
  if (error instanceof ApiError || isApiErrorLike(error)) {
@@ -660,20 +664,28 @@ export class ApiServer {
660
664
  const normalizedErrors = apiError.errors && typeof apiError.errors === 'object' && !Array.isArray(apiError.errors)
661
665
  ? apiError.errors
662
666
  : {};
663
- res.status(apiError.code).json({
667
+ const errorPayload = {
664
668
  code: apiError.code,
665
669
  message: apiError.message,
666
670
  data: apiError.data ?? null,
667
671
  errors: normalizedErrors
668
- });
672
+ };
673
+ if (this.config.debug) {
674
+ this.dumpResponse(apiReq, errorPayload, apiError.code);
675
+ }
676
+ res.status(apiError.code).json(errorPayload);
669
677
  }
670
678
  else {
671
- res.status(500).json({
679
+ const errorPayload = {
672
680
  code: 500,
673
681
  message: this.guessExceptionText(error),
674
682
  data: null,
675
683
  errors: {}
676
- });
684
+ };
685
+ if (this.config.debug) {
686
+ this.dumpResponse(apiReq, errorPayload, 500);
687
+ }
688
+ res.status(500).json(errorPayload);
677
689
  }
678
690
  }
679
691
  };
@@ -711,5 +723,58 @@ export class ApiServer {
711
723
  console.log('Headers:', req.headers);
712
724
  console.log('------------------------');
713
725
  }
726
+ formatDebugValue(value, maxLength = 50, seen = new WeakSet()) {
727
+ if (value === null || value === undefined) {
728
+ return value;
729
+ }
730
+ if (typeof value === 'string') {
731
+ return value.length <= maxLength
732
+ ? value
733
+ : `${value.slice(0, maxLength)}… [truncated ${value.length - maxLength} chars]`;
734
+ }
735
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
736
+ return value;
737
+ }
738
+ if (typeof value === 'symbol') {
739
+ return value.toString();
740
+ }
741
+ if (value instanceof Date) {
742
+ return value.toISOString();
743
+ }
744
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
745
+ return `<Buffer length=${value.length}>`;
746
+ }
747
+ if (typeof value === 'function') {
748
+ return `[Function ${value.name || 'anonymous'}]`;
749
+ }
750
+ if (typeof value === 'object') {
751
+ const obj = value;
752
+ if (seen.has(obj)) {
753
+ return '[Circular]';
754
+ }
755
+ seen.add(obj);
756
+ if (Array.isArray(value)) {
757
+ const arr = value.map((item) => this.formatDebugValue(item, maxLength, seen));
758
+ seen.delete(obj);
759
+ return arr;
760
+ }
761
+ const recordValue = value;
762
+ const entries = Object.entries(recordValue).reduce((acc, [key, val]) => {
763
+ acc[key] = this.formatDebugValue(val, maxLength, seen);
764
+ return acc;
765
+ }, {});
766
+ seen.delete(obj);
767
+ return entries;
768
+ }
769
+ return value;
770
+ }
771
+ dumpResponse(apiReq, payload, status) {
772
+ const url = apiReq.req.originalUrl || apiReq.req.url;
773
+ console.log('--- Outgoing Response! ---');
774
+ console.log('URL:', url);
775
+ console.log('Status:', status);
776
+ console.log('Payload:', this.formatDebugValue(payload));
777
+ console.log('--------------------------');
778
+ }
714
779
  }
715
780
  export default ApiServer;
@@ -9,6 +9,7 @@ export interface AuthTokenMetadata {
9
9
  ip?: string;
10
10
  os?: string;
11
11
  scope?: string | string[];
12
+ loginType?: string;
12
13
  revokeSessions?: 'device' | 'domain' | 'client' | 'user';
13
14
  }
14
15
  export interface AuthTokenData extends AuthTokenMetadata {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/api-server-base",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Api Server Skeleton / Base Class",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",