@extk/expressive 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/dist/index.d.mts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +565 -0
- package/dist/index.mjs +499 -0
- package/package.json +53 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// src/expressive.ts
|
|
2
|
+
import express from "express";
|
|
3
|
+
import helmet from "helmet";
|
|
4
|
+
import morgan from "morgan";
|
|
5
|
+
import qs from "qs";
|
|
6
|
+
import swaggerUi from "swagger-ui-express";
|
|
7
|
+
|
|
8
|
+
// src/swagger.ts
|
|
9
|
+
var buildSwaggerBuilder = (swaggerDoc) => {
|
|
10
|
+
return {
|
|
11
|
+
swaggerBuilder: () => {
|
|
12
|
+
return {
|
|
13
|
+
withInfo(info) {
|
|
14
|
+
swaggerDoc.info = info;
|
|
15
|
+
return this;
|
|
16
|
+
},
|
|
17
|
+
withServers(servers) {
|
|
18
|
+
swaggerDoc.servers = servers;
|
|
19
|
+
return this;
|
|
20
|
+
},
|
|
21
|
+
withSecuritySchemes(schemes) {
|
|
22
|
+
swaggerDoc.components.securitySchemes = schemes;
|
|
23
|
+
return this;
|
|
24
|
+
},
|
|
25
|
+
withSchemas(schemas) {
|
|
26
|
+
swaggerDoc.components.schemas = schemas;
|
|
27
|
+
return this;
|
|
28
|
+
},
|
|
29
|
+
withDefaultSecurity(globalAuthMethods) {
|
|
30
|
+
swaggerDoc.security = globalAuthMethods;
|
|
31
|
+
return this;
|
|
32
|
+
},
|
|
33
|
+
get() {
|
|
34
|
+
return swaggerDoc;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
var securitySchemes = {
|
|
41
|
+
BasicAuth: () => ({
|
|
42
|
+
type: "http",
|
|
43
|
+
scheme: "basic"
|
|
44
|
+
}),
|
|
45
|
+
BearerAuth: () => ({
|
|
46
|
+
type: "http",
|
|
47
|
+
scheme: "bearer"
|
|
48
|
+
}),
|
|
49
|
+
ApiKeyAuth: (headerName) => ({
|
|
50
|
+
type: "apiKey",
|
|
51
|
+
in: "header",
|
|
52
|
+
name: headerName
|
|
53
|
+
}),
|
|
54
|
+
OpenID: (openIdConnectUrl) => ({
|
|
55
|
+
type: "openIdConnect",
|
|
56
|
+
openIdConnectUrl
|
|
57
|
+
}),
|
|
58
|
+
OAuth2: (authorizationUrl, tokenUrl, scopes) => ({
|
|
59
|
+
type: "oauth2",
|
|
60
|
+
flows: {
|
|
61
|
+
authorizationCode: {
|
|
62
|
+
authorizationUrl,
|
|
63
|
+
tokenUrl,
|
|
64
|
+
scopes
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
var securityRegistry = {};
|
|
70
|
+
var security = (name) => {
|
|
71
|
+
if (!securityRegistry[name]) {
|
|
72
|
+
securityRegistry[name] = { [name]: [] };
|
|
73
|
+
}
|
|
74
|
+
return securityRegistry[name];
|
|
75
|
+
};
|
|
76
|
+
function jsonSchema(schema) {
|
|
77
|
+
return {
|
|
78
|
+
content: {
|
|
79
|
+
"application/json": {
|
|
80
|
+
schema
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function jsonSchemaRef(name) {
|
|
86
|
+
return jsonSchema({ $ref: `#/components/schemas/${name}` });
|
|
87
|
+
}
|
|
88
|
+
function param(inP, id, schema, required = true, description = "", name) {
|
|
89
|
+
return {
|
|
90
|
+
in: inP,
|
|
91
|
+
name: name ?? id,
|
|
92
|
+
desciption: description,
|
|
93
|
+
required,
|
|
94
|
+
schema
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function pathParam(id, schema, required = true, description = "", name) {
|
|
98
|
+
return param("path", id, schema, required, description, name);
|
|
99
|
+
}
|
|
100
|
+
function queryParam(id, schema, required = true, description = "", name) {
|
|
101
|
+
return param("query", id, schema, required, description, name);
|
|
102
|
+
}
|
|
103
|
+
function headerParam(id, schema, required = true, description = "", name) {
|
|
104
|
+
return param("headers", id, schema, required, description, name);
|
|
105
|
+
}
|
|
106
|
+
function convertExpressPath(path2) {
|
|
107
|
+
return path2.replace(/:([a-zA-Z0-9_*]+)/g, "{$1}");
|
|
108
|
+
}
|
|
109
|
+
function tryParsePathParameters(path2) {
|
|
110
|
+
const matches = path2.match(/(?<={)[^}]+(?=})/g);
|
|
111
|
+
if (!matches) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return matches.map((pp) => pathParam(pp, { type: "string" }));
|
|
115
|
+
}
|
|
116
|
+
var SWG = {
|
|
117
|
+
param,
|
|
118
|
+
pathParam,
|
|
119
|
+
queryParam,
|
|
120
|
+
headerParam,
|
|
121
|
+
jsonSchema,
|
|
122
|
+
jsonSchemaRef,
|
|
123
|
+
security,
|
|
124
|
+
securitySchemes
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// src/expressive.ts
|
|
128
|
+
function buildExpressive(container, swaggerDoc) {
|
|
129
|
+
return {
|
|
130
|
+
expressiveServer(configs) {
|
|
131
|
+
const { options } = configs;
|
|
132
|
+
const app = configs.app ?? express();
|
|
133
|
+
app.use(helmet(options?.helmet ?? {}));
|
|
134
|
+
app.set("query parser", function(str) {
|
|
135
|
+
return qs.parse(str, { decoder(s) {
|
|
136
|
+
return decodeURIComponent(s);
|
|
137
|
+
} });
|
|
138
|
+
});
|
|
139
|
+
app.use(morgan(
|
|
140
|
+
options?.morgan?.format ?? ":req[x-real-ip] :method :url :status :res[content-length] - :response-time ms",
|
|
141
|
+
options?.morgan?.options ?? { stream: { write(message) {
|
|
142
|
+
container.logger.info(message.trim());
|
|
143
|
+
} } }
|
|
144
|
+
));
|
|
145
|
+
app.use(configs.swagger.path, swaggerUi.serve, swaggerUi.setup(configs.swagger.doc));
|
|
146
|
+
return app;
|
|
147
|
+
},
|
|
148
|
+
expressiveRouter(configs) {
|
|
149
|
+
const router = express.Router();
|
|
150
|
+
return {
|
|
151
|
+
router,
|
|
152
|
+
addRoute(context, ...handlers) {
|
|
153
|
+
router[context.method](context.path, ...handlers);
|
|
154
|
+
const {
|
|
155
|
+
pathOverride,
|
|
156
|
+
pathParameters,
|
|
157
|
+
headerParameters,
|
|
158
|
+
queryParameters,
|
|
159
|
+
...pathItemConfig
|
|
160
|
+
} = context.oapi || {};
|
|
161
|
+
const route = pathOverride ?? convertExpressPath(context.path);
|
|
162
|
+
const pathItem = {
|
|
163
|
+
// -- defaults --
|
|
164
|
+
responses: {},
|
|
165
|
+
// has to be defined or else responses are not documented... ¯\_(ツ)_/¯
|
|
166
|
+
// -- group defaults --
|
|
167
|
+
...configs?.oapi || {},
|
|
168
|
+
// -- overrides --
|
|
169
|
+
...pathItemConfig || {},
|
|
170
|
+
parameters: [
|
|
171
|
+
// ...(contract.pathParameters || []),
|
|
172
|
+
...headerParameters || [],
|
|
173
|
+
...queryParameters || []
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
if (pathParameters?.length) {
|
|
177
|
+
pathItem.parameters.push(...pathParameters);
|
|
178
|
+
} else {
|
|
179
|
+
pathItem.parameters.push(...tryParsePathParameters(route));
|
|
180
|
+
}
|
|
181
|
+
swaggerDoc.paths[route] = {
|
|
182
|
+
[context.method]: pathItem
|
|
183
|
+
};
|
|
184
|
+
return router;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/env.ts
|
|
192
|
+
import dotenv from "dotenv";
|
|
193
|
+
dotenv.config();
|
|
194
|
+
function isDev() {
|
|
195
|
+
return getEnvVar("ENV") === "dev";
|
|
196
|
+
}
|
|
197
|
+
function isProd() {
|
|
198
|
+
return getEnvVar("ENV") === "prod";
|
|
199
|
+
}
|
|
200
|
+
function getEnvVar(configName) {
|
|
201
|
+
const config = process.env[configName];
|
|
202
|
+
if (!config) {
|
|
203
|
+
throw new Error(`Missing config '${configName}'`);
|
|
204
|
+
}
|
|
205
|
+
return config;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/errors.ts
|
|
209
|
+
var ApiError = class extends Error {
|
|
210
|
+
code;
|
|
211
|
+
httpStatusCode;
|
|
212
|
+
data;
|
|
213
|
+
constructor(message, httpStatusCode, errorCode) {
|
|
214
|
+
super(message);
|
|
215
|
+
this.name = this.constructor.name;
|
|
216
|
+
this.code = errorCode;
|
|
217
|
+
this.httpStatusCode = httpStatusCode;
|
|
218
|
+
}
|
|
219
|
+
setData(data) {
|
|
220
|
+
this.data = data;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var NotFoundError = class extends ApiError {
|
|
225
|
+
constructor(message = "Resource not found") {
|
|
226
|
+
super(message, 404, "NOT_FOUND");
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var DuplicateError = class extends ApiError {
|
|
230
|
+
constructor(message = "Duplicate entry") {
|
|
231
|
+
super(message, 409, "DUPLICATE_ENTRY");
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var BadRequestError = class extends ApiError {
|
|
235
|
+
constructor(message = "Bad request") {
|
|
236
|
+
super(message, 400, "BAD_REQUEST");
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var SchemaValidationError = class extends ApiError {
|
|
240
|
+
constructor(message = "Failed to validate Schema") {
|
|
241
|
+
super(message, 400, "SCHEMA_VALIDATION_ERROR");
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var FileTooBigError = class extends ApiError {
|
|
245
|
+
constructor() {
|
|
246
|
+
super("File too big", 400, "FILE_TOO_BIG");
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var InvalidFileTypeError = class extends ApiError {
|
|
250
|
+
constructor() {
|
|
251
|
+
super("Invalid file type", 400, "INVALID_FILE_TYPE");
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var InvalidCredentialsError = class extends ApiError {
|
|
255
|
+
constructor() {
|
|
256
|
+
super("Invalid credentials", 401, "INVALID_CREDENTIALS");
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
var InternalError = class extends ApiError {
|
|
260
|
+
constructor() {
|
|
261
|
+
super("Internal error", 500, "INTERNAL_ERROR");
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var TooManyRequestsError = class extends ApiError {
|
|
265
|
+
constructor(message = "Too many requests") {
|
|
266
|
+
super(message, 429, "TOO_MANY_REQUESTS");
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
var ForbiddenError = class extends ApiError {
|
|
270
|
+
constructor(message = "Action not allowed") {
|
|
271
|
+
super(message, 403, "FORBIDDEN");
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var TokenExpiredError = class extends ApiError {
|
|
275
|
+
constructor(message = "Token Expired") {
|
|
276
|
+
super(message, 401, "TOKEN_EXPIRED");
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var UserUnauthorizedError = class extends ApiError {
|
|
280
|
+
constructor(message = "User unauthorized") {
|
|
281
|
+
super(message, 401, "USER_UNAUTHORIZED");
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/response/ApiErrorResponse.ts
|
|
286
|
+
var ApiErrorResponse = class {
|
|
287
|
+
status = "error";
|
|
288
|
+
message;
|
|
289
|
+
errorCode;
|
|
290
|
+
errors;
|
|
291
|
+
constructor(message, errorCode, errors) {
|
|
292
|
+
this.message = message;
|
|
293
|
+
this.errorCode = errorCode;
|
|
294
|
+
this.errors = errors;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/common.ts
|
|
299
|
+
import path from "path";
|
|
300
|
+
function parsePositiveInteger(v, defaultValue, max) {
|
|
301
|
+
const value = Number(v);
|
|
302
|
+
return Number.isInteger(value) && value > 0 && (!max || value <= max) ? value : defaultValue;
|
|
303
|
+
}
|
|
304
|
+
function parseIdOrFail(v) {
|
|
305
|
+
const value = Number(v);
|
|
306
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
307
|
+
throw new ApiError("Invalid Id", 400, "INVALID_ID");
|
|
308
|
+
}
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
function slugify(text) {
|
|
312
|
+
return text.toString().normalize("NFKD").toLowerCase().trim().replace(/[\s\n_+.]/g, "-").replace(/--+/g, "-");
|
|
313
|
+
}
|
|
314
|
+
function getTmpDir() {
|
|
315
|
+
return path.resolve("tmp");
|
|
316
|
+
}
|
|
317
|
+
function getTmpPath(...steps) {
|
|
318
|
+
return path.join(getTmpDir(), ...steps);
|
|
319
|
+
}
|
|
320
|
+
function parseDefaultPagination(query) {
|
|
321
|
+
const limit = parsePositiveInteger(query.limit, 50, 100);
|
|
322
|
+
const page = parsePositiveInteger(query.page, 1, 1e3);
|
|
323
|
+
const offset = (page - 1) * limit;
|
|
324
|
+
return { limit, offset };
|
|
325
|
+
}
|
|
326
|
+
function createReqSnapshot(req) {
|
|
327
|
+
return {
|
|
328
|
+
query: req.query,
|
|
329
|
+
path: req.path,
|
|
330
|
+
method: req.method,
|
|
331
|
+
userId: req?.user?.id
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/middleware/errorHandlerMiddleware.ts
|
|
336
|
+
var buildErrorHandlerMiddleware = (container) => {
|
|
337
|
+
const { logger, alertHandler } = container;
|
|
338
|
+
return {
|
|
339
|
+
getErrorHandlerMiddleware: (errorMapper) => {
|
|
340
|
+
return async (err, req, res, _next) => {
|
|
341
|
+
let finalError;
|
|
342
|
+
const customMappedError = errorMapper && errorMapper(err);
|
|
343
|
+
if (customMappedError) {
|
|
344
|
+
finalError = customMappedError;
|
|
345
|
+
logger.error("%s\n%o", finalError.message, finalError.data);
|
|
346
|
+
} else if (err.name === "SyntaxError" && err.type === "entity.parse.failed") {
|
|
347
|
+
logger.error("%s", err);
|
|
348
|
+
finalError = new BadRequestError("Invalid json format");
|
|
349
|
+
} else if (err instanceof ApiError) {
|
|
350
|
+
logger.error("%s", err);
|
|
351
|
+
finalError = err;
|
|
352
|
+
} else {
|
|
353
|
+
logger.error("Error: %s", err);
|
|
354
|
+
if (err.cause) {
|
|
355
|
+
logger.error("Cause: %s", err.cause);
|
|
356
|
+
}
|
|
357
|
+
if (alertHandler) {
|
|
358
|
+
alertHandler(err, createReqSnapshot(req));
|
|
359
|
+
}
|
|
360
|
+
finalError = new InternalError();
|
|
361
|
+
if (!isProd()) {
|
|
362
|
+
finalError.data = { name: err.name, message: err.message, stack: err.stack, cause: err.cause };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
res.status(finalError.httpStatusCode).json(new ApiErrorResponse(finalError.message, finalError.code, finalError.data));
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/middleware/notFoundMiddleware.ts
|
|
372
|
+
var notFoundMiddleware = (_req, res, _next) => {
|
|
373
|
+
res.status(404).send("\xAF\\_(\u30C4)_/\xAF").end();
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// src/logger.ts
|
|
377
|
+
import winston from "winston";
|
|
378
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
379
|
+
var loggerRegistry = {};
|
|
380
|
+
var defaultFormat = winston.format.combine(
|
|
381
|
+
winston.format.timestamp({ format: "DD/MM/YYYY HH:mm:ss" }),
|
|
382
|
+
winston.format.splat(),
|
|
383
|
+
// String interpolation splat for %d %s-style messages.
|
|
384
|
+
winston.format.errors({ stack: true }),
|
|
385
|
+
winston.format.printf(({ level, message, timestamp, stack }) => `${timestamp}[${level.toUpperCase()}]: ${stack || message}`)
|
|
386
|
+
);
|
|
387
|
+
var consoleTransport = new winston.transports.Console({
|
|
388
|
+
handleExceptions: true
|
|
389
|
+
});
|
|
390
|
+
var createFileLogger = (filename) => {
|
|
391
|
+
const logger = winston.createLogger({
|
|
392
|
+
format: defaultFormat,
|
|
393
|
+
transports: [
|
|
394
|
+
new DailyRotateFile({
|
|
395
|
+
level: "debug",
|
|
396
|
+
filename: `./logs/${filename}-%DATE%.log`,
|
|
397
|
+
handleExceptions: true,
|
|
398
|
+
// exitOnError: false, // do not exit on handled exceptions
|
|
399
|
+
datePattern: "YYYY-MM-DD",
|
|
400
|
+
zippedArchive: true,
|
|
401
|
+
auditFile: "./logs/audit.json",
|
|
402
|
+
maxSize: "20m",
|
|
403
|
+
maxFiles: isProd() ? "30d" : "1d"
|
|
404
|
+
// format: defaultFormat,
|
|
405
|
+
// the nested 'format' field causes issues with logging errors;
|
|
406
|
+
// use the 'format' field on logger, instead of transport;
|
|
407
|
+
})
|
|
408
|
+
]
|
|
409
|
+
});
|
|
410
|
+
if (isDev()) {
|
|
411
|
+
logger.add(consoleTransport);
|
|
412
|
+
}
|
|
413
|
+
return logger;
|
|
414
|
+
};
|
|
415
|
+
var createLogger = winston.createLogger;
|
|
416
|
+
var getDefaultFileLogger = (name = "app") => {
|
|
417
|
+
if (!loggerRegistry[name]) {
|
|
418
|
+
loggerRegistry[name] = createFileLogger(name.toString());
|
|
419
|
+
}
|
|
420
|
+
return loggerRegistry[name];
|
|
421
|
+
};
|
|
422
|
+
var getDefaultConsoleLogger = (name = "console") => {
|
|
423
|
+
if (!loggerRegistry[name]) {
|
|
424
|
+
loggerRegistry[name] = winston.createLogger({
|
|
425
|
+
format: defaultFormat,
|
|
426
|
+
transports: [
|
|
427
|
+
consoleTransport
|
|
428
|
+
]
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return loggerRegistry[name];
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/response/ApiResponse.ts
|
|
435
|
+
var ApiResponse = class {
|
|
436
|
+
status = "ok";
|
|
437
|
+
result;
|
|
438
|
+
constructor(result) {
|
|
439
|
+
this.result = result;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/index.ts
|
|
444
|
+
function bootstrap(container) {
|
|
445
|
+
const swaggerDoc = {
|
|
446
|
+
openapi: "3.1.0",
|
|
447
|
+
// TODO
|
|
448
|
+
info: {},
|
|
449
|
+
paths: {},
|
|
450
|
+
components: {}
|
|
451
|
+
};
|
|
452
|
+
return {
|
|
453
|
+
...buildExpressive(container, swaggerDoc),
|
|
454
|
+
...buildSwaggerBuilder(swaggerDoc),
|
|
455
|
+
...buildErrorHandlerMiddleware(container),
|
|
456
|
+
notFoundMiddleware,
|
|
457
|
+
silently: (fn, reqSnapshot) => {
|
|
458
|
+
fn().catch((e) => {
|
|
459
|
+
if (container.alertHandler && e instanceof Error) {
|
|
460
|
+
container.alertHandler(e, reqSnapshot);
|
|
461
|
+
} else {
|
|
462
|
+
container.logger.error(e);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
export {
|
|
469
|
+
ApiError,
|
|
470
|
+
ApiErrorResponse,
|
|
471
|
+
ApiResponse,
|
|
472
|
+
BadRequestError,
|
|
473
|
+
DuplicateError,
|
|
474
|
+
FileTooBigError,
|
|
475
|
+
ForbiddenError,
|
|
476
|
+
InternalError,
|
|
477
|
+
InvalidCredentialsError,
|
|
478
|
+
InvalidFileTypeError,
|
|
479
|
+
NotFoundError,
|
|
480
|
+
SWG,
|
|
481
|
+
SchemaValidationError,
|
|
482
|
+
TokenExpiredError,
|
|
483
|
+
TooManyRequestsError,
|
|
484
|
+
UserUnauthorizedError,
|
|
485
|
+
bootstrap,
|
|
486
|
+
createLogger,
|
|
487
|
+
createReqSnapshot,
|
|
488
|
+
getDefaultConsoleLogger,
|
|
489
|
+
getDefaultFileLogger,
|
|
490
|
+
getEnvVar,
|
|
491
|
+
getTmpDir,
|
|
492
|
+
getTmpPath,
|
|
493
|
+
isDev,
|
|
494
|
+
isProd,
|
|
495
|
+
parseDefaultPagination,
|
|
496
|
+
parseIdOrFail,
|
|
497
|
+
parsePositiveInteger,
|
|
498
|
+
slugify
|
|
499
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@extk/expressive",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0 <23.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"test": "node --import=tsx --test 'test/**/*.test.ts'",
|
|
24
|
+
"lint": "eslint ./"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@extk/eslint-config": "^0.2.0",
|
|
28
|
+
"@types/chance": "^1.1.7",
|
|
29
|
+
"@types/express": "^5.0.1",
|
|
30
|
+
"@types/morgan": "^1.9.9",
|
|
31
|
+
"@types/node": "^20.9.3",
|
|
32
|
+
"@types/swagger-ui-express": "^4.1.8",
|
|
33
|
+
"chance": "^1.1.13",
|
|
34
|
+
"eslint": "^9.36.0",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"tsx": "^4.20.3",
|
|
37
|
+
"typescript": "^5.9.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"express": "^5.1.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"dotenv": "^17.1.0",
|
|
44
|
+
"helmet": "^8.0.0",
|
|
45
|
+
"morgan": "^1.10.0",
|
|
46
|
+
"swagger-ui-express": "^5.0.1",
|
|
47
|
+
"winston": "^3.11.0",
|
|
48
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"author": "",
|
|
51
|
+
"license": "ISC",
|
|
52
|
+
"description": ""
|
|
53
|
+
}
|