@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 +49 -8
- package/dist/index.d.mts +10 -21
- package/dist/index.d.ts +10 -21
- package/dist/index.js +50 -124
- package/dist/index.mjs +50 -120
- package/package.json +2 -4
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,
|
|
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:
|
|
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
|
-
|
|
262
|
+
Expressive does not bundle a logger. Instead, `bootstrap` accepts any object that satisfies the `Logger` interface:
|
|
252
263
|
|
|
253
264
|
```ts
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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>,
|
|
32
|
-
type Logger =
|
|
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
|
|
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,
|
|
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
|
|
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>,
|
|
32
|
-
type Logger =
|
|
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
|
|
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,
|
|
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/
|
|
280
|
-
var
|
|
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
|
|
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/
|
|
465
|
-
var
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
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
|
|
545
|
-
|
|
472
|
+
silently: async (fn) => {
|
|
473
|
+
try {
|
|
474
|
+
await fn();
|
|
475
|
+
} catch (e) {
|
|
546
476
|
if (container.alertHandler && e instanceof Error) {
|
|
547
|
-
container.alertHandler(e
|
|
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/
|
|
215
|
-
import
|
|
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
|
|
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/
|
|
400
|
-
import
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
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
|
|
480
|
-
|
|
411
|
+
silently: async (fn) => {
|
|
412
|
+
try {
|
|
413
|
+
await fn();
|
|
414
|
+
} catch (e) {
|
|
481
415
|
if (container.alertHandler && e instanceof Error) {
|
|
482
|
-
container.alertHandler(e
|
|
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.
|
|
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",
|