@extk/expressive 0.4.4 → 0.5.1

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/README.md CHANGED
@@ -40,17 +40,18 @@ npm install @extk/expressive express
40
40
 
41
41
  ```ts
42
42
  import express from 'express';
43
- import { bootstrap, getDefaultFileLogger, ApiResponse, NotFoundError, SWG } from '@extk/expressive';
43
+ import { bootstrap, ApiResponse, NotFoundError, SWG } from '@extk/expressive';
44
44
 
45
- // 1. Bootstrap with a logger
45
+ // 1. Bootstrap with a logger (bring your own)
46
46
  const {
47
47
  expressiveServer,
48
48
  expressiveRouter,
49
49
  swaggerBuilder,
50
50
  notFoundMiddleware,
51
51
  getErrorHandlerMiddleware,
52
+ silently,
52
53
  } = bootstrap({
53
- logger: getDefaultFileLogger('my-api'),
54
+ logger: console, // any object with info/warn/error/debug
54
55
  });
55
56
 
56
57
  // 2. Configure swagger metadata
@@ -246,16 +247,56 @@ addRoute({
246
247
 
247
248
  This way your Zod schemas serve as the single source of truth for both runtime validation and API documentation.
248
249
 
250
+ ## silently
251
+
252
+ `silently` runs a function — sync or async — and suppresses any errors it throws. Errors are forwarded to `alertHandler` (if configured) or logged via the container logger.
253
+
254
+ ```ts
255
+ // fire-and-forget without crashing the process
256
+ silently(() => sendAnalyticsEvent(req));
257
+ silently(async () => await notifySlack('Server started'));
258
+ ```
259
+
249
260
  ## Logging
250
261
 
251
- Winston-based logging with daily rotating files in production and console output in development.
262
+ Expressive does not bundle a logger. Instead, `bootstrap` accepts any object that satisfies the `Logger` interface:
252
263
 
253
264
  ```ts
