ts-prorm-orm 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/CHANGELOG.md +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validator System for the TypeScript ORM
|
|
4
|
+
* Provides comprehensive validation methods and utilities
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Validator = exports.ValidationError = void 0;
|
|
8
|
+
exports.registerValidatorModel = registerValidatorModel;
|
|
9
|
+
exports.unregisterValidatorModel = unregisterValidatorModel;
|
|
10
|
+
exports.getValidatorModel = getValidatorModel;
|
|
11
|
+
exports.validate = validate;
|
|
12
|
+
exports.validateField = validateField;
|
|
13
|
+
exports.validateAsync = validateAsync;
|
|
14
|
+
exports.validateFieldAsync = validateFieldAsync;
|
|
15
|
+
exports.createValidationError = createValidationError;
|
|
16
|
+
exports.getValidatorsForDataType = getValidatorsForDataType;
|
|
17
|
+
const operators_1 = require("../models/operators");
|
|
18
|
+
// ==================== Validation Error ====================
|
|
19
|
+
/**
|
|
20
|
+
* Custom validation error class that extends Error
|
|
21
|
+
* Contains an array of validation error items
|
|
22
|
+
*/
|
|
23
|
+
class ValidationError extends Error {
|
|
24
|
+
constructor(message = 'Validation error', errors = []) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'PrormValidationError';
|
|
27
|
+
this.errors = errors;
|
|
28
|
+
// Maintains proper stack trace for where error was thrown (V8 engines)
|
|
29
|
+
if (Error.captureStackTrace) {
|
|
30
|
+
Error.captureStackTrace(this, ValidationError);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Add an error to the validation errors array
|
|
35
|
+
*/
|
|
36
|
+
addError(error) {
|
|
37
|
+
this.errors.push(error);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if there are any validation errors
|
|
41
|
+
*/
|
|
42
|
+
hasErrors() {
|
|
43
|
+
return this.errors.length > 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get errors for a specific field
|
|
47
|
+
*/
|
|
48
|
+
getErrorsForField(path) {
|
|
49
|
+
return this.errors.filter((error) => error.path === path);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ValidationError = ValidationError;
|
|
53
|
+
/**
|
|
54
|
+
* Registry mapping a model name to a live model class reference.
|
|
55
|
+
*
|
|
56
|
+
* Validators run inside `src/validators/**` and, by design, have no
|
|
57
|
+
* built-in access to a live `Prorm` instance or model classes. To let
|
|
58
|
+
* `isUnique` perform a real `findOne` uniqueness check without validators
|
|
59
|
+
* importing `src/models/model.ts` (which would create a circular
|
|
60
|
+
* dependency: model.ts -> validators -> model.ts), model classes must be
|
|
61
|
+
* registered here - typically when a model is defined/initialized.
|
|
62
|
+
*
|
|
63
|
+
* NOTE: as of this fix, nothing in `src/models/model.ts` calls
|
|
64
|
+
* `registerValidatorModel` yet. Until it does, `isUnique`/`isUniqueAsync`
|
|
65
|
+
* cannot resolve a `model: 'SomeModelName'` string reference and will fall
|
|
66
|
+
* back to a safe no-op (see `isUniqueAsync` below). Callers can work around
|
|
67
|
+
* this today by passing a live model class directly as `options.model`
|
|
68
|
+
* instead of a string name.
|
|
69
|
+
*/
|
|
70
|
+
const modelRegistry = new Map();
|
|
71
|
+
/**
|
|
72
|
+
* Register a live model class so `isUnique`/`isUniqueAsync` validators can
|
|
73
|
+
* resolve a `model: 'SomeModelName'` string reference to a real class with
|
|
74
|
+
* a working `findOne`.
|
|
75
|
+
*/
|
|
76
|
+
function registerValidatorModel(name, model) {
|
|
77
|
+
modelRegistry.set(name, model);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Remove a previously registered model reference.
|
|
81
|
+
*/
|
|
82
|
+
function unregisterValidatorModel(name) {
|
|
83
|
+
modelRegistry.delete(name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Look up a registered model reference by name.
|
|
87
|
+
*/
|
|
88
|
+
function getValidatorModel(name) {
|
|
89
|
+
return modelRegistry.get(name);
|
|
90
|
+
}
|
|
91
|
+
// ==================== Validator Class ====================
|
|
92
|
+
/**
|
|
93
|
+
* Validator class with built-in validation methods
|
|
94
|
+
* Provides comprehensive validation for various data types
|
|
95
|
+
*/
|
|
96
|
+
class Validator {
|
|
97
|
+
/**
|
|
98
|
+
* Check if value is a valid email address
|
|
99
|
+
*/
|
|
100
|
+
static isEmail(value) {
|
|
101
|
+
if (value === null || value === undefined || value === '') {
|
|
102
|
+
return true; // Use notEmpty for required fields
|
|
103
|
+
}
|
|
104
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
105
|
+
return emailRegex.test(String(value));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if value is a valid URL
|
|
109
|
+
*/
|
|
110
|
+
static isUrl(value) {
|
|
111
|
+
if (value === null || value === undefined || value === '') {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
new URL(String(value));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if value is a valid IP address (IPv4 or IPv6)
|
|
124
|
+
*/
|
|
125
|
+
static isIP(value) {
|
|
126
|
+
if (value === null || value === undefined || value === '') {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const ipValue = String(value);
|
|
130
|
+
// IPv4
|
|
131
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
132
|
+
if (ipv4Regex.test(ipValue)) {
|
|
133
|
+
const parts = ipValue.split('.');
|
|
134
|
+
return parts.every((part) => {
|
|
135
|
+
const num = parseInt(part, 10);
|
|
136
|
+
return num >= 0 && num <= 255;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// IPv6
|
|
140
|
+
const ipv6Regex = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i;
|
|
141
|
+
return ipv6Regex.test(ipValue);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if value contains only alphabetic characters
|
|
145
|
+
*/
|
|
146
|
+
static isAlpha(value) {
|
|
147
|
+
if (value === null || value === undefined || value === '') {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
const alphaRegex = /^[a-zA-Z]+$/;
|
|
151
|
+
return alphaRegex.test(String(value));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check if value contains only alphanumeric characters
|
|
155
|
+
*/
|
|
156
|
+
static isAlphanumeric(value) {
|
|
157
|
+
if (value === null || value === undefined || value === '') {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
const alphanumericRegex = /^[a-zA-Z0-9]+$/;
|
|
161
|
+
return alphanumericRegex.test(String(value));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if value contains only numeric characters
|
|
165
|
+
*/
|
|
166
|
+
static isNumeric(value) {
|
|
167
|
+
if (value === null || value === undefined || value === '') {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const numericRegex = /^[0-9]+$/;
|
|
171
|
+
return numericRegex.test(String(value));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if value is an integer
|
|
175
|
+
*/
|
|
176
|
+
static isInt(value) {
|
|
177
|
+
if (value === null || value === undefined || value === '') {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (typeof value === 'number') {
|
|
181
|
+
return Number.isInteger(value);
|
|
182
|
+
}
|
|
183
|
+
const intRegex = /^-?\d+$/;
|
|
184
|
+
return intRegex.test(String(value));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if value is a floating-point number
|
|
188
|
+
*/
|
|
189
|
+
static isFloat(value) {
|
|
190
|
+
if (value === null || value === undefined || value === '') {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
if (typeof value === 'number') {
|
|
194
|
+
return !isNaN(value);
|
|
195
|
+
}
|
|
196
|
+
return !isNaN(parseFloat(String(value)));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check if value is a decimal number
|
|
200
|
+
*/
|
|
201
|
+
static isDecimal(value) {
|
|
202
|
+
if (value === null || value === undefined || value === '') {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
const decimalRegex = /^\d+\.\d+$/;
|
|
206
|
+
return decimalRegex.test(String(value));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if value is a boolean
|
|
210
|
+
*/
|
|
211
|
+
static isBoolean(value) {
|
|
212
|
+
if (value === null || value === undefined || value === '') {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (typeof value === 'boolean') {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (typeof value === 'string') {
|
|
219
|
+
const lower = value.toLowerCase();
|
|
220
|
+
return lower === 'true' || lower === 'false' || lower === '1' || lower === '0';
|
|
221
|
+
}
|
|
222
|
+
return typeof value === 'number' && (value === 0 || value === 1);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Check if value is a valid date
|
|
226
|
+
*/
|
|
227
|
+
static isDate(value) {
|
|
228
|
+
if (value === null || value === undefined || value === '') {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (value instanceof Date) {
|
|
232
|
+
return !isNaN(value.getTime());
|
|
233
|
+
}
|
|
234
|
+
const date = new Date(String(value));
|
|
235
|
+
return !isNaN(date.getTime());
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Check if value is a valid time string (HH:MM or HH:MM:SS)
|
|
239
|
+
*/
|
|
240
|
+
static isTime(value) {
|
|
241
|
+
if (value === null || value === undefined || value === '') {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const timeRegex = /^([01]?[0-9]|2[0-3]):([0-5][0-9])(:([0-5][0-9]))?$/;
|
|
245
|
+
return timeRegex.test(String(value));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if value is a valid date string (ISO 8601)
|
|
249
|
+
*/
|
|
250
|
+
static isDateString(value) {
|
|
251
|
+
if (value === null || value === undefined || value === '') {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const dateStr = String(value);
|
|
255
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/;
|
|
256
|
+
return isoDateRegex.test(dateStr) && Validator.isDate(value);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if value is a valid UUID (version 1, 4, 5)
|
|
260
|
+
*/
|
|
261
|
+
static isUUID(value) {
|
|
262
|
+
if (value === null || value === undefined || value === '') {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
266
|
+
return uuidRegex.test(String(value));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if value is valid JSON
|
|
270
|
+
*/
|
|
271
|
+
static isJSON(value) {
|
|
272
|
+
if (value === null || value === undefined || value === '') {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
if (typeof value === 'object' && value !== null) {
|
|
276
|
+
try {
|
|
277
|
+
JSON.stringify(value);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
JSON.parse(String(value));
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if value is empty (null, undefined, or empty string)
|
|
294
|
+
*/
|
|
295
|
+
static isEmpty(value) {
|
|
296
|
+
if (value === null || value === undefined) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (typeof value === 'string') {
|
|
300
|
+
return value.trim() === '';
|
|
301
|
+
}
|
|
302
|
+
if (Array.isArray(value)) {
|
|
303
|
+
return value.length === 0;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check if value is not empty
|
|
309
|
+
*/
|
|
310
|
+
static notEmpty(value) {
|
|
311
|
+
return !Validator.isEmpty(value);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check if string length is within bounds
|
|
315
|
+
* @param value - The value to check
|
|
316
|
+
* @param len - Single number for exact length, or [min, max] tuple
|
|
317
|
+
*/
|
|
318
|
+
static len(value, len) {
|
|
319
|
+
if (value === null || value === undefined) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
const strValue = String(value);
|
|
323
|
+
const strLen = strValue.length;
|
|
324
|
+
if (typeof len === 'number') {
|
|
325
|
+
return strLen === len;
|
|
326
|
+
}
|
|
327
|
+
const [min, max] = len;
|
|
328
|
+
if (max !== undefined) {
|
|
329
|
+
return strLen >= min && strLen <= max;
|
|
330
|
+
}
|
|
331
|
+
return strLen >= min;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Check if numeric value is >= minimum
|
|
335
|
+
*/
|
|
336
|
+
static min(value, minVal) {
|
|
337
|
+
if (value === null || value === undefined || value === '') {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
const num = typeof value === 'number' ? value : parseFloat(String(value));
|
|
341
|
+
return !isNaN(num) && num >= minVal;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Check if numeric value is <= maximum
|
|
345
|
+
*/
|
|
346
|
+
static max(value, maxVal) {
|
|
347
|
+
if (value === null || value === undefined || value === '') {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
const num = typeof value === 'number' ? value : parseFloat(String(value));
|
|
351
|
+
return !isNaN(num) && num <= maxVal;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if numeric value is within range
|
|
355
|
+
*/
|
|
356
|
+
static range(value, range) {
|
|
357
|
+
const [min, max] = range;
|
|
358
|
+
return Validator.min(value, min) && Validator.max(value, max);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Check if value is in the allowed list
|
|
362
|
+
*/
|
|
363
|
+
static isIn(value, values) {
|
|
364
|
+
if (value === null || value === undefined) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
return values.includes(value);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Check if value is not in the disallowed list
|
|
371
|
+
*/
|
|
372
|
+
static notIn(value, values) {
|
|
373
|
+
if (value === null || value === undefined) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return !values.includes(value);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Synchronous uniqueness check.
|
|
380
|
+
*
|
|
381
|
+
* A real uniqueness check requires an async database query, which cannot
|
|
382
|
+
* be performed from a synchronous function. This method exists only for
|
|
383
|
+
* backwards compatibility with call sites that still use the
|
|
384
|
+
* synchronous `validate()`/`validateField()` entry points; it always
|
|
385
|
+
* returns `true` (never fails validation) and logs a warning so silent
|
|
386
|
+
* false-passes are visible.
|
|
387
|
+
*
|
|
388
|
+
* Prefer `Validator.isUniqueAsync()` (via `validateAsync()`/
|
|
389
|
+
* `validateFieldAsync()`) wherever a real database uniqueness check is
|
|
390
|
+
* required.
|
|
391
|
+
*/
|
|
392
|
+
static isUnique(value, options) {
|
|
393
|
+
if (value === null || value === undefined) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
397
|
+
console.warn('[Validator.isUnique] Synchronous uniqueness checks cannot query the database and ' +
|
|
398
|
+
'always pass. Use Validator.isUniqueAsync (via validateAsync/validateFieldAsync) for ' +
|
|
399
|
+
`a real uniqueness check${options?.field ? ` on field "${options.field}"` : ''}.`);
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Check if value is unique by performing a real database lookup via
|
|
405
|
+
* `Model.findOne()`.
|
|
406
|
+
*
|
|
407
|
+
* @param value - The value to check for uniqueness
|
|
408
|
+
* @param options.model - Either a model name registered via
|
|
409
|
+
* `registerValidatorModel()`, or a live model reference (any object
|
|
410
|
+
* exposing an async `findOne({ where })` method, e.g. a Model class).
|
|
411
|
+
* @param options.field - The column/attribute name to check. Defaults to
|
|
412
|
+
* the field the rule was declared on when used via `validateAsync`.
|
|
413
|
+
* @param options.where - Extra where conditions to scope the lookup
|
|
414
|
+
* (e.g. multi-column uniqueness, soft-delete scoping).
|
|
415
|
+
* @param options.exclude - Primary-key (or other) conditions identifying
|
|
416
|
+
* the current instance, so an update does not fail uniqueness against
|
|
417
|
+
* itself. Each entry is turned into a `{ [Op.ne]: value }` condition.
|
|
418
|
+
*/
|
|
419
|
+
static async isUniqueAsync(value, options) {
|
|
420
|
+
if (value === null || value === undefined) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
if (!options || !options.field) {
|
|
424
|
+
// Without knowing which column to check, there is nothing to query.
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const modelRef = typeof options.model === 'string' ? modelRegistry.get(options.model) : options.model;
|
|
428
|
+
if (!modelRef || typeof modelRef.findOne !== 'function') {
|
|
429
|
+
// No live model reference available (e.g. model not registered).
|
|
430
|
+
// Fail open rather than silently rejecting otherwise-valid data.
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
const where = {
|
|
434
|
+
[options.field]: value,
|
|
435
|
+
...(options.where ?? {}),
|
|
436
|
+
};
|
|
437
|
+
if (options.exclude) {
|
|
438
|
+
for (const [key, excludeValue] of Object.entries(options.exclude)) {
|
|
439
|
+
if (excludeValue !== undefined && excludeValue !== null) {
|
|
440
|
+
where[key] = { [operators_1.Op.ne]: excludeValue };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const existing = await modelRef.findOne({ where });
|
|
445
|
+
return existing === null || existing === undefined;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Check if value equals the expected value
|
|
449
|
+
*/
|
|
450
|
+
static equals(value, expected) {
|
|
451
|
+
if (value === null || value === undefined) {
|
|
452
|
+
return expected === null || expected === undefined;
|
|
453
|
+
}
|
|
454
|
+
return value === expected;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Check if value does not equal the expected value
|
|
458
|
+
*/
|
|
459
|
+
static notEquals(value, expected) {
|
|
460
|
+
return !Validator.equals(value, expected);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Check if value matches a regular expression
|
|
464
|
+
*/
|
|
465
|
+
static matches(value, pattern) {
|
|
466
|
+
if (value === null || value === undefined || value === '') {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
|
|
470
|
+
return regex.test(String(value));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Check if value does not match a regular expression
|
|
474
|
+
*/
|
|
475
|
+
static notMatches(value, pattern) {
|
|
476
|
+
return !Validator.matches(value, pattern);
|
|
477
|
+
}
|
|
478
|
+
// Additional useful validators
|
|
479
|
+
/**
|
|
480
|
+
* Check if value is a valid credit card number (Luhn algorithm)
|
|
481
|
+
*/
|
|
482
|
+
static isCreditCard(value) {
|
|
483
|
+
if (value === null || value === undefined || value === '') {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
const card = String(value).replace(/\s+/g, '');
|
|
487
|
+
if (!/^\d{13,19}$/.test(card)) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
// Luhn algorithm
|
|
491
|
+
let sum = 0;
|
|
492
|
+
let isEven = false;
|
|
493
|
+
for (let i = card.length - 1; i >= 0; i--) {
|
|
494
|
+
let digit = parseInt(card[i], 10);
|
|
495
|
+
if (isEven) {
|
|
496
|
+
digit *= 2;
|
|
497
|
+
if (digit > 9) {
|
|
498
|
+
digit -= 9;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
sum += digit;
|
|
502
|
+
isEven = !isEven;
|
|
503
|
+
}
|
|
504
|
+
return sum % 10 === 0;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Check if value is a valid postal code
|
|
508
|
+
*/
|
|
509
|
+
static isPostalCode(value, locale = 'US') {
|
|
510
|
+
if (value === null || value === undefined || value === '') {
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
const postalCode = String(value);
|
|
514
|
+
const patterns = {
|
|
515
|
+
US: /^\d{5}(-\d{4})?$/,
|
|
516
|
+
CA: /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i,
|
|
517
|
+
UK: /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i,
|
|
518
|
+
DE: /^\d{5}$/,
|
|
519
|
+
FR: /^\d{5}$/,
|
|
520
|
+
JP: /^\d{3}-?\d{4}$/,
|
|
521
|
+
};
|
|
522
|
+
const pattern = patterns[locale.toUpperCase()] || /^.+$/;
|
|
523
|
+
return pattern.test(postalCode);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Check if value is a valid phone number
|
|
527
|
+
*/
|
|
528
|
+
static isMobilePhone(value, locale = 'en-US') {
|
|
529
|
+
if (value === null || value === undefined || value === '') {
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
const phone = String(value).replace(/[\s\-\(\)]/g, '');
|
|
533
|
+
const patterns = {
|
|
534
|
+
'en-US': /^(\+1)?\d{10}$/,
|
|
535
|
+
'en-GB': /^(\+44)?\d{10}$/,
|
|
536
|
+
'de-DE': /^(\+49)?\d{10,11}$/,
|
|
537
|
+
'fr-FR': /^(\+33)?\d{9}$/,
|
|
538
|
+
'jp-JP': /^(\+81)?\d{9,10}$/,
|
|
539
|
+
};
|
|
540
|
+
const pattern = patterns[locale.toLowerCase()] || /^\+?\d{7,15}$/;
|
|
541
|
+
return pattern.test(phone);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Check if value is a valid hexadecimal color
|
|
545
|
+
*/
|
|
546
|
+
static isHexColor(value) {
|
|
547
|
+
if (value === null || value === undefined || value === '') {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
const hexColorRegex = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/;
|
|
551
|
+
return hexColorRegex.test(String(value));
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Check if string contains only lowercase characters
|
|
555
|
+
*/
|
|
556
|
+
static isLowercase(value) {
|
|
557
|
+
if (value === null || value === undefined || value === '') {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
return String(value) === String(value).toLowerCase();
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Check if string contains only uppercase characters
|
|
564
|
+
*/
|
|
565
|
+
static isUppercase(value) {
|
|
566
|
+
if (value === null || value === undefined || value === '') {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
return String(value) === String(value).toUpperCase();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
exports.Validator = Validator;
|
|
573
|
+
// ==================== Validate Function ====================
|
|
574
|
+
/**
|
|
575
|
+
* Validator method map for dynamic method lookup
|
|
576
|
+
*/
|
|
577
|
+
const validatorMethods = {
|
|
578
|
+
isEmail: Validator.isEmail,
|
|
579
|
+
isUrl: Validator.isUrl,
|
|
580
|
+
isIP: Validator.isIP,
|
|
581
|
+
isAlpha: Validator.isAlpha,
|
|
582
|
+
isAlphanumeric: Validator.isAlphanumeric,
|
|
583
|
+
isNumeric: Validator.isNumeric,
|
|
584
|
+
isInt: Validator.isInt,
|
|
585
|
+
isFloat: Validator.isFloat,
|
|
586
|
+
isDecimal: Validator.isDecimal,
|
|
587
|
+
isBoolean: Validator.isBoolean,
|
|
588
|
+
isDate: Validator.isDate,
|
|
589
|
+
isTime: Validator.isTime,
|
|
590
|
+
isDateString: Validator.isDateString,
|
|
591
|
+
isUUID: Validator.isUUID,
|
|
592
|
+
isJSON: Validator.isJSON,
|
|
593
|
+
isEmpty: Validator.isEmpty,
|
|
594
|
+
notEmpty: Validator.notEmpty,
|
|
595
|
+
isIn: (value, ...args) => Validator.isIn(value, args),
|
|
596
|
+
notIn: (value, ...args) => Validator.notIn(value, args),
|
|
597
|
+
isUnique: (value, ...args) => Validator.isUnique(value, args[0]),
|
|
598
|
+
equals: Validator.equals,
|
|
599
|
+
notEquals: Validator.notEquals,
|
|
600
|
+
matches: (value, ...args) => Validator.matches(value, args[0]),
|
|
601
|
+
notMatches: (value, ...args) => Validator.notMatches(value, args[0]),
|
|
602
|
+
isCreditCard: Validator.isCreditCard,
|
|
603
|
+
isPostalCode: (value, ...args) => Validator.isPostalCode(value, args[0]),
|
|
604
|
+
isMobilePhone: (value, ...args) => Validator.isMobilePhone(value, args[0]),
|
|
605
|
+
isHexColor: Validator.isHexColor,
|
|
606
|
+
isLowercase: Validator.isLowercase,
|
|
607
|
+
isUppercase: Validator.isUppercase,
|
|
608
|
+
};
|
|
609
|
+
/**
|
|
610
|
+
* Get error message for a validation rule
|
|
611
|
+
*/
|
|
612
|
+
function getErrorMessage(rule, field, ruleValue, customMessage) {
|
|
613
|
+
if (customMessage) {
|
|
614
|
+
return customMessage;
|
|
615
|
+
}
|
|
616
|
+
const messages = {
|
|
617
|
+
isEmail: 'Invalid email address',
|
|
618
|
+
isUrl: 'Invalid URL',
|
|
619
|
+
isIP: 'Invalid IP address',
|
|
620
|
+
isAlpha: 'Must contain only letters',
|
|
621
|
+
isAlphanumeric: 'Must contain only letters and numbers',
|
|
622
|
+
isNumeric: 'Must contain only numbers',
|
|
623
|
+
isInt: 'Must be an integer',
|
|
624
|
+
isFloat: 'Must be a number',
|
|
625
|
+
isDecimal: 'Must be a decimal number',
|
|
626
|
+
isBoolean: 'Must be a boolean',
|
|
627
|
+
isDate: 'Invalid date',
|
|
628
|
+
isTime: 'Invalid time format',
|
|
629
|
+
isDateString: 'Invalid date string',
|
|
630
|
+
isUUID: 'Invalid UUID',
|
|
631
|
+
isJSON: 'Invalid JSON',
|
|
632
|
+
isEmpty: 'Cannot be empty',
|
|
633
|
+
notEmpty: 'Cannot be empty',
|
|
634
|
+
isIn: `Must be one of: ${Array.isArray(ruleValue) ? ruleValue.join(', ') : ruleValue}`,
|
|
635
|
+
notIn: `Must not be one of: ${Array.isArray(ruleValue) ? ruleValue.join(', ') : ruleValue}`,
|
|
636
|
+
isUnique: 'Value must be unique',
|
|
637
|
+
equals: `Must equal ${ruleValue}`,
|
|
638
|
+
notEquals: `Must not equal ${ruleValue}`,
|
|
639
|
+
matches: 'Does not match required pattern',
|
|
640
|
+
notMatches: 'Must not match the pattern',
|
|
641
|
+
len: `Length must be ${Array.isArray(ruleValue) ? `${ruleValue[0]}-${ruleValue[1]}` : ruleValue}`,
|
|
642
|
+
min: `Must be at least ${ruleValue}`,
|
|
643
|
+
max: `Must be at most ${ruleValue}`,
|
|
644
|
+
range: `Must be between ${Array.isArray(ruleValue) ? ruleValue.join(' and ') : ruleValue}`,
|
|
645
|
+
isCreditCard: 'Invalid credit card number',
|
|
646
|
+
isPostalCode: 'Invalid postal code',
|
|
647
|
+
isMobilePhone: 'Invalid phone number',
|
|
648
|
+
isHexColor: 'Invalid hex color',
|
|
649
|
+
isLowercase: 'Must be lowercase',
|
|
650
|
+
isUppercase: 'Must be uppercase',
|
|
651
|
+
};
|
|
652
|
+
return messages[rule] || `Validation failed for ${field}`;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Evaluate every synchronous rule except `isUnique` (which needs an async
|
|
656
|
+
* database query - see `Validator.isUniqueAsync`). Shared by `validate`,
|
|
657
|
+
* `validateField`, `validateAsync`, and `validateFieldAsync` so the rule
|
|
658
|
+
* dispatch logic only lives in one place.
|
|
659
|
+
*/
|
|
660
|
+
function evaluateSyncRule(rule, value, ruleValue) {
|
|
661
|
+
switch (rule) {
|
|
662
|
+
case 'len':
|
|
663
|
+
return Validator.len(value, ruleValue);
|
|
664
|
+
case 'min':
|
|
665
|
+
return Validator.min(value, ruleValue);
|
|
666
|
+
case 'max':
|
|
667
|
+
return Validator.max(value, ruleValue);
|
|
668
|
+
case 'range':
|
|
669
|
+
return Validator.range(value, ruleValue);
|
|
670
|
+
case 'isIn':
|
|
671
|
+
return Array.isArray(ruleValue) ? Validator.isIn(value, ruleValue) : true;
|
|
672
|
+
case 'notIn':
|
|
673
|
+
return Array.isArray(ruleValue) ? Validator.notIn(value, ruleValue) : true;
|
|
674
|
+
case 'equals':
|
|
675
|
+
return Validator.equals(value, ruleValue);
|
|
676
|
+
case 'notEquals':
|
|
677
|
+
return Validator.notEquals(value, ruleValue);
|
|
678
|
+
case 'matches':
|
|
679
|
+
return Validator.matches(value, ruleValue);
|
|
680
|
+
case 'notMatches':
|
|
681
|
+
return Validator.notMatches(value, ruleValue);
|
|
682
|
+
case 'isUnique':
|
|
683
|
+
// Synchronous fallback only - always passes. Use validateAsync /
|
|
684
|
+
// validateFieldAsync for a real database uniqueness check.
|
|
685
|
+
return Validator.isUnique(value, ruleValue);
|
|
686
|
+
default: {
|
|
687
|
+
const method = validatorMethods[rule];
|
|
688
|
+
return method ? method(value) : true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function shouldSkipRule(ruleValue) {
|
|
693
|
+
return ruleValue === false;
|
|
694
|
+
}
|
|
695
|
+
function getCustomMessage(rule, ruleValue) {
|
|
696
|
+
if (typeof ruleValue === 'string' && rule !== 'isIn' && rule !== 'notIn') {
|
|
697
|
+
return ruleValue;
|
|
698
|
+
}
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Run all validators on data and return validation result
|
|
703
|
+
* @param data - The data object to validate
|
|
704
|
+
* @param validators - The validator configuration
|
|
705
|
+
* @returns Validation result with valid flag and errors array
|
|
706
|
+
*/
|
|
707
|
+
function validate(data, validators) {
|
|
708
|
+
const errors = [];
|
|
709
|
+
for (const [field, rules] of Object.entries(validators)) {
|
|
710
|
+
const value = data[field];
|
|
711
|
+
for (const [rule, ruleValue] of Object.entries(rules)) {
|
|
712
|
+
// Skip if ruleValue is false (validation disabled)
|
|
713
|
+
if (shouldSkipRule(ruleValue)) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const isValid = evaluateSyncRule(rule, value, ruleValue);
|
|
717
|
+
if (!isValid) {
|
|
718
|
+
const customMessage = getCustomMessage(rule, ruleValue);
|
|
719
|
+
const message = getErrorMessage(rule, field, ruleValue, customMessage);
|
|
720
|
+
errors.push({
|
|
721
|
+
message,
|
|
722
|
+
type: 'Validation error',
|
|
723
|
+
path: field,
|
|
724
|
+
value: value,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
valid: errors.length === 0,
|
|
731
|
+
errors,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Validate a single field with given rules
|
|
736
|
+
*/
|
|
737
|
+
function validateField(value, rules) {
|
|
738
|
+
const errors = [];
|
|
739
|
+
for (const [rule, ruleValue] of Object.entries(rules)) {
|
|
740
|
+
if (shouldSkipRule(ruleValue)) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const isValid = evaluateSyncRule(rule, value, ruleValue);
|
|
744
|
+
if (!isValid) {
|
|
745
|
+
const customMessage = getCustomMessage(rule, ruleValue);
|
|
746
|
+
const message = getErrorMessage(rule, 'value', ruleValue, customMessage);
|
|
747
|
+
errors.push({
|
|
748
|
+
message,
|
|
749
|
+
type: 'Validation error',
|
|
750
|
+
path: 'value',
|
|
751
|
+
value: value,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
valid: errors.length === 0,
|
|
757
|
+
errors,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Async counterpart to `validate()` that performs a real database
|
|
762
|
+
* uniqueness check for `isUnique` rules (via `Validator.isUniqueAsync`)
|
|
763
|
+
* instead of always passing.
|
|
764
|
+
*
|
|
765
|
+
* @param data - The data object to validate
|
|
766
|
+
* @param validators - The validator configuration
|
|
767
|
+
* @param context - Optional async context, e.g. `{ exclude: { id: 5 } }`
|
|
768
|
+
* to exclude the current record from the uniqueness lookup on update.
|
|
769
|
+
*/
|
|
770
|
+
async function validateAsync(data, validators, context) {
|
|
771
|
+
const errors = [];
|
|
772
|
+
for (const [field, rules] of Object.entries(validators)) {
|
|
773
|
+
const value = data[field];
|
|
774
|
+
for (const [rule, ruleValue] of Object.entries(rules)) {
|
|
775
|
+
if (shouldSkipRule(ruleValue)) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
let isValid;
|
|
779
|
+
if (rule === 'isUnique') {
|
|
780
|
+
const opts = (typeof ruleValue === 'object' && ruleValue !== null && !Array.isArray(ruleValue)
|
|
781
|
+
? ruleValue
|
|
782
|
+
: {});
|
|
783
|
+
isValid = await Validator.isUniqueAsync(value, {
|
|
784
|
+
model: opts.model,
|
|
785
|
+
field: opts.field ?? field,
|
|
786
|
+
where: opts.where,
|
|
787
|
+
exclude: context?.exclude,
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
isValid = evaluateSyncRule(rule, value, ruleValue);
|
|
792
|
+
}
|
|
793
|
+
if (!isValid) {
|
|
794
|
+
const customMessage = getCustomMessage(rule, ruleValue);
|
|
795
|
+
const message = getErrorMessage(rule, field, ruleValue, customMessage);
|
|
796
|
+
errors.push({
|
|
797
|
+
message,
|
|
798
|
+
type: 'Validation error',
|
|
799
|
+
path: field,
|
|
800
|
+
value: value,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
valid: errors.length === 0,
|
|
807
|
+
errors,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Async counterpart to `validateField()` - see `validateAsync`.
|
|
812
|
+
*/
|
|
813
|
+
async function validateFieldAsync(value, rules, context) {
|
|
814
|
+
const errors = [];
|
|
815
|
+
for (const [rule, ruleValue] of Object.entries(rules)) {
|
|
816
|
+
if (shouldSkipRule(ruleValue)) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
let isValid;
|
|
820
|
+
if (rule === 'isUnique') {
|
|
821
|
+
const opts = (typeof ruleValue === 'object' && ruleValue !== null && !Array.isArray(ruleValue)
|
|
822
|
+
? ruleValue
|
|
823
|
+
: {});
|
|
824
|
+
isValid = await Validator.isUniqueAsync(value, {
|
|
825
|
+
model: opts.model,
|
|
826
|
+
field: opts.field ?? context?.field,
|
|
827
|
+
where: opts.where,
|
|
828
|
+
exclude: context?.exclude,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
isValid = evaluateSyncRule(rule, value, ruleValue);
|
|
833
|
+
}
|
|
834
|
+
if (!isValid) {
|
|
835
|
+
const customMessage = getCustomMessage(rule, ruleValue);
|
|
836
|
+
const message = getErrorMessage(rule, 'value', ruleValue, customMessage);
|
|
837
|
+
errors.push({
|
|
838
|
+
message,
|
|
839
|
+
type: 'Validation error',
|
|
840
|
+
path: 'value',
|
|
841
|
+
value: value,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
valid: errors.length === 0,
|
|
847
|
+
errors,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Create a ValidationError from validation result
|
|
852
|
+
*/
|
|
853
|
+
function createValidationError(result) {
|
|
854
|
+
const error = new ValidationError(result.errors.length > 0 ? result.errors[0].message : 'Validation error', result.errors);
|
|
855
|
+
return error;
|
|
856
|
+
}
|
|
857
|
+
// ==================== DataType Integration ====================
|
|
858
|
+
/**
|
|
859
|
+
* Get validation rules based on DataType
|
|
860
|
+
* Useful for automatic validation based on column type
|
|
861
|
+
*/
|
|
862
|
+
function getValidatorsForDataType(dataType) {
|
|
863
|
+
const validators = {};
|
|
864
|
+
switch (dataType.toUpperCase()) {
|
|
865
|
+
case 'INTEGER':
|
|
866
|
+
case 'BIGINT':
|
|
867
|
+
validators.isInt = true;
|
|
868
|
+
break;
|
|
869
|
+
case 'FLOAT':
|
|
870
|
+
case 'DOUBLE':
|
|
871
|
+
case 'DECIMAL':
|
|
872
|
+
validators.isFloat = true;
|
|
873
|
+
break;
|
|
874
|
+
case 'BOOLEAN':
|
|
875
|
+
validators.isBoolean = true;
|
|
876
|
+
break;
|
|
877
|
+
case 'DATE':
|
|
878
|
+
case 'DATETIME':
|
|
879
|
+
validators.isDate = true;
|
|
880
|
+
break;
|
|
881
|
+
case 'DATEONLY':
|
|
882
|
+
validators.isDateString = true;
|
|
883
|
+
break;
|
|
884
|
+
case 'TIME':
|
|
885
|
+
validators.isTime = true;
|
|
886
|
+
break;
|
|
887
|
+
case 'UUID':
|
|
888
|
+
case 'UUIDV4':
|
|
889
|
+
validators.isUUID = true;
|
|
890
|
+
break;
|
|
891
|
+
case 'JSON':
|
|
892
|
+
case 'JSONB':
|
|
893
|
+
validators.isJSON = true;
|
|
894
|
+
break;
|
|
895
|
+
case 'TEXT':
|
|
896
|
+
case 'VARCHAR':
|
|
897
|
+
case 'CHAR':
|
|
898
|
+
case 'STRING':
|
|
899
|
+
// No specific validators for string types
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
return validators;
|
|
903
|
+
}
|
|
904
|
+
exports.default = {
|
|
905
|
+
Validator,
|
|
906
|
+
ValidationError,
|
|
907
|
+
validate,
|
|
908
|
+
validateField,
|
|
909
|
+
createValidationError,
|
|
910
|
+
getValidatorsForDataType,
|
|
911
|
+
};
|