canxjs 1.0.0 → 1.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 +13 -13
- package/cli/bin.ts +30 -0
- package/cli/create.ts +14 -10
- package/dist/Application.d.ts +1 -1
- package/dist/Application.d.ts.map +1 -1
- package/dist/docs/ApiDoc.d.ts +3 -2
- package/dist/docs/ApiDoc.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +567 -203
- package/dist/middlewares/RateLimitMiddleware.d.ts +10 -0
- package/dist/middlewares/RateLimitMiddleware.d.ts.map +1 -0
- package/dist/middlewares/SecurityMiddleware.d.ts +14 -0
- package/dist/middlewares/SecurityMiddleware.d.ts.map +1 -0
- package/dist/middlewares/ValidationMiddleware.d.ts +4 -0
- package/dist/middlewares/ValidationMiddleware.d.ts.map +1 -0
- package/dist/schema/Schema.d.ts +64 -0
- package/dist/schema/Schema.d.ts.map +1 -0
- package/dist/utils/ErrorHandler.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/Application.ts +9 -2
- package/src/docs/ApiDoc.ts +12 -4
- package/src/index.ts +12 -0
- package/src/middlewares/RateLimitMiddleware.ts +62 -0
- package/src/middlewares/SecurityMiddleware.ts +55 -0
- package/src/middlewares/ValidationMiddleware.ts +35 -0
- package/src/schema/Schema.ts +299 -0
- package/src/utils/ErrorHandler.ts +45 -17
package/dist/index.js
CHANGED
|
@@ -22777,14 +22777,14 @@ var require_messages = __commonJS((exports) => {
|
|
|
22777
22777
|
length: 4
|
|
22778
22778
|
};
|
|
22779
22779
|
|
|
22780
|
-
class
|
|
22780
|
+
class DatabaseError2 extends Error {
|
|
22781
22781
|
constructor(message, length, name) {
|
|
22782
22782
|
super(message);
|
|
22783
22783
|
this.length = length;
|
|
22784
22784
|
this.name = name;
|
|
22785
22785
|
}
|
|
22786
22786
|
}
|
|
22787
|
-
exports.DatabaseError =
|
|
22787
|
+
exports.DatabaseError = DatabaseError2;
|
|
22788
22788
|
|
|
22789
22789
|
class CopyDataMessage {
|
|
22790
22790
|
constructor(length, chunk) {
|
|
@@ -25326,7 +25326,7 @@ var require_lib5 = __commonJS((exports, module) => {
|
|
|
25326
25326
|
var utils = require_utils();
|
|
25327
25327
|
var Pool = require_pg_pool();
|
|
25328
25328
|
var TypeOverrides = require_type_overrides();
|
|
25329
|
-
var { DatabaseError } = require_dist();
|
|
25329
|
+
var { DatabaseError: DatabaseError2 } = require_dist();
|
|
25330
25330
|
var { escapeIdentifier, escapeLiteral } = require_utils();
|
|
25331
25331
|
var poolFactory = (Client2) => {
|
|
25332
25332
|
return class BoundPool extends Pool {
|
|
@@ -25343,7 +25343,7 @@ var require_lib5 = __commonJS((exports, module) => {
|
|
|
25343
25343
|
this._pools = [];
|
|
25344
25344
|
this.Connection = Connection;
|
|
25345
25345
|
this.types = require_pg_types();
|
|
25346
|
-
this.DatabaseError =
|
|
25346
|
+
this.DatabaseError = DatabaseError2;
|
|
25347
25347
|
this.TypeOverrides = TypeOverrides;
|
|
25348
25348
|
this.escapeIdentifier = escapeIdentifier;
|
|
25349
25349
|
this.escapeLiteral = escapeLiteral;
|
|
@@ -25387,11 +25387,11 @@ __export(exports_esm, {
|
|
|
25387
25387
|
Result: () => Result,
|
|
25388
25388
|
Query: () => Query,
|
|
25389
25389
|
Pool: () => Pool,
|
|
25390
|
-
DatabaseError: () =>
|
|
25390
|
+
DatabaseError: () => DatabaseError2,
|
|
25391
25391
|
Connection: () => Connection,
|
|
25392
25392
|
Client: () => Client
|
|
25393
25393
|
});
|
|
25394
|
-
var import_lib, Client, Pool, Connection, types, Query,
|
|
25394
|
+
var import_lib, Client, Pool, Connection, types, Query, DatabaseError2, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default;
|
|
25395
25395
|
var init_esm = __esm(() => {
|
|
25396
25396
|
import_lib = __toESM(require_lib5(), 1);
|
|
25397
25397
|
Client = import_lib.default.Client;
|
|
@@ -25399,7 +25399,7 @@ var init_esm = __esm(() => {
|
|
|
25399
25399
|
Connection = import_lib.default.Connection;
|
|
25400
25400
|
types = import_lib.default.types;
|
|
25401
25401
|
Query = import_lib.default.Query;
|
|
25402
|
-
|
|
25402
|
+
DatabaseError2 = import_lib.default.DatabaseError;
|
|
25403
25403
|
escapeIdentifier = import_lib.default.escapeIdentifier;
|
|
25404
25404
|
escapeLiteral = import_lib.default.escapeLiteral;
|
|
25405
25405
|
Result = import_lib.default.Result;
|
|
@@ -26337,6 +26337,547 @@ var compress = () => {
|
|
|
26337
26337
|
function createMiddlewarePipeline() {
|
|
26338
26338
|
return new MiddlewarePipeline;
|
|
26339
26339
|
}
|
|
26340
|
+
// src/middlewares/SecurityMiddleware.ts
|
|
26341
|
+
function security(config = {}) {
|
|
26342
|
+
return async (req, res, next) => {
|
|
26343
|
+
if (config.xssProtection !== false) {
|
|
26344
|
+
res.header("X-XSS-Protection", "1; mode=block");
|
|
26345
|
+
}
|
|
26346
|
+
if (config.contentTypeOptions !== false) {
|
|
26347
|
+
res.header("X-Content-Type-Options", "nosniff");
|
|
26348
|
+
}
|
|
26349
|
+
if (config.frameOptions) {
|
|
26350
|
+
res.header("X-Frame-Options", config.frameOptions);
|
|
26351
|
+
} else if (config.frameOptions !== undefined) {
|
|
26352
|
+
res.header("X-Frame-Options", config.frameOptions);
|
|
26353
|
+
} else {
|
|
26354
|
+
res.header("X-Frame-Options", "SAMEORIGIN");
|
|
26355
|
+
}
|
|
26356
|
+
if (config.hsts) {
|
|
26357
|
+
const maxAge = typeof config.hsts === "object" ? config.hsts.maxAge : 31536000;
|
|
26358
|
+
const includeSubDomains = typeof config.hsts === "object" && config.hsts.includeSubDomains ? "; includeSubDomains" : "";
|
|
26359
|
+
res.header("Strict-Transport-Security", `max-age=${maxAge}${includeSubDomains}`);
|
|
26360
|
+
}
|
|
26361
|
+
if (config.contentSecurityPolicy) {
|
|
26362
|
+
res.header("Content-Security-Policy", config.contentSecurityPolicy);
|
|
26363
|
+
}
|
|
26364
|
+
if (config.referrerPolicy) {
|
|
26365
|
+
res.header("Referrer-Policy", config.referrerPolicy);
|
|
26366
|
+
}
|
|
26367
|
+
return next();
|
|
26368
|
+
};
|
|
26369
|
+
}
|
|
26370
|
+
// src/middlewares/RateLimitMiddleware.ts
|
|
26371
|
+
class MemoryStore {
|
|
26372
|
+
hits = new Map;
|
|
26373
|
+
increment(key, windowMs) {
|
|
26374
|
+
const now = Date.now();
|
|
26375
|
+
let record = this.hits.get(key);
|
|
26376
|
+
if (!record || record.resetTime <= now) {
|
|
26377
|
+
record = { count: 1, resetTime: now + windowMs };
|
|
26378
|
+
} else {
|
|
26379
|
+
record.count++;
|
|
26380
|
+
}
|
|
26381
|
+
this.hits.set(key, record);
|
|
26382
|
+
return record;
|
|
26383
|
+
}
|
|
26384
|
+
}
|
|
26385
|
+
function rateLimit2(config) {
|
|
26386
|
+
const store = new MemoryStore;
|
|
26387
|
+
const {
|
|
26388
|
+
windowMs = 60000,
|
|
26389
|
+
max = 100,
|
|
26390
|
+
message = "Too many requests, please try again later.",
|
|
26391
|
+
statusCode = 429,
|
|
26392
|
+
keyGenerator = (req) => req.header("x-forwarded-for") || req.header("cf-connecting-ip") || "unknown-ip"
|
|
26393
|
+
} = config;
|
|
26394
|
+
return async (req, res, next) => {
|
|
26395
|
+
const key = keyGenerator(req);
|
|
26396
|
+
const { count, resetTime } = store.increment(key, windowMs);
|
|
26397
|
+
const remaining = Math.max(0, max - count);
|
|
26398
|
+
const resetDate = new Date(resetTime);
|
|
26399
|
+
res.header("X-RateLimit-Limit", String(max));
|
|
26400
|
+
res.header("X-RateLimit-Remaining", String(remaining));
|
|
26401
|
+
res.header("X-RateLimit-Reset", String(Math.ceil(resetTime / 1000)));
|
|
26402
|
+
if (count > max) {
|
|
26403
|
+
if (typeof message === "object") {
|
|
26404
|
+
return res.status(statusCode).json(message);
|
|
26405
|
+
}
|
|
26406
|
+
return res.status(statusCode).text(String(message));
|
|
26407
|
+
}
|
|
26408
|
+
return next();
|
|
26409
|
+
};
|
|
26410
|
+
}
|
|
26411
|
+
// src/utils/ErrorHandler.ts
|
|
26412
|
+
class CanxError extends Error {
|
|
26413
|
+
code;
|
|
26414
|
+
statusCode;
|
|
26415
|
+
details;
|
|
26416
|
+
timestamp;
|
|
26417
|
+
constructor(message, code = "CANX_ERROR", statusCode = 500, details) {
|
|
26418
|
+
super(message);
|
|
26419
|
+
this.name = "CanxError";
|
|
26420
|
+
this.code = code;
|
|
26421
|
+
this.statusCode = statusCode;
|
|
26422
|
+
this.details = details;
|
|
26423
|
+
this.timestamp = new Date;
|
|
26424
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
26425
|
+
}
|
|
26426
|
+
toJSON() {
|
|
26427
|
+
return {
|
|
26428
|
+
name: this.name,
|
|
26429
|
+
code: this.code,
|
|
26430
|
+
message: this.message,
|
|
26431
|
+
statusCode: this.statusCode,
|
|
26432
|
+
details: this.details,
|
|
26433
|
+
timestamp: this.timestamp.toISOString()
|
|
26434
|
+
};
|
|
26435
|
+
}
|
|
26436
|
+
}
|
|
26437
|
+
|
|
26438
|
+
class ValidationError extends CanxError {
|
|
26439
|
+
errors;
|
|
26440
|
+
constructor(errors, message = "Validation failed") {
|
|
26441
|
+
const errorMap = errors instanceof Map ? errors : new Map(Object.entries(errors));
|
|
26442
|
+
super(message, "VALIDATION_ERROR", 422, { errors: Object.fromEntries(errorMap) });
|
|
26443
|
+
this.name = "ValidationError";
|
|
26444
|
+
this.errors = errorMap;
|
|
26445
|
+
}
|
|
26446
|
+
}
|
|
26447
|
+
|
|
26448
|
+
class NotFoundError extends CanxError {
|
|
26449
|
+
constructor(resource = "Resource", id) {
|
|
26450
|
+
const message = id ? `${resource} with ID ${id} not found` : `${resource} not found`;
|
|
26451
|
+
super(message, "NOT_FOUND", 404, { resource, id });
|
|
26452
|
+
this.name = "NotFoundError";
|
|
26453
|
+
}
|
|
26454
|
+
}
|
|
26455
|
+
|
|
26456
|
+
class AuthenticationError extends CanxError {
|
|
26457
|
+
constructor(message = "Authentication required") {
|
|
26458
|
+
super(message, "AUTHENTICATION_ERROR", 401);
|
|
26459
|
+
this.name = "AuthenticationError";
|
|
26460
|
+
}
|
|
26461
|
+
}
|
|
26462
|
+
|
|
26463
|
+
class AuthorizationError extends CanxError {
|
|
26464
|
+
constructor(message = "You do not have permission to access this resource") {
|
|
26465
|
+
super(message, "AUTHORIZATION_ERROR", 403);
|
|
26466
|
+
this.name = "AuthorizationError";
|
|
26467
|
+
}
|
|
26468
|
+
}
|
|
26469
|
+
|
|
26470
|
+
class ConflictError extends CanxError {
|
|
26471
|
+
constructor(message = "Resource conflict", resource) {
|
|
26472
|
+
super(message, "CONFLICT_ERROR", 409, { resource });
|
|
26473
|
+
this.name = "ConflictError";
|
|
26474
|
+
}
|
|
26475
|
+
}
|
|
26476
|
+
|
|
26477
|
+
class RateLimitError extends CanxError {
|
|
26478
|
+
retryAfter;
|
|
26479
|
+
constructor(retryAfter = 60, message = "Too many requests") {
|
|
26480
|
+
super(message, "RATE_LIMIT_ERROR", 429, { retryAfter });
|
|
26481
|
+
this.name = "RateLimitError";
|
|
26482
|
+
this.retryAfter = retryAfter;
|
|
26483
|
+
}
|
|
26484
|
+
}
|
|
26485
|
+
|
|
26486
|
+
class BadRequestError extends CanxError {
|
|
26487
|
+
constructor(message = "Bad request") {
|
|
26488
|
+
super(message, "BAD_REQUEST", 400);
|
|
26489
|
+
this.name = "BadRequestError";
|
|
26490
|
+
}
|
|
26491
|
+
}
|
|
26492
|
+
|
|
26493
|
+
class DatabaseError extends CanxError {
|
|
26494
|
+
constructor(message = "Database error", originalError) {
|
|
26495
|
+
super(message, "DATABASE_ERROR", 500, {
|
|
26496
|
+
originalMessage: originalError?.message,
|
|
26497
|
+
stack: originalError?.stack
|
|
26498
|
+
});
|
|
26499
|
+
this.name = "DatabaseError";
|
|
26500
|
+
}
|
|
26501
|
+
}
|
|
26502
|
+
|
|
26503
|
+
class ServiceUnavailableError extends CanxError {
|
|
26504
|
+
constructor(service, message) {
|
|
26505
|
+
super(message || `Service ${service} is currently unavailable`, "SERVICE_UNAVAILABLE", 503, { service });
|
|
26506
|
+
this.name = "ServiceUnavailableError";
|
|
26507
|
+
}
|
|
26508
|
+
}
|
|
26509
|
+
function errorHandler(options = {}) {
|
|
26510
|
+
const { showStack = true, logErrors = true } = options;
|
|
26511
|
+
return async (req, res, next) => {
|
|
26512
|
+
try {
|
|
26513
|
+
return await next();
|
|
26514
|
+
} catch (error) {
|
|
26515
|
+
if (logErrors) {
|
|
26516
|
+
console.error(`[CanxJS Error] ${req.method} ${req.path}:`, error);
|
|
26517
|
+
}
|
|
26518
|
+
if (options.onError) {
|
|
26519
|
+
options.onError(error, req);
|
|
26520
|
+
}
|
|
26521
|
+
const isJson = req.header("accept")?.includes("application/json") ?? true;
|
|
26522
|
+
let statusCode = 500;
|
|
26523
|
+
let errorResponse = {
|
|
26524
|
+
code: "INTERNAL_ERROR",
|
|
26525
|
+
message: "Internal server error"
|
|
26526
|
+
};
|
|
26527
|
+
if (error instanceof CanxError) {
|
|
26528
|
+
statusCode = error.statusCode;
|
|
26529
|
+
errorResponse = {
|
|
26530
|
+
code: error.code,
|
|
26531
|
+
message: error.message,
|
|
26532
|
+
...error.details && { details: error.details }
|
|
26533
|
+
};
|
|
26534
|
+
} else {
|
|
26535
|
+
const unknownError = error;
|
|
26536
|
+
errorResponse.message = showStack ? unknownError.message : "Internal server error";
|
|
26537
|
+
}
|
|
26538
|
+
if (showStack && error instanceof Error) {
|
|
26539
|
+
errorResponse.stack = error.stack;
|
|
26540
|
+
}
|
|
26541
|
+
if (isJson) {
|
|
26542
|
+
return res.status(statusCode).json({ error: errorResponse });
|
|
26543
|
+
} else {
|
|
26544
|
+
const html = `
|
|
26545
|
+
<!DOCTYPE html>
|
|
26546
|
+
<html>
|
|
26547
|
+
<head>
|
|
26548
|
+
<title>Error ${statusCode}</title>
|
|
26549
|
+
<style>
|
|
26550
|
+
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
|
26551
|
+
.error-box { background: #fee2e2; color: #991b1b; padding: 1.5rem; border-radius: 0.5rem; }
|
|
26552
|
+
pre { background: #f1f5f9; padding: 1rem; overflow-x: auto; border-radius: 0.5rem; }
|
|
26553
|
+
</style>
|
|
26554
|
+
</head>
|
|
26555
|
+
<body>
|
|
26556
|
+
<h1>Error ${statusCode}</h1>
|
|
26557
|
+
<div class="error-box">
|
|
26558
|
+
<p><strong>${errorResponse.code}</strong>: ${errorResponse.message}</p>
|
|
26559
|
+
</div>
|
|
26560
|
+
${showStack && errorResponse.stack ? `<pre>${errorResponse.stack}</pre>` : ""}
|
|
26561
|
+
</body>
|
|
26562
|
+
</html>
|
|
26563
|
+
`;
|
|
26564
|
+
return res.status(statusCode).html(html);
|
|
26565
|
+
}
|
|
26566
|
+
}
|
|
26567
|
+
};
|
|
26568
|
+
}
|
|
26569
|
+
function asyncHandler(fn) {
|
|
26570
|
+
return async (req, res) => {
|
|
26571
|
+
try {
|
|
26572
|
+
return await fn(req, res);
|
|
26573
|
+
} catch (error) {
|
|
26574
|
+
if (error instanceof CanxError) {
|
|
26575
|
+
return res.status(error.statusCode).json(error.toJSON());
|
|
26576
|
+
}
|
|
26577
|
+
throw error;
|
|
26578
|
+
}
|
|
26579
|
+
};
|
|
26580
|
+
}
|
|
26581
|
+
function assertFound(value, resource = "Resource", id) {
|
|
26582
|
+
if (value === null || value === undefined) {
|
|
26583
|
+
throw new NotFoundError(resource, id);
|
|
26584
|
+
}
|
|
26585
|
+
return value;
|
|
26586
|
+
}
|
|
26587
|
+
function assertAuthenticated(user) {
|
|
26588
|
+
if (!user) {
|
|
26589
|
+
throw new AuthenticationError;
|
|
26590
|
+
}
|
|
26591
|
+
return user;
|
|
26592
|
+
}
|
|
26593
|
+
function assertAuthorized(condition, message) {
|
|
26594
|
+
if (!condition) {
|
|
26595
|
+
throw new AuthorizationError(message);
|
|
26596
|
+
}
|
|
26597
|
+
}
|
|
26598
|
+
function assertValid(valid, errors) {
|
|
26599
|
+
if (!valid) {
|
|
26600
|
+
throw new ValidationError(errors || new Map);
|
|
26601
|
+
}
|
|
26602
|
+
}
|
|
26603
|
+
var errors = {
|
|
26604
|
+
CanxError,
|
|
26605
|
+
ValidationError,
|
|
26606
|
+
NotFoundError,
|
|
26607
|
+
AuthenticationError,
|
|
26608
|
+
AuthorizationError,
|
|
26609
|
+
ConflictError,
|
|
26610
|
+
RateLimitError,
|
|
26611
|
+
BadRequestError,
|
|
26612
|
+
DatabaseError,
|
|
26613
|
+
ServiceUnavailableError
|
|
26614
|
+
};
|
|
26615
|
+
var ErrorHandler_default = {
|
|
26616
|
+
...errors,
|
|
26617
|
+
errorHandler,
|
|
26618
|
+
asyncHandler,
|
|
26619
|
+
assertFound,
|
|
26620
|
+
assertAuthenticated,
|
|
26621
|
+
assertAuthorized,
|
|
26622
|
+
assertValid
|
|
26623
|
+
};
|
|
26624
|
+
|
|
26625
|
+
// src/middlewares/ValidationMiddleware.ts
|
|
26626
|
+
function validateSchema(schema, target = "body") {
|
|
26627
|
+
return async (req, res, next) => {
|
|
26628
|
+
let data;
|
|
26629
|
+
if (target === "body") {
|
|
26630
|
+
data = await req.body();
|
|
26631
|
+
} else if (target === "query") {
|
|
26632
|
+
data = req.query;
|
|
26633
|
+
} else {
|
|
26634
|
+
data = req.params;
|
|
26635
|
+
}
|
|
26636
|
+
try {
|
|
26637
|
+
const validated = schema.parse(data);
|
|
26638
|
+
req.validatedData = validated;
|
|
26639
|
+
return next();
|
|
26640
|
+
} catch (error) {
|
|
26641
|
+
if (error instanceof ValidationError) {
|
|
26642
|
+
return res.status(422).json({
|
|
26643
|
+
error: {
|
|
26644
|
+
code: "VALIDATION_ERROR",
|
|
26645
|
+
message: "Validation failed",
|
|
26646
|
+
details: Object.fromEntries(error.errors)
|
|
26647
|
+
}
|
|
26648
|
+
});
|
|
26649
|
+
}
|
|
26650
|
+
throw error;
|
|
26651
|
+
}
|
|
26652
|
+
};
|
|
26653
|
+
}
|
|
26654
|
+
// src/schema/Schema.ts
|
|
26655
|
+
class Schema {
|
|
26656
|
+
_output;
|
|
26657
|
+
_input;
|
|
26658
|
+
description;
|
|
26659
|
+
isOptional = false;
|
|
26660
|
+
safeParse(value) {
|
|
26661
|
+
try {
|
|
26662
|
+
const data = this.parse(value);
|
|
26663
|
+
return { success: true, data };
|
|
26664
|
+
} catch (error) {
|
|
26665
|
+
if (error instanceof ValidationError) {
|
|
26666
|
+
return { success: false, error };
|
|
26667
|
+
}
|
|
26668
|
+
throw error;
|
|
26669
|
+
}
|
|
26670
|
+
}
|
|
26671
|
+
optional() {
|
|
26672
|
+
const newSchema = Object.create(this);
|
|
26673
|
+
newSchema.isOptional = true;
|
|
26674
|
+
return newSchema;
|
|
26675
|
+
}
|
|
26676
|
+
describe(description) {
|
|
26677
|
+
this.description = description;
|
|
26678
|
+
return this;
|
|
26679
|
+
}
|
|
26680
|
+
}
|
|
26681
|
+
|
|
26682
|
+
class StringSchema extends Schema {
|
|
26683
|
+
checks = [];
|
|
26684
|
+
constructor() {
|
|
26685
|
+
super();
|
|
26686
|
+
}
|
|
26687
|
+
min(length, message) {
|
|
26688
|
+
this.checks.push((v) => v.length >= length ? null : message || `String must contain at least ${length} character(s)`);
|
|
26689
|
+
return this;
|
|
26690
|
+
}
|
|
26691
|
+
max(length, message) {
|
|
26692
|
+
this.checks.push((v) => v.length <= length ? null : message || `String must contain at most ${length} character(s)`);
|
|
26693
|
+
return this;
|
|
26694
|
+
}
|
|
26695
|
+
email(message) {
|
|
26696
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
26697
|
+
this.checks.push((v) => emailRegex.test(v) ? null : message || "Invalid email address");
|
|
26698
|
+
return this;
|
|
26699
|
+
}
|
|
26700
|
+
parse(value) {
|
|
26701
|
+
if (this.isOptional && (value === undefined || value === null)) {
|
|
26702
|
+
return value;
|
|
26703
|
+
}
|
|
26704
|
+
if (typeof value !== "string") {
|
|
26705
|
+
throw new ValidationError({ _errors: ["Expected string, received " + typeof value] });
|
|
26706
|
+
}
|
|
26707
|
+
for (const check of this.checks) {
|
|
26708
|
+
const error = check(value);
|
|
26709
|
+
if (error) {
|
|
26710
|
+
throw new ValidationError({ _errors: [error] });
|
|
26711
|
+
}
|
|
26712
|
+
}
|
|
26713
|
+
return value;
|
|
26714
|
+
}
|
|
26715
|
+
getJsonSchema() {
|
|
26716
|
+
return {
|
|
26717
|
+
type: "string",
|
|
26718
|
+
description: this.description
|
|
26719
|
+
};
|
|
26720
|
+
}
|
|
26721
|
+
}
|
|
26722
|
+
|
|
26723
|
+
class NumberSchema extends Schema {
|
|
26724
|
+
checks = [];
|
|
26725
|
+
min(min, message) {
|
|
26726
|
+
this.checks.push((v) => v >= min ? null : message || `Number must be greater than or equal to ${min}`);
|
|
26727
|
+
return this;
|
|
26728
|
+
}
|
|
26729
|
+
max(max, message) {
|
|
26730
|
+
this.checks.push((v) => v <= max ? null : message || `Number must be less than or equal to ${max}`);
|
|
26731
|
+
return this;
|
|
26732
|
+
}
|
|
26733
|
+
parse(value) {
|
|
26734
|
+
if (this.isOptional && (value === undefined || value === null)) {
|
|
26735
|
+
return value;
|
|
26736
|
+
}
|
|
26737
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
26738
|
+
if (typeof value === "string" && !isNaN(parseFloat(value))) {
|
|
26739
|
+
const num = parseFloat(value);
|
|
26740
|
+
return this.validateChecks(num);
|
|
26741
|
+
}
|
|
26742
|
+
throw new ValidationError({ _errors: ["Expected number, received " + typeof value] });
|
|
26743
|
+
}
|
|
26744
|
+
return this.validateChecks(value);
|
|
26745
|
+
}
|
|
26746
|
+
validateChecks(value) {
|
|
26747
|
+
for (const check of this.checks) {
|
|
26748
|
+
const error = check(value);
|
|
26749
|
+
if (error) {
|
|
26750
|
+
throw new ValidationError({ _errors: [error] });
|
|
26751
|
+
}
|
|
26752
|
+
}
|
|
26753
|
+
return value;
|
|
26754
|
+
}
|
|
26755
|
+
getJsonSchema() {
|
|
26756
|
+
return {
|
|
26757
|
+
type: "number",
|
|
26758
|
+
description: this.description
|
|
26759
|
+
};
|
|
26760
|
+
}
|
|
26761
|
+
}
|
|
26762
|
+
|
|
26763
|
+
class BooleanSchema extends Schema {
|
|
26764
|
+
parse(value) {
|
|
26765
|
+
if (this.isOptional && (value === undefined || value === null)) {
|
|
26766
|
+
return value;
|
|
26767
|
+
}
|
|
26768
|
+
if (typeof value === "boolean")
|
|
26769
|
+
return value;
|
|
26770
|
+
if (value === "true")
|
|
26771
|
+
return true;
|
|
26772
|
+
if (value === "false")
|
|
26773
|
+
return false;
|
|
26774
|
+
throw new ValidationError({ _errors: ["Expected boolean, received " + typeof value] });
|
|
26775
|
+
}
|
|
26776
|
+
getJsonSchema() {
|
|
26777
|
+
return {
|
|
26778
|
+
type: "boolean",
|
|
26779
|
+
description: this.description
|
|
26780
|
+
};
|
|
26781
|
+
}
|
|
26782
|
+
}
|
|
26783
|
+
|
|
26784
|
+
class ObjectSchema extends Schema {
|
|
26785
|
+
shape;
|
|
26786
|
+
constructor(shape) {
|
|
26787
|
+
super();
|
|
26788
|
+
this.shape = shape;
|
|
26789
|
+
}
|
|
26790
|
+
parse(value) {
|
|
26791
|
+
if (this.isOptional && (value === undefined || value === null)) {
|
|
26792
|
+
return value;
|
|
26793
|
+
}
|
|
26794
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
26795
|
+
throw new ValidationError({ _errors: ["Expected object, received " + typeof value] });
|
|
26796
|
+
}
|
|
26797
|
+
const result = {};
|
|
26798
|
+
const errors2 = new Map;
|
|
26799
|
+
for (const [key, schema] of Object.entries(this.shape)) {
|
|
26800
|
+
try {
|
|
26801
|
+
result[key] = schema.parse(value[key]);
|
|
26802
|
+
} catch (error) {
|
|
26803
|
+
if (error instanceof ValidationError) {
|
|
26804
|
+
const fieldErrors = error.errors.get("_errors") || [];
|
|
26805
|
+
if (error.errors.size > 0 && !error.errors.has("_errors")) {
|
|
26806
|
+
error.errors.forEach((msgs, path) => {
|
|
26807
|
+
errors2.set(`${key}.${path}`, msgs);
|
|
26808
|
+
});
|
|
26809
|
+
} else {
|
|
26810
|
+
errors2.set(key, fieldErrors);
|
|
26811
|
+
}
|
|
26812
|
+
} else {
|
|
26813
|
+
errors2.set(key, ["Invalid value"]);
|
|
26814
|
+
}
|
|
26815
|
+
}
|
|
26816
|
+
}
|
|
26817
|
+
if (errors2.size > 0) {
|
|
26818
|
+
throw new ValidationError(errors2);
|
|
26819
|
+
}
|
|
26820
|
+
return result;
|
|
26821
|
+
}
|
|
26822
|
+
getJsonSchema() {
|
|
26823
|
+
const properties = {};
|
|
26824
|
+
const required = [];
|
|
26825
|
+
for (const [key, schema] of Object.entries(this.shape)) {
|
|
26826
|
+
properties[key] = schema.getJsonSchema();
|
|
26827
|
+
if (!schema.isOptional) {
|
|
26828
|
+
if (!schema.isOptional) {
|
|
26829
|
+
required.push(key);
|
|
26830
|
+
}
|
|
26831
|
+
}
|
|
26832
|
+
}
|
|
26833
|
+
return {
|
|
26834
|
+
type: "object",
|
|
26835
|
+
properties,
|
|
26836
|
+
required: required.length > 0 ? required : undefined,
|
|
26837
|
+
description: this.description
|
|
26838
|
+
};
|
|
26839
|
+
}
|
|
26840
|
+
}
|
|
26841
|
+
|
|
26842
|
+
class ArraySchema extends Schema {
|
|
26843
|
+
element;
|
|
26844
|
+
constructor(element) {
|
|
26845
|
+
super();
|
|
26846
|
+
this.element = element;
|
|
26847
|
+
}
|
|
26848
|
+
parse(value) {
|
|
26849
|
+
if (this.isOptional && (value === undefined || value === null)) {
|
|
26850
|
+
return value;
|
|
26851
|
+
}
|
|
26852
|
+
if (!Array.isArray(value)) {
|
|
26853
|
+
throw new ValidationError({ _errors: ["Expected array, received " + typeof value] });
|
|
26854
|
+
}
|
|
26855
|
+
return value.map((item, index) => {
|
|
26856
|
+
try {
|
|
26857
|
+
return this.element.parse(item);
|
|
26858
|
+
} catch (error) {
|
|
26859
|
+
if (error instanceof ValidationError) {
|
|
26860
|
+
throw new ValidationError({ [index]: error.errors.get("_errors") || ["Invalid Item"] });
|
|
26861
|
+
}
|
|
26862
|
+
throw error;
|
|
26863
|
+
}
|
|
26864
|
+
});
|
|
26865
|
+
}
|
|
26866
|
+
getJsonSchema() {
|
|
26867
|
+
return {
|
|
26868
|
+
type: "array",
|
|
26869
|
+
items: this.element.getJsonSchema(),
|
|
26870
|
+
description: this.description
|
|
26871
|
+
};
|
|
26872
|
+
}
|
|
26873
|
+
}
|
|
26874
|
+
var z = {
|
|
26875
|
+
string: () => new StringSchema,
|
|
26876
|
+
number: () => new NumberSchema,
|
|
26877
|
+
boolean: () => new BooleanSchema,
|
|
26878
|
+
object: (shape) => new ObjectSchema(shape),
|
|
26879
|
+
array: (element) => new ArraySchema(element)
|
|
26880
|
+
};
|
|
26340
26881
|
// src/mvc/Controller.ts
|
|
26341
26882
|
var controllerMeta = new WeakMap;
|
|
26342
26883
|
function getControllerMeta(target) {
|
|
@@ -27600,7 +28141,7 @@ class TableBuilder {
|
|
|
27600
28141
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
27601
28142
|
}
|
|
27602
28143
|
}
|
|
27603
|
-
var
|
|
28144
|
+
var Schema2 = {
|
|
27604
28145
|
async create(table, callback) {
|
|
27605
28146
|
const builder = new TableBuilder(table);
|
|
27606
28147
|
callback(builder);
|
|
@@ -27631,9 +28172,9 @@ var Schema = {
|
|
|
27631
28172
|
class Migrator {
|
|
27632
28173
|
migrations = [];
|
|
27633
28174
|
async ensureTable() {
|
|
27634
|
-
const exists = await
|
|
28175
|
+
const exists = await Schema2.hasTable("migrations");
|
|
27635
28176
|
if (!exists) {
|
|
27636
|
-
await
|
|
28177
|
+
await Schema2.create("migrations", (table) => {
|
|
27637
28178
|
table.id();
|
|
27638
28179
|
table.string("name").unique();
|
|
27639
28180
|
table.integer("batch");
|
|
@@ -29494,7 +30035,7 @@ function parseRule(rule) {
|
|
|
29494
30035
|
return { name, param };
|
|
29495
30036
|
}
|
|
29496
30037
|
function validate(data, schema) {
|
|
29497
|
-
const
|
|
30038
|
+
const errors2 = new Map;
|
|
29498
30039
|
const validData = {};
|
|
29499
30040
|
for (const [field, rules] of Object.entries(schema)) {
|
|
29500
30041
|
const value = data[field];
|
|
@@ -29530,12 +30071,12 @@ function validate(data, schema) {
|
|
|
29530
30071
|
}
|
|
29531
30072
|
}
|
|
29532
30073
|
if (fieldErrors.length > 0) {
|
|
29533
|
-
|
|
30074
|
+
errors2.set(field, fieldErrors);
|
|
29534
30075
|
} else if (value !== undefined) {
|
|
29535
30076
|
validData[field] = value;
|
|
29536
30077
|
}
|
|
29537
30078
|
}
|
|
29538
|
-
return { valid:
|
|
30079
|
+
return { valid: errors2.size === 0, errors: errors2, data: validData };
|
|
29539
30080
|
}
|
|
29540
30081
|
function validateAsync(data, schema) {
|
|
29541
30082
|
return Promise.resolve(validate(data, schema));
|
|
@@ -29764,193 +30305,6 @@ class RequestParser {
|
|
|
29764
30305
|
function parseRequest(raw) {
|
|
29765
30306
|
return new RequestParser(raw);
|
|
29766
30307
|
}
|
|
29767
|
-
// src/utils/ErrorHandler.ts
|
|
29768
|
-
class CanxError extends Error {
|
|
29769
|
-
code;
|
|
29770
|
-
statusCode;
|
|
29771
|
-
details;
|
|
29772
|
-
timestamp;
|
|
29773
|
-
constructor(message, code = "CANX_ERROR", statusCode = 500, details) {
|
|
29774
|
-
super(message);
|
|
29775
|
-
this.name = "CanxError";
|
|
29776
|
-
this.code = code;
|
|
29777
|
-
this.statusCode = statusCode;
|
|
29778
|
-
this.details = details;
|
|
29779
|
-
this.timestamp = new Date;
|
|
29780
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
29781
|
-
}
|
|
29782
|
-
toJSON() {
|
|
29783
|
-
return {
|
|
29784
|
-
name: this.name,
|
|
29785
|
-
code: this.code,
|
|
29786
|
-
message: this.message,
|
|
29787
|
-
statusCode: this.statusCode,
|
|
29788
|
-
details: this.details,
|
|
29789
|
-
timestamp: this.timestamp.toISOString()
|
|
29790
|
-
};
|
|
29791
|
-
}
|
|
29792
|
-
}
|
|
29793
|
-
|
|
29794
|
-
class ValidationError extends CanxError {
|
|
29795
|
-
errors;
|
|
29796
|
-
constructor(errors, message = "Validation failed") {
|
|
29797
|
-
const errorMap = errors instanceof Map ? errors : new Map(Object.entries(errors));
|
|
29798
|
-
super(message, "VALIDATION_ERROR", 422, { errors: Object.fromEntries(errorMap) });
|
|
29799
|
-
this.name = "ValidationError";
|
|
29800
|
-
this.errors = errorMap;
|
|
29801
|
-
}
|
|
29802
|
-
}
|
|
29803
|
-
|
|
29804
|
-
class NotFoundError extends CanxError {
|
|
29805
|
-
constructor(resource = "Resource", id) {
|
|
29806
|
-
const message = id ? `${resource} with ID ${id} not found` : `${resource} not found`;
|
|
29807
|
-
super(message, "NOT_FOUND", 404, { resource, id });
|
|
29808
|
-
this.name = "NotFoundError";
|
|
29809
|
-
}
|
|
29810
|
-
}
|
|
29811
|
-
|
|
29812
|
-
class AuthenticationError extends CanxError {
|
|
29813
|
-
constructor(message = "Authentication required") {
|
|
29814
|
-
super(message, "AUTHENTICATION_ERROR", 401);
|
|
29815
|
-
this.name = "AuthenticationError";
|
|
29816
|
-
}
|
|
29817
|
-
}
|
|
29818
|
-
|
|
29819
|
-
class AuthorizationError extends CanxError {
|
|
29820
|
-
constructor(message = "You do not have permission to access this resource") {
|
|
29821
|
-
super(message, "AUTHORIZATION_ERROR", 403);
|
|
29822
|
-
this.name = "AuthorizationError";
|
|
29823
|
-
}
|
|
29824
|
-
}
|
|
29825
|
-
|
|
29826
|
-
class ConflictError extends CanxError {
|
|
29827
|
-
constructor(message = "Resource conflict", resource) {
|
|
29828
|
-
super(message, "CONFLICT_ERROR", 409, { resource });
|
|
29829
|
-
this.name = "ConflictError";
|
|
29830
|
-
}
|
|
29831
|
-
}
|
|
29832
|
-
|
|
29833
|
-
class RateLimitError extends CanxError {
|
|
29834
|
-
retryAfter;
|
|
29835
|
-
constructor(retryAfter = 60, message = "Too many requests") {
|
|
29836
|
-
super(message, "RATE_LIMIT_ERROR", 429, { retryAfter });
|
|
29837
|
-
this.name = "RateLimitError";
|
|
29838
|
-
this.retryAfter = retryAfter;
|
|
29839
|
-
}
|
|
29840
|
-
}
|
|
29841
|
-
|
|
29842
|
-
class BadRequestError extends CanxError {
|
|
29843
|
-
constructor(message = "Bad request") {
|
|
29844
|
-
super(message, "BAD_REQUEST", 400);
|
|
29845
|
-
this.name = "BadRequestError";
|
|
29846
|
-
}
|
|
29847
|
-
}
|
|
29848
|
-
|
|
29849
|
-
class DatabaseError2 extends CanxError {
|
|
29850
|
-
constructor(message = "Database error", originalError) {
|
|
29851
|
-
super(message, "DATABASE_ERROR", 500, {
|
|
29852
|
-
originalMessage: originalError?.message,
|
|
29853
|
-
stack: originalError?.stack
|
|
29854
|
-
});
|
|
29855
|
-
this.name = "DatabaseError";
|
|
29856
|
-
}
|
|
29857
|
-
}
|
|
29858
|
-
|
|
29859
|
-
class ServiceUnavailableError extends CanxError {
|
|
29860
|
-
constructor(service, message) {
|
|
29861
|
-
super(message || `Service ${service} is currently unavailable`, "SERVICE_UNAVAILABLE", 503, { service });
|
|
29862
|
-
this.name = "ServiceUnavailableError";
|
|
29863
|
-
}
|
|
29864
|
-
}
|
|
29865
|
-
function errorHandler(options = {}) {
|
|
29866
|
-
const { showStack = true, logErrors = true } = options;
|
|
29867
|
-
return async (req, res, next) => {
|
|
29868
|
-
try {
|
|
29869
|
-
return await next();
|
|
29870
|
-
} catch (error) {
|
|
29871
|
-
if (logErrors) {
|
|
29872
|
-
console.error(`[CanxJS Error] ${req.method} ${req.path}:`, error);
|
|
29873
|
-
}
|
|
29874
|
-
if (options.onError) {
|
|
29875
|
-
options.onError(error, req);
|
|
29876
|
-
}
|
|
29877
|
-
if (error instanceof CanxError) {
|
|
29878
|
-
const response2 = {
|
|
29879
|
-
error: {
|
|
29880
|
-
code: error.code,
|
|
29881
|
-
message: error.message,
|
|
29882
|
-
...error.details && { details: error.details },
|
|
29883
|
-
...showStack && { stack: error.stack }
|
|
29884
|
-
}
|
|
29885
|
-
};
|
|
29886
|
-
return res.status(error.statusCode).json(response2);
|
|
29887
|
-
}
|
|
29888
|
-
const unknownError = error;
|
|
29889
|
-
return res.status(500).json({
|
|
29890
|
-
error: {
|
|
29891
|
-
code: "INTERNAL_ERROR",
|
|
29892
|
-
message: showStack ? unknownError.message : "Internal server error",
|
|
29893
|
-
...showStack && { stack: unknownError.stack }
|
|
29894
|
-
}
|
|
29895
|
-
});
|
|
29896
|
-
}
|
|
29897
|
-
};
|
|
29898
|
-
}
|
|
29899
|
-
function asyncHandler(fn) {
|
|
29900
|
-
return async (req, res) => {
|
|
29901
|
-
try {
|
|
29902
|
-
return await fn(req, res);
|
|
29903
|
-
} catch (error) {
|
|
29904
|
-
if (error instanceof CanxError) {
|
|
29905
|
-
return res.status(error.statusCode).json(error.toJSON());
|
|
29906
|
-
}
|
|
29907
|
-
throw error;
|
|
29908
|
-
}
|
|
29909
|
-
};
|
|
29910
|
-
}
|
|
29911
|
-
function assertFound(value, resource = "Resource", id) {
|
|
29912
|
-
if (value === null || value === undefined) {
|
|
29913
|
-
throw new NotFoundError(resource, id);
|
|
29914
|
-
}
|
|
29915
|
-
return value;
|
|
29916
|
-
}
|
|
29917
|
-
function assertAuthenticated(user) {
|
|
29918
|
-
if (!user) {
|
|
29919
|
-
throw new AuthenticationError;
|
|
29920
|
-
}
|
|
29921
|
-
return user;
|
|
29922
|
-
}
|
|
29923
|
-
function assertAuthorized(condition, message) {
|
|
29924
|
-
if (!condition) {
|
|
29925
|
-
throw new AuthorizationError(message);
|
|
29926
|
-
}
|
|
29927
|
-
}
|
|
29928
|
-
function assertValid(valid, errors) {
|
|
29929
|
-
if (!valid) {
|
|
29930
|
-
throw new ValidationError(errors || new Map);
|
|
29931
|
-
}
|
|
29932
|
-
}
|
|
29933
|
-
var errors = {
|
|
29934
|
-
CanxError,
|
|
29935
|
-
ValidationError,
|
|
29936
|
-
NotFoundError,
|
|
29937
|
-
AuthenticationError,
|
|
29938
|
-
AuthorizationError,
|
|
29939
|
-
ConflictError,
|
|
29940
|
-
RateLimitError,
|
|
29941
|
-
BadRequestError,
|
|
29942
|
-
DatabaseError: DatabaseError2,
|
|
29943
|
-
ServiceUnavailableError
|
|
29944
|
-
};
|
|
29945
|
-
var ErrorHandler_default = {
|
|
29946
|
-
...errors,
|
|
29947
|
-
errorHandler,
|
|
29948
|
-
asyncHandler,
|
|
29949
|
-
assertFound,
|
|
29950
|
-
assertAuthenticated,
|
|
29951
|
-
assertAuthorized,
|
|
29952
|
-
assertValid
|
|
29953
|
-
};
|
|
29954
30308
|
// src/utils/Logger.ts
|
|
29955
30309
|
var LOG_LEVELS = {
|
|
29956
30310
|
debug: 0,
|
|
@@ -30870,8 +31224,13 @@ class Canx {
|
|
|
30870
31224
|
return this;
|
|
30871
31225
|
}
|
|
30872
31226
|
async listen(port, callback) {
|
|
30873
|
-
if (port)
|
|
31227
|
+
if (typeof port === "function") {
|
|
31228
|
+
callback = port;
|
|
31229
|
+
port = undefined;
|
|
31230
|
+
}
|
|
31231
|
+
if (port && typeof port === "number") {
|
|
30874
31232
|
this.config.port = port;
|
|
31233
|
+
}
|
|
30875
31234
|
for (const plugin of this.plugins) {
|
|
30876
31235
|
await plugin.install(this);
|
|
30877
31236
|
}
|
|
@@ -30919,9 +31278,11 @@ function createApp(config) {
|
|
|
30919
31278
|
return new Canx(config);
|
|
30920
31279
|
}
|
|
30921
31280
|
export {
|
|
31281
|
+
z,
|
|
30922
31282
|
ws,
|
|
30923
31283
|
verifyPassword,
|
|
30924
31284
|
verifyJWT,
|
|
31285
|
+
validateSchema,
|
|
30925
31286
|
validateAsync,
|
|
30926
31287
|
validate,
|
|
30927
31288
|
useI18n,
|
|
@@ -30935,6 +31296,7 @@ export {
|
|
|
30935
31296
|
sendMail,
|
|
30936
31297
|
factory as seederFactory,
|
|
30937
31298
|
seeder,
|
|
31299
|
+
security,
|
|
30938
31300
|
roles,
|
|
30939
31301
|
response,
|
|
30940
31302
|
resolve,
|
|
@@ -30942,6 +31304,7 @@ export {
|
|
|
30942
31304
|
renderPage,
|
|
30943
31305
|
render,
|
|
30944
31306
|
redisCheck,
|
|
31307
|
+
rateLimit2 as rateLimitMiddleware,
|
|
30945
31308
|
rateLimit,
|
|
30946
31309
|
randomUuid,
|
|
30947
31310
|
randomString,
|
|
@@ -31031,13 +31394,14 @@ export {
|
|
|
31031
31394
|
assertFound,
|
|
31032
31395
|
assertAuthorized,
|
|
31033
31396
|
assertAuthenticated,
|
|
31397
|
+
Schema as ZSchema,
|
|
31034
31398
|
WebSocketServer,
|
|
31035
31399
|
ValidationError,
|
|
31036
31400
|
TestClient,
|
|
31037
31401
|
ServiceUnavailableError,
|
|
31038
31402
|
Server,
|
|
31039
31403
|
ScopedContainer,
|
|
31040
|
-
Schema,
|
|
31404
|
+
Schema2 as Schema,
|
|
31041
31405
|
S3Driver,
|
|
31042
31406
|
Router,
|
|
31043
31407
|
ResponseBuilder,
|
|
@@ -31068,7 +31432,7 @@ export {
|
|
|
31068
31432
|
EventServiceProvider,
|
|
31069
31433
|
EventEmitter,
|
|
31070
31434
|
Delete,
|
|
31071
|
-
|
|
31435
|
+
DatabaseError,
|
|
31072
31436
|
Controller,
|
|
31073
31437
|
Container,
|
|
31074
31438
|
ConflictError,
|