254
- import { getDefaultFileLogger, getDefaultConsoleLogger } from '@extk/expressive';
265
+ export type Logger = {
266
+ info(message: string, ...args: any[]): void;
267
+ error(message: string | Error | unknown, ...args: any[]): void;
268
+ warn(message: string, ...args: any[]): void;
269
+ debug(message: string, ...args: any[]): void;
270
+ };
271
+ ```
255
272
 
256
- const logger = getDefaultFileLogger('my-service'); // logs to ./logs/my-service-YYYY-MM-DD.log
257
- logger.info('Server started on port %d', 3000);
258
- logger.error('Something went wrong: %s', err.message);
273
+ This means you can pass `console` directly, or plug in any logging library (Winston, Pino, etc.):
274
+
275
+ ```ts
276
+ bootstrap({ logger: console });
277
+ ```
278
+
279
+ The `@extk/logger-cloudwatch` package from the same org is a drop-in fit:
280
+
281
+ ```ts
282
+ import { getCloudwatchLogger, getConsoleLogger } from '@extk/logger-cloudwatch';
283
+
284
+ // development
285
+ bootstrap({ logger: getConsoleLogger() });
286
+
287
+ // production — streams structured JSON logs to AWS CloudWatch
288
+ bootstrap({
289
+ logger: getCloudwatchLogger({
290
+ aws: {
291
+ region: 'us-east-1',
292
+ logGroup: '/my-app/production',
293
+ credentials: {
294
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
295
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
296
+ },
297
+ },
298
+ }),
299
+ });
259
300
  ```
260
301
 
261
302
  ## Utilities
package/dist/index.d.mts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as express from 'express';
2
- import express__default, { RequestHandler, Request } from 'express';
2
+ import express__default, { RequestHandler } from 'express';
3
3
  import * as express_serve_static_core from 'express-serve-static-core';
4
4
  import { RouteParameters } from 'express-serve-static-core';
5
- import winston, { Logger as Logger$1 } from 'winston';
6
5
  import { HelmetOptions } from 'helmet';
7
6
  import morgan from 'morgan';
8
7
 
@@ -17,19 +16,18 @@ type Pagination = {
17
16
  limit: number;
18
17
  offset: number;
19
18
  };
20
- type ReqSnapshot = {
21
- readonly query: qs.ParsedQs;
22
- readonly path: string;
23
- readonly method: string;
24
- readonly userId: string | number | undefined;
25
- };
26
19
 
27
20
  type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'trace';
28
21
  type OtherString = string & {};
29
22
  type OtherUnknown = unknown & {};
30
23
  type ContentType = 'application/json' | 'application/xml' | 'text/plain' | 'text/html' | OtherString;
31
- type AlertHandler = (err: Error & Record<string, any>, reqSnapshot?: ReqSnapshot) => void | Promise<void>;
32
- type Logger = Logger$1;
24
+ type AlertHandler = (err: Error & Record<string, any>, context?: string) => void | Promise<void>;
25
+ type Logger = {
26
+ info(message: string, ...args: any[]): void;
27
+ error(message: string | Error | unknown, ...args: any[]): void;
28
+ warn(message: string, ...args: any[]): void;
29
+ debug(message: string, ...args: any[]): void;
30
+ };
33
31
  type Container = {
34
32
  logger: Logger;
35
33
  alertHandler?: AlertHandler;
@@ -263,21 +261,12 @@ declare function slugify(text: string): string;
263
261
  declare function getTmpDir(): string;
264
262
  declare function getTmpPath(...steps: string[]): string;
265
263
  declare function parseDefaultPagination(query: PaginationQuery): Pagination;
266
- declare function createReqSnapshot(req: Request & {
267
- user?: {
268
- id?: string | number;
269
- };
270
- }): ReqSnapshot;
271
264
 
272
265
  declare function isDev(): boolean;
273
266
  declare function isProd(): boolean;
274
267
  declare function getEnvVar(configName: string): string;
275
268
  type Env = 'dev' | 'prod' | OtherString;
276
269
 
277
- declare const createLogger: (options?: winston.LoggerOptions) => winston.Logger;
278
- declare const getDefaultFileLogger: (name?: string) => winston.Logger;
279
- declare const getDefaultConsoleLogger: (name?: string) => winston.Logger;
280
-
281
270
  declare class ApiErrorResponse<T = undefined> {
282
271
  readonly status = "error";
283
272
  readonly message: string;
@@ -294,7 +283,7 @@ declare class ApiResponse<T = undefined> {
294
283
 
295
284
  declare function bootstrap(container: Container): {
296
285
  swaggerBuilder: () => SwaggerBuilder;
297
- silently: (fn: () => Promise<void>, reqSnapshot?: ReqSnapshot) => void;
286
+ silently: (fn: () => Promise<void> | void) => Promise<void>;
298
287
  notFoundMiddleware: (_req: express.Request, res: express.Response, _next: express.NextFunction) => void;
299
288
  getErrorHandlerMiddleware: (errorMapper?: (err: Error & Record<string, unknown>) => ApiError | null | undefined) => (err: Error & Record<string, unknown>, req: express.Request, res: express.Response, _next: express.NextFunction) => Promise<void>;
300
289
  expressiveServer: (configs?: {
@@ -321,4 +310,4 @@ declare function bootstrap(container: Container): {
321
310
  };
322
311
  };
323
312
 
324
- export { type AlertHandler, ApiError, ApiErrorResponse, ApiResponse, type AuthMethod, BadRequestError, type Container, type Content, type ContentType, DuplicateError, type Env, type ExpressHandler, type ExpressLocalsObj, type ExpressRoute, FileTooBigError, ForbiddenError, type HttpMethod, InternalError, InvalidCredentialsError, InvalidFileTypeError, type Logger, NotFoundError, type OtherString, type OtherUnknown, type Pagination, type PaginationQuery, type Param, type PathItem, type ReqSnapshot, SWG, type Schema, SchemaValidationError, type SecurityScheme, type Servers, type SwaggerConfig, TokenExpiredError, TooManyRequestsError, UserUnauthorizedError, bootstrap, createLogger, createReqSnapshot, getDefaultConsoleLogger, getDefaultFileLogger, getEnvVar, getTmpDir, getTmpPath, isDev, isProd, parseDefaultPagination, parseIdOrFail, parsePositiveInteger, slugify };
313
+ export { type AlertHandler, ApiError, ApiErrorResponse, ApiResponse, type AuthMethod, BadRequestError, type Container, type Content, type ContentType, DuplicateError, type Env, type ExpressHandler, type ExpressLocalsObj, type ExpressRoute, FileTooBigError, ForbiddenError, type HttpMethod, InternalError, InvalidCredentialsError, InvalidFileTypeError, type Logger, NotFoundError, type OtherString, type OtherUnknown, type Pagination, type PaginationQuery, type Param, type PathItem, SWG, type Schema, SchemaValidationError, type SecurityScheme, type Servers, type SwaggerConfig, TokenExpiredError, TooManyRequestsError, UserUnauthorizedError, bootstrap, getEnvVar, getTmpDir, getTmpPath, isDev, isProd, parseDefaultPagination, parseIdOrFail, parsePositiveInteger, slugify };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as express from 'express';
2
- import express__default, { RequestHandler, Request } from 'express';
2
+ import express__default, { RequestHandler } from 'express';
3
3
  import * as express_serve_static_core from 'express-serve-static-core';
4
4
  import { RouteParameters } from 'express-serve-static-core';
5
- import winston, { Logger as Logger$1 } from 'winston';
6
5
  import { HelmetOptions } from 'helmet';
7
6
  import morgan from 'morgan';
8
7
 
@@ -17,19 +16,18 @@ type Pagination = {
17
16
  limit: number;
18
17
  offset: number;
19
18
  };
20
- type ReqSnapshot = {
21
- readonly query: qs.ParsedQs;
22
- readonly path: string;
23
- readonly method: string;
24
- readonly userId: string | number | undefined;
25
- };
26
19
 
27
20
  type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'trace';
28
21
  type OtherString = string & {};
29
22
  type OtherUnknown = unknown & {};
30
23
  type ContentType = 'application/json' | 'application/xml' | 'text/plain' | 'text/html' | OtherString;
31
- type AlertHandler = (err: Error & Record<string, any>, reqSnapshot?: ReqSnapshot) => void | Promise<void>;
32
- type Logger = Logger$1;
24
+ type AlertHandler = (err: Error & Record<string, any>, context?: string) => void | Promise<void>;
25
+ type Logger = {
26
+ info(message: string, ...args: any[]): void;
27
+ error(message: string | Error | unknown, ...args: any[]): void;
28
+ warn(message: string, ...args: any[]): void;
29
+ debug(message: string, ...args: any[]): void;
30
+ };
33
31
  type Container = {
34
32
  logger: Logger;
35
33
  alertHandler?: AlertHandler;
@@ -263,21 +261,12 @@ declare function slugify(text: string): string;
263
261
  declare function getTmpDir(): string;
264
262
  declare function getTmpPath(...steps: string[]): string;
265
263
  declare function parseDefaultPagination(query: PaginationQuery): Pagination;
266
- declare function createReqSnapshot(req: Request & {
267
- user?: {
268
- id?: string | number;
269
- };
270
- }): ReqSnapshot;
271
264
 
272
265
  declare function isDev(): boolean;
273
266
  declare function isProd(): boolean;
274
267
  declare function getEnvVar(configName: string): string;
275
268
  type Env = 'dev' | 'prod' | OtherString;
276
269
 
277
- declare const createLogger: (options?: winston.LoggerOptions) => winston.Logger;
278
- declare const getDefaultFileLogger: (name?: string) => winston.Logger;
279
- declare const getDefaultConsoleLogger: (name?: string) => winston.Logger;
280
-
281
270
  declare class ApiErrorResponse<T = undefined> {
282
271
  readonly status = "error";
283
272
  readonly message: string;
@@ -294,7 +283,7 @@ declare class ApiResponse<T = undefined> {
294
283
 
295
284
  declare function bootstrap(container: Container): {
296
285
  swaggerBuilder: () => SwaggerBuilder;
297
- silently: (fn: () => Promise<void>, reqSnapshot?: ReqSnapshot) => void;
286
+ silently: (fn: () => Promise<void> | void) => Promise<void>;
298
287
  notFoundMiddleware: (_req: express.Request, res: express.Response, _next: express.NextFunction) => void;
299
288
  getErrorHandlerMiddleware: (errorMapper?: (err: Error & Record<string, unknown>) => ApiError | null | undefined) => (err: Error & Record<string, unknown>, req: express.Request, res: express.Response, _next: express.NextFunction) => Promise<void>;
300
289
  expressiveServer: (configs?: {
@@ -321,4 +310,4 @@ declare function bootstrap(container: Container): {
321
310
  };
322
311
  };
323
312
 
324
- export { type AlertHandler, ApiError, ApiErrorResponse, ApiResponse, type AuthMethod, BadRequestError, type Container, type Content, type ContentType, DuplicateError, type Env, type ExpressHandler, type ExpressLocalsObj, type ExpressRoute, FileTooBigError, ForbiddenError, type HttpMethod, InternalError, InvalidCredentialsError, InvalidFileTypeError, type Logger, NotFoundError, type OtherString, type OtherUnknown, type Pagination, type PaginationQuery, type Param, type PathItem, type ReqSnapshot, SWG, type Schema, SchemaValidationError, type SecurityScheme, type Servers, type SwaggerConfig, TokenExpiredError, TooManyRequestsError, UserUnauthorizedError, bootstrap, createLogger, createReqSnapshot, getDefaultConsoleLogger, getDefaultFileLogger, getEnvVar, getTmpDir, getTmpPath, isDev, isProd, parseDefaultPagination, parseIdOrFail, parsePositiveInteger, slugify };
313
+ export { type AlertHandler, ApiError, ApiErrorResponse, ApiResponse, type AuthMethod, BadRequestError, type Container, type Content, type ContentType, DuplicateError, type Env, type ExpressHandler, type ExpressLocalsObj, type ExpressRoute, FileTooBigError, ForbiddenError, type HttpMethod, InternalError, InvalidCredentialsError, InvalidFileTypeError, type Logger, NotFoundError, type OtherString, type OtherUnknown, type Pagination, type PaginationQuery, type Param, type PathItem, SWG, type Schema, SchemaValidationError, type SecurityScheme, type Servers, type SwaggerConfig, TokenExpiredError, TooManyRequestsError, UserUnauthorizedError, bootstrap, getEnvVar, getTmpDir, getTmpPath, isDev, isProd, parseDefaultPagination, parseIdOrFail, parsePositiveInteger, slugify };
package/dist/index.js CHANGED
@@ -47,10 +47,6 @@ __export(index_exports, {
47
47
  TooManyRequestsError: () => TooManyRequestsError,
48
48
  UserUnauthorizedError: () => UserUnauthorizedError,
49
49
  bootstrap: () => bootstrap,
50
- createLogger: () => createLogger,
51
- createReqSnapshot: () => createReqSnapshot,
52
- getDefaultConsoleLogger: () => getDefaultConsoleLogger,
53
- getDefaultFileLogger: () => getDefaultFileLogger,
54
50
  getEnvVar: () => getEnvVar,
55
51
  getTmpDir: () => getTmpDir,
56
52
  getTmpPath: () => getTmpPath,
@@ -276,8 +272,22 @@ function buildExpressive(container, swaggerDoc) {
276
272
  };
277
273
  }
278
274
 
279
- // src/common.ts
280
- var import_path = __toESM(require("path"));
275
+ // src/env.ts
276
+ var import_dotenv = __toESM(require("dotenv"));
277
+ import_dotenv.default.config();
278
+ function isDev() {
279
+ return getEnvVar("ENV") === "dev";
280
+ }
281
+ function isProd() {
282
+ return getEnvVar("ENV") === "prod";
283
+ }
284
+ function getEnvVar(configName) {
285
+ const config = process.env[configName];
286
+ if (!config) {
287
+ throw new Error(`Missing config '${configName}'`);
288
+ }
289
+ return config;
290
+ }
281
291
 
282
292
  // src/errors.ts
283
293
  var ApiError = class extends Error {
@@ -356,59 +366,6 @@ var UserUnauthorizedError = class extends ApiError {
356
366
  }
357
367
  };
358
368
 
359
- // src/common.ts
360
- function parsePositiveInteger(v, defaultValue, max) {
361
- const value = Number(v);
362
- return Number.isInteger(value) && value > 0 && (!max || value <= max) ? value : defaultValue;
363
- }
364
- function parseIdOrFail(v) {
365
- const value = Number(v);
366
- if (!Number.isInteger(value) || value <= 0) {
367
- throw new ApiError("Invalid Id", 400, "INVALID_ID");
368
- }
369
- return value;
370
- }
371
- function slugify(text) {
372
- return text.toString().normalize("NFKD").toLowerCase().trim().replace(/[\s\n_+.]/g, "-").replace(/--+/g, "-");
373
- }
374
- function getTmpDir() {
375
- return import_path.default.resolve("tmp");
376
- }
377
- function getTmpPath(...steps) {
378
- return import_path.default.join(getTmpDir(), ...steps);
379
- }
380
- function parseDefaultPagination(query) {
381
- const limit = parsePositiveInteger(query.limit, 50, 100);
382
- const page = parsePositiveInteger(query.page, 1, 1e3);
383
- const offset = (page - 1) * limit;
384
- return { limit, offset };
385
- }
386
- function createReqSnapshot(req) {
387
- return {
388
- query: req.query,
389
- path: req.path,
390
- method: req.method,
391
- userId: req?.user?.id
392
- };
393
- }
394
-
395
- // src/env.ts
396
- var import_dotenv = __toESM(require("dotenv"));
397
- import_dotenv.default.config();
398
- function isDev() {
399
- return getEnvVar("ENV") === "dev";
400
- }
401
- function isProd() {
402
- return getEnvVar("ENV") === "prod";
403
- }
404
- function getEnvVar(configName) {
405
- const config = process.env[configName];
406
- if (!config) {
407
- throw new Error(`Missing config '${configName}'`);
408
- }
409
- return config;
410
- }
411
-
412
369
  // src/response/ApiErrorResponse.ts
413
370
  var ApiErrorResponse = class {
414
371
  status = "error";
@@ -448,7 +405,7 @@ var buildMiddleware = (container) => {
448
405
  logger.error("Cause: %s", err.cause);
449
406
  }
450
407
  if (alertHandler) {
451
- alertHandler(err, createReqSnapshot(req));
408
+ alertHandler(err);
452
409
  }
453
410
  finalError = new InternalError();
454
411
  if (!isProd()) {
@@ -461,63 +418,34 @@ var buildMiddleware = (container) => {
461
418
  };
462
419
  };
463
420
 
464
- // src/logger.ts
465
- var import_winston = __toESM(require("winston"));
466
- var import_winston_daily_rotate_file = __toESM(require("winston-daily-rotate-file"));
467
- var loggerRegistry = {};
468
- var defaultFormat = import_winston.default.format.combine(
469
- import_winston.default.format.timestamp({ format: "DD/MM/YYYY HH:mm:ss" }),
470
- import_winston.default.format.splat(),
471
- // String interpolation splat for %d %s-style messages.
472
- import_winston.default.format.errors({ stack: true }),
473
- import_winston.default.format.printf(({ level, message, timestamp, stack }) => `${timestamp}[${level.toUpperCase()}]: ${stack || message}`)
474
- );
475
- var consoleTransport = new import_winston.default.transports.Console({
476
- handleExceptions: true
477
- });
478
- var createFileLogger = (filename) => {
479
- const logger = import_winston.default.createLogger({
480
- format: defaultFormat,
481
- transports: [
482
- new import_winston_daily_rotate_file.default({
483
- level: "debug",
484
- filename: `./logs/${filename}-%DATE%.log`,
485
- handleExceptions: true,
486
- // exitOnError: false, // do not exit on handled exceptions
487
- datePattern: "YYYY-MM-DD",
488
- zippedArchive: true,
489
- auditFile: "./logs/audit.json",
490
- maxSize: "20m",
491
- maxFiles: isProd() ? "30d" : "1d"
492
- // format: defaultFormat,
493
- // the nested 'format' field causes issues with logging errors;
494
- // use the 'format' field on logger, instead of transport;
495
- })
496
- ]
497
- });
498
- if (isDev()) {
499
- logger.add(consoleTransport);
500
- }
501
- return logger;
502
- };
503
- var createLogger = import_winston.default.createLogger;
504
- var getDefaultFileLogger = (name = "app") => {
505
- if (!loggerRegistry[name]) {
506
- loggerRegistry[name] = createFileLogger(name.toString());
507
- }
508
- return loggerRegistry[name];
509
- };
510
- var getDefaultConsoleLogger = (name = "console") => {
511
- if (!loggerRegistry[name]) {
512
- loggerRegistry[name] = import_winston.default.createLogger({
513
- format: defaultFormat,
514
- transports: [
515
- consoleTransport
516
- ]
517
- });
421
+ // src/common.ts
422
+ var import_path = __toESM(require("path"));
423
+ function parsePositiveInteger(v, defaultValue, max) {
424
+ const value = Number(v);
425
+ return Number.isInteger(value) && value > 0 && (!max || value <= max) ? value : defaultValue;
426
+ }
427
+ function parseIdOrFail(v) {
428
+ const value = Number(v);
429
+ if (!Number.isInteger(value) || value <= 0) {
430
+ throw new ApiError("Invalid Id", 400, "INVALID_ID");
518
431
  }
519
- return loggerRegistry[name];
520
- };
432
+ return value;
433
+ }
434
+ function slugify(text) {
435
+ return text.toString().normalize("NFKD").toLowerCase().trim().replace(/[\s\n_+.]/g, "-").replace(/--+/g, "-");
436
+ }
437
+ function getTmpDir() {
438
+ return import_path.default.resolve("tmp");
439
+ }
440
+ function getTmpPath(...steps) {
441
+ return import_path.default.join(getTmpDir(), ...steps);
442
+ }
443
+ function parseDefaultPagination(query) {
444
+ const limit = parsePositiveInteger(query.limit, 50, 100);
445
+ const page = parsePositiveInteger(query.page, 1, 1e3);
446
+ const offset = (page - 1) * limit;
447
+ return { limit, offset };
448
+ }
521
449
 
522
450
  // src/response/ApiResponse.ts
523
451
  var ApiResponse = class {
@@ -541,14 +469,16 @@ function bootstrap(container) {
541
469
  ...buildExpressive(container, swaggerDoc),
542
470
  ...buildMiddleware(container),
543
471
  swaggerBuilder: () => new SwaggerBuilder(swaggerDoc),
544
- silently: (fn, reqSnapshot) => {
545
- fn().catch((e) => {
472
+ silently: async (fn) => {
473
+ try {
474
+ await fn();
475
+ } catch (e) {
546
476
  if (container.alertHandler && e instanceof Error) {
547
- container.alertHandler(e, reqSnapshot);
477
+ container.alertHandler(e);
548
478
  } else {
549
479
  container.logger.error(e);
550
480
  }
551
- });
481
+ }
552
482
  }
553
483
  };
554
484
  }
@@ -571,10 +501,6 @@ function bootstrap(container) {
571
501
  TooManyRequestsError,
572
502
  UserUnauthorizedError,
573
503
  bootstrap,
574
- createLogger,
575
- createReqSnapshot,
576
- getDefaultConsoleLogger,
577
- getDefaultFileLogger,
578
504
  getEnvVar,
579
505
  getTmpDir,
580
506
  getTmpPath,
package/dist/index.mjs CHANGED
@@ -211,8 +211,22 @@ function buildExpressive(container, swaggerDoc) {
211
211
  };
212
212
  }
213
213
 
214
- // src/common.ts
215
- import path from "path";
214
+ // src/env.ts
215
+ import dotenv from "dotenv";
216
+ dotenv.config();
217
+ function isDev() {
218
+ return getEnvVar("ENV") === "dev";
219
+ }
220
+ function isProd() {
221
+ return getEnvVar("ENV") === "prod";
222
+ }
223
+ function getEnvVar(configName) {
224
+ const config = process.env[configName];
225
+ if (!config) {
226
+ throw new Error(`Missing config '${configName}'`);
227
+ }
228
+ return config;
229
+ }
216
230
 
217
231
  // src/errors.ts
218
232
  var ApiError = class extends Error {
@@ -291,59 +305,6 @@ var UserUnauthorizedError = class extends ApiError {
291
305
  }
292
306
  };
293
307
 
294
- // src/common.ts
295
- function parsePositiveInteger(v, defaultValue, max) {
296
- const value = Number(v);
297
- return Number.isInteger(value) && value > 0 && (!max || value <= max) ? value : defaultValue;
298
- }
299
- function parseIdOrFail(v) {
300
- const value = Number(v);
301
- if (!Number.isInteger(value) || value <= 0) {
302
- throw new ApiError("Invalid Id", 400, "INVALID_ID");
303
- }
304
- return value;
305
- }
306
- function slugify(text) {
307
- return text.toString().normalize("NFKD").toLowerCase().trim().replace(/[\s\n_+.]/g, "-").replace(/--+/g, "-");
308
- }
309
- function getTmpDir() {
310
- return path.resolve("tmp");
311
- }
312
- function getTmpPath(...steps) {
313
- return path.join(getTmpDir(), ...steps);
314
- }
315
- function parseDefaultPagination(query) {
316
- const limit = parsePositiveInteger(query.limit, 50, 100);
317
- const page = parsePositiveInteger(query.page, 1, 1e3);
318
- const offset = (page - 1) * limit;
319
- return { limit, offset };
320
- }
321
- function createReqSnapshot(req) {
322
- return {
323
- query: req.query,
324
- path: req.path,
325
- method: req.method,
326
- userId: req?.user?.id
327
- };
328
- }
329
-
330
- // src/env.ts
331
- import dotenv from "dotenv";
332
- dotenv.config();
333
- function isDev() {
334
- return getEnvVar("ENV") === "dev";
335
- }
336
- function isProd() {
337
- return getEnvVar("ENV") === "prod";
338
- }
339
- function getEnvVar(configName) {
340
- const config = process.env[configName];
341
- if (!config) {
342
- throw new Error(`Missing config '${configName}'`);
343
- }
344
- return config;
345
- }
346
-
347
308
  // src/response/ApiErrorResponse.ts
348
309
  var ApiErrorResponse = class {
349
310
  status = "error";
@@ -383,7 +344,7 @@ var buildMiddleware = (container) => {
383
344
  logger.error("Cause: %s", err.cause);
384
345
  }
385
346
  if (alertHandler) {
386
- alertHandler(err, createReqSnapshot(req));
347
+ alertHandler(err);
387
348
  }
388
349
  finalError = new InternalError();
389
350
  if (!isProd()) {
@@ -396,63 +357,34 @@ var buildMiddleware = (container) => {
396
357
  };
397
358
  };
398
359
 
399
- // src/logger.ts
400
- import winston from "winston";
401
- import DailyRotateFile from "winston-daily-rotate-file";
402
- var loggerRegistry = {};
403
- var defaultFormat = winston.format.combine(
404
- winston.format.timestamp({ format: "DD/MM/YYYY HH:mm:ss" }),
405
- winston.format.splat(),
406
- // String interpolation splat for %d %s-style messages.
407
- winston.format.errors({ stack: true }),
408
- winston.format.printf(({ level, message, timestamp, stack }) => `${timestamp}[${level.toUpperCase()}]: ${stack || message}`)
409
- );
410
- var consoleTransport = new winston.transports.Console({
411
- handleExceptions: true
412
- });
413
- var createFileLogger = (filename) => {
414
- const logger = winston.createLogger({
415
- format: defaultFormat,
416
- transports: [
417
- new DailyRotateFile({
418
- level: "debug",
419
- filename: `./logs/${filename}-%DATE%.log`,
420
- handleExceptions: true,
421
- // exitOnError: false, // do not exit on handled exceptions
422
- datePattern: "YYYY-MM-DD",
423
- zippedArchive: true,
424
- auditFile: "./logs/audit.json",
425
- maxSize: "20m",
426
- maxFiles: isProd() ? "30d" : "1d"
427
- // format: defaultFormat,
428
- // the nested 'format' field causes issues with logging errors;
429
- // use the 'format' field on logger, instead of transport;
430
- })
431
- ]
432
- });
433
- if (isDev()) {
434
- logger.add(consoleTransport);
435
- }
436
- return logger;
437
- };
438
- var createLogger = winston.createLogger;
439
- var getDefaultFileLogger = (name = "app") => {
440
- if (!loggerRegistry[name]) {
441
- loggerRegistry[name] = createFileLogger(name.toString());
442
- }
443
- return loggerRegistry[name];
444
- };
445
- var getDefaultConsoleLogger = (name = "console") => {
446
- if (!loggerRegistry[name]) {
447
- loggerRegistry[name] = winston.createLogger({
448
- format: defaultFormat,
449
- transports: [
450
- consoleTransport
451
- ]
452
- });
360
+ // src/common.ts
361
+ import path from "path";
362
+ function parsePositiveInteger(v, defaultValue, max) {
363
+ const value = Number(v);
364
+ return Number.isInteger(value) && value > 0 && (!max || value <= max) ? value : defaultValue;
365
+ }
366
+ function parseIdOrFail(v) {
367
+ const value = Number(v);
368
+ if (!Number.isInteger(value) || value <= 0) {
369
+ throw new ApiError("Invalid Id", 400, "INVALID_ID");
453
370
  }
454
- return loggerRegistry[name];
455
- };
371
+ return value;
372
+ }
373
+ function slugify(text) {
374
+ return text.toString().normalize("NFKD").toLowerCase().trim().replace(/[\s\n_+.]/g, "-").replace(/--+/g, "-");
375
+ }
376
+ function getTmpDir() {
377
+ return path.resolve("tmp");
378
+ }
379
+ function getTmpPath(...steps) {
380
+ return path.join(getTmpDir(), ...steps);
381
+ }
382
+ function parseDefaultPagination(query) {
383
+ const limit = parsePositiveInteger(query.limit, 50, 100);
384
+ const page = parsePositiveInteger(query.page, 1, 1e3);
385
+ const offset = (page - 1) * limit;
386
+ return { limit, offset };
387
+ }
456
388
 
457
389
  // src/response/ApiResponse.ts
458
390
  var ApiResponse = class {
@@ -476,14 +408,16 @@ function bootstrap(container) {
476
408
  ...buildExpressive(container, swaggerDoc),
477
409
  ...buildMiddleware(container),
478
410
  swaggerBuilder: () => new SwaggerBuilder(swaggerDoc),
479
- silently: (fn, reqSnapshot) => {
480
- fn().catch((e) => {
411
+ silently: async (fn) => {
412
+ try {
413
+ await fn();
414
+ } catch (e) {
481
415
  if (container.alertHandler && e instanceof Error) {
482
- container.alertHandler(e, reqSnapshot);
416
+ container.alertHandler(e);
483
417
  } else {
484
418
  container.logger.error(e);
485
419
  }
486
- });
420
+ }
487
421
  }
488
422
  };
489
423
  }
@@ -505,10 +439,6 @@ export {
505
439
  TooManyRequestsError,
506
440
  UserUnauthorizedError,
507
441
  bootstrap,
508
- createLogger,
509
- createReqSnapshot,
510
- getDefaultConsoleLogger,
511
- getDefaultFileLogger,
512
442
  getEnvVar,
513
443
  getTmpDir,
514
444
  getTmpPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@extk/expressive",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "type": "commonjs",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -46,9 +46,7 @@
46
46
  "helmet": "^8.0.0",
47
47
  "morgan": "^1.10.0",
48
48
  "qs": "^6.14.1",
49
- "swagger-ui-express": "^5.0.1",
50
- "winston": "^3.11.0",
51
- "winston-daily-rotate-file": "^5.0.0"
49
+ "swagger-ui-express": "^5.0.1"
52
50
  },
53
51
  "author": "",
54
52
  "license": "ISC",