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,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @Check decorator for the ORM — generates CHECK constraints without writing raw SQL.
|
|
4
|
+
*
|
|
5
|
+
* Supports ORM-native operator syntax that gets converted to SQL CHECK expressions.
|
|
6
|
+
*
|
|
7
|
+
* Usage Examples:
|
|
8
|
+
*
|
|
9
|
+
* // Column-level check with ORM operators
|
|
10
|
+
* @Check({ age: { $gte: 18 } })
|
|
11
|
+
* age: number;
|
|
12
|
+
*
|
|
13
|
+
* // Multiple column checks
|
|
14
|
+
* @Check({
|
|
15
|
+
* status: { $in: ['active', 'pending', 'draft'] },
|
|
16
|
+
* email: { $regex: '^[^@]+@[^@]+$' }
|
|
17
|
+
* })
|
|
18
|
+
* email: string;
|
|
19
|
+
*
|
|
20
|
+
* // Custom condition with message
|
|
21
|
+
* @Check({
|
|
22
|
+
* condition: (row) => row.startDate < row.endDate,
|
|
23
|
+
* message: 'Start date must be before end date'
|
|
24
|
+
* })
|
|
25
|
+
* startDate: Date;
|
|
26
|
+
*
|
|
27
|
+
* // Column-level check (explicit column)
|
|
28
|
+
* @CheckColumn('price', { $gte: 0 }, 'Price must be positive')
|
|
29
|
+
* @CheckColumn('quantity', { $gt: 0 }, 'Quantity must be greater than zero')
|
|
30
|
+
*
|
|
31
|
+
* // Table-level check (multiple columns)
|
|
32
|
+
* @CheckTable({
|
|
33
|
+
* condition: (row) => row.quantity * row.price >= row.minimumOrder,
|
|
34
|
+
* message: 'Total must meet minimum order amount'
|
|
35
|
+
* })
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.operatorToSQL = operatorToSQL;
|
|
39
|
+
exports.Check = Check;
|
|
40
|
+
exports.CheckColumn = CheckColumn;
|
|
41
|
+
exports.CheckTable = CheckTable;
|
|
42
|
+
exports.getCheckConstraints = getCheckConstraints;
|
|
43
|
+
exports.getTableCheckConstraints = getTableCheckConstraints;
|
|
44
|
+
exports.getAllCheckConstraints = getAllCheckConstraints;
|
|
45
|
+
exports.buildCheckConstraintSQL = buildCheckConstraintSQL;
|
|
46
|
+
exports.buildAllCheckConstraints = buildAllCheckConstraints;
|
|
47
|
+
exports.clearCheckConstraints = clearCheckConstraints;
|
|
48
|
+
// ==================== Metadata Keys ====================
|
|
49
|
+
const CHECK_CONSTRAINTS_KEY = '__orm_check_constraints__';
|
|
50
|
+
const TABLE_CHECK_CONSTRAINTS_KEY = '__orm_table_check_constraints__';
|
|
51
|
+
// ==================== SQL Generation ====================
|
|
52
|
+
/**
|
|
53
|
+
* Escape SQL string value for use in CHECK constraints
|
|
54
|
+
*/
|
|
55
|
+
function escapeSQLValue(value) {
|
|
56
|
+
if (value === null)
|
|
57
|
+
return 'NULL';
|
|
58
|
+
if (typeof value === 'number')
|
|
59
|
+
return String(value);
|
|
60
|
+
if (typeof value === 'boolean')
|
|
61
|
+
return value ? 'TRUE' : 'FALSE';
|
|
62
|
+
if (typeof value === 'string')
|
|
63
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map((v) => escapeSQLValue(v)).join(', ');
|
|
66
|
+
}
|
|
67
|
+
// For objects, serialize as JSON
|
|
68
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Throw if the given operator is used with a non-Postgres dialect.
|
|
72
|
+
* These operators map directly to PostgreSQL jsonb/array operators
|
|
73
|
+
* (@>, <@, &&) that have no CHECK-constraint-safe equivalent elsewhere.
|
|
74
|
+
*/
|
|
75
|
+
function assertPostgres(dialect, opName) {
|
|
76
|
+
if (dialect !== 'postgres') {
|
|
77
|
+
throw new Error(`Operator "${opName}" generates PostgreSQL-specific jsonb/array syntax and is not supported for the "${dialect}" dialect.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a case-insensitive LIKE expression appropriate for the target dialect.
|
|
82
|
+
*
|
|
83
|
+
* - PostgreSQL: native ILIKE / NOT ILIKE
|
|
84
|
+
* - SQLite: LIKE with COLLATE NOCASE (SQLite has no ILIKE keyword)
|
|
85
|
+
* - MySQL/MariaDB: plain LIKE — the default collation (e.g. utf8mb4_general_ci)
|
|
86
|
+
* is already case-insensitive, so no special-casing is required
|
|
87
|
+
* - MSSQL: plain LIKE — default (non-binary) collations are case-insensitive
|
|
88
|
+
* - Oracle: case-sensitive by default, so wrap both sides in UPPER()
|
|
89
|
+
*/
|
|
90
|
+
function buildCaseInsensitiveLike(quotedField, value, dialect, negate) {
|
|
91
|
+
const escapedValue = escapeSQLValue(value);
|
|
92
|
+
switch (dialect) {
|
|
93
|
+
case 'postgres':
|
|
94
|
+
return `${quotedField} ${negate ? 'NOT ILIKE' : 'ILIKE'} ${escapedValue}`;
|
|
95
|
+
case 'sqlite':
|
|
96
|
+
return `${quotedField} ${negate ? 'NOT LIKE' : 'LIKE'} ${escapedValue} COLLATE NOCASE`;
|
|
97
|
+
case 'oracle':
|
|
98
|
+
return `UPPER(${quotedField}) ${negate ? 'NOT LIKE' : 'LIKE'} UPPER(${escapedValue})`;
|
|
99
|
+
case 'mysql':
|
|
100
|
+
case 'mariadb':
|
|
101
|
+
case 'mssql':
|
|
102
|
+
default:
|
|
103
|
+
// Case-insensitive by default collation on these dialects
|
|
104
|
+
return `${quotedField} ${negate ? 'NOT LIKE' : 'LIKE'} ${escapedValue}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build a regex-match expression appropriate for the target dialect.
|
|
109
|
+
*
|
|
110
|
+
* - PostgreSQL: ~ / !~ (case-sensitive), ~* / !~* (case-insensitive)
|
|
111
|
+
* - MySQL/MariaDB: REGEXP / NOT REGEXP (MySQL's REGEXP is case-insensitive
|
|
112
|
+
* by default under the standard case-insensitive collations, so it is
|
|
113
|
+
* used for both $regex and $iRegexp)
|
|
114
|
+
* - Oracle: REGEXP_LIKE(field, pattern[, 'i']) function, wrapped with NOT for negation
|
|
115
|
+
* - SQLite: no REGEXP function is registered by this ORM's SQLite dialect
|
|
116
|
+
* (see src/dialects/sqlite/index.ts), so regex CHECK constraints would
|
|
117
|
+
* fail at runtime — throw a clear error instead of emitting invalid SQL
|
|
118
|
+
* - MSSQL: has no native regex support in T-SQL — throw a clear error
|
|
119
|
+
*/
|
|
120
|
+
function buildRegexExpression(quotedField, value, dialect, opts) {
|
|
121
|
+
const escapedValue = escapeSQLValue(value);
|
|
122
|
+
const { negate, ci } = opts;
|
|
123
|
+
switch (dialect) {
|
|
124
|
+
case 'postgres': {
|
|
125
|
+
const op = ci ? (negate ? '!~*' : '~*') : negate ? '!~' : '~';
|
|
126
|
+
return `${quotedField} ${op} ${escapedValue}`;
|
|
127
|
+
}
|
|
128
|
+
case 'mysql':
|
|
129
|
+
case 'mariadb': {
|
|
130
|
+
const op = negate ? 'NOT REGEXP' : 'REGEXP';
|
|
131
|
+
return `${quotedField} ${op} ${escapedValue}`;
|
|
132
|
+
}
|
|
133
|
+
case 'oracle': {
|
|
134
|
+
const modifier = ci ? `, 'i'` : '';
|
|
135
|
+
const expr = `REGEXP_LIKE(${quotedField}, ${escapedValue}${modifier})`;
|
|
136
|
+
return negate ? `NOT ${expr}` : expr;
|
|
137
|
+
}
|
|
138
|
+
case 'sqlite':
|
|
139
|
+
throw new Error('Regex CHECK constraints are not supported for the "sqlite" dialect: no REGEXP function is registered.');
|
|
140
|
+
case 'mssql':
|
|
141
|
+
default:
|
|
142
|
+
throw new Error(`Regex CHECK constraints are not supported for the "${dialect}" dialect.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Convert ORM operator to SQL CHECK expression
|
|
147
|
+
* @param fieldName - The column name
|
|
148
|
+
* @param operator - The ORM operator object
|
|
149
|
+
* @param dialect - The target SQL dialect (defaults to 'postgres' for backwards compatibility)
|
|
150
|
+
* @returns SQL expression string
|
|
151
|
+
*/
|
|
152
|
+
function operatorToSQL(fieldName, operator, dialect = 'postgres') {
|
|
153
|
+
const opKeys = Object.keys(operator);
|
|
154
|
+
const opKey = opKeys[0];
|
|
155
|
+
const value = operator[opKey];
|
|
156
|
+
// Wrap field name in quotes for safety
|
|
157
|
+
const quotedField = `"${fieldName}"`;
|
|
158
|
+
switch (opKey) {
|
|
159
|
+
case '$eq':
|
|
160
|
+
return `${quotedField} = ${escapeSQLValue(value)}`;
|
|
161
|
+
case '$ne':
|
|
162
|
+
return `${quotedField} <> ${escapeSQLValue(value)}`;
|
|
163
|
+
case '$gt':
|
|
164
|
+
return `${quotedField} > ${escapeSQLValue(value)}`;
|
|
165
|
+
case '$gte':
|
|
166
|
+
return `${quotedField} >= ${escapeSQLValue(value)}`;
|
|
167
|
+
case '$lt':
|
|
168
|
+
return `${quotedField} < ${escapeSQLValue(value)}`;
|
|
169
|
+
case '$lte':
|
|
170
|
+
return `${quotedField} <= ${escapeSQLValue(value)}`;
|
|
171
|
+
case '$in':
|
|
172
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
173
|
+
throw new Error('$in operator requires a non-empty array');
|
|
174
|
+
}
|
|
175
|
+
return `${quotedField} IN (${value.map((v) => escapeSQLValue(v)).join(', ')})`;
|
|
176
|
+
case '$notIn':
|
|
177
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
178
|
+
throw new Error('$notIn operator requires a non-empty array');
|
|
179
|
+
}
|
|
180
|
+
return `${quotedField} NOT IN (${value.map((v) => escapeSQLValue(v)).join(', ')})`;
|
|
181
|
+
case '$between':
|
|
182
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
183
|
+
throw new Error('$between operator requires an array of exactly 2 values');
|
|
184
|
+
}
|
|
185
|
+
return `${quotedField} BETWEEN ${escapeSQLValue(value[0])} AND ${escapeSQLValue(value[1])}`;
|
|
186
|
+
case '$notBetween':
|
|
187
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
188
|
+
throw new Error('$notBetween operator requires an array of exactly 2 values');
|
|
189
|
+
}
|
|
190
|
+
return `${quotedField} NOT BETWEEN ${escapeSQLValue(value[0])} AND ${escapeSQLValue(value[1])}`;
|
|
191
|
+
case '$like':
|
|
192
|
+
return `${quotedField} LIKE ${escapeSQLValue(value)}`;
|
|
193
|
+
case '$notLike':
|
|
194
|
+
return `${quotedField} NOT LIKE ${escapeSQLValue(value)}`;
|
|
195
|
+
case '$iLike':
|
|
196
|
+
return buildCaseInsensitiveLike(quotedField, value, dialect, false);
|
|
197
|
+
case '$notILike':
|
|
198
|
+
return buildCaseInsensitiveLike(quotedField, value, dialect, true);
|
|
199
|
+
case '$regex':
|
|
200
|
+
return buildRegexExpression(quotedField, value, dialect, { negate: false, ci: false });
|
|
201
|
+
case '$notRegexp':
|
|
202
|
+
return buildRegexExpression(quotedField, value, dialect, { negate: true, ci: false });
|
|
203
|
+
case '$iRegexp':
|
|
204
|
+
return buildRegexExpression(quotedField, value, dialect, { negate: false, ci: true });
|
|
205
|
+
case '$notIRegexp':
|
|
206
|
+
return buildRegexExpression(quotedField, value, dialect, { negate: true, ci: true });
|
|
207
|
+
case '$isNull':
|
|
208
|
+
return `${quotedField} IS NULL`;
|
|
209
|
+
case '$isNotNull':
|
|
210
|
+
return `${quotedField} IS NOT NULL`;
|
|
211
|
+
case '$contains':
|
|
212
|
+
// PostgreSQL jsonb/array containment operator — no equivalent elsewhere
|
|
213
|
+
assertPostgres(dialect, '$contains');
|
|
214
|
+
return `${quotedField} @> ${escapeSQLValue(value)}`;
|
|
215
|
+
case '$containedBy':
|
|
216
|
+
// PostgreSQL jsonb/array containment operator — no equivalent elsewhere
|
|
217
|
+
assertPostgres(dialect, '$containedBy');
|
|
218
|
+
return `${quotedField} <@ ${escapeSQLValue(value)}`;
|
|
219
|
+
case '$overlap':
|
|
220
|
+
// PostgreSQL array overlap operator — no equivalent elsewhere
|
|
221
|
+
assertPostgres(dialect, '$overlap');
|
|
222
|
+
return `${quotedField} && ${escapeSQLValue(value)}`;
|
|
223
|
+
default:
|
|
224
|
+
throw new Error(`Unsupported operator: ${opKey}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Generate a CHECK constraint name
|
|
229
|
+
*/
|
|
230
|
+
function generateCheckName(tableName, fieldName) {
|
|
231
|
+
const timestamp = Date.now().toString(36).slice(-6);
|
|
232
|
+
if (fieldName) {
|
|
233
|
+
return `chk_${tableName}_${fieldName}_${timestamp}`;
|
|
234
|
+
}
|
|
235
|
+
return `chk_${tableName}_table_${timestamp}`;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Parse condition function to SQL (simple implementation)
|
|
239
|
+
* This handles common patterns for row-level checks
|
|
240
|
+
*/
|
|
241
|
+
function conditionToSQL(condition, paramNames) {
|
|
242
|
+
// Get the function source code
|
|
243
|
+
const funcStr = condition.toString();
|
|
244
|
+
// Extract parameter names if not provided
|
|
245
|
+
const params = paramNames.length > 0 ? paramNames : ['row'];
|
|
246
|
+
// Simple pattern matching for common expressions
|
|
247
|
+
// This is a basic implementation - complex conditions may need manual SQL
|
|
248
|
+
// Handle simple comparisons: row.a < row.b
|
|
249
|
+
const simpleCompare = funcStr.match(/row\.(\w+)\s*([<>]=?)\s*row\.(\w+)/);
|
|
250
|
+
if (simpleCompare) {
|
|
251
|
+
return `"${simpleCompare[1]}" ${simpleCompare[2]} "${simpleCompare[3]}"`;
|
|
252
|
+
}
|
|
253
|
+
// Handle arithmetic: row.a * row.b >= row.c
|
|
254
|
+
const arithmeticCompare = funcStr.match(/row\.(\w+)\s*([*\/+-])\s*row\.(\w+)\s*(>=?|<=?|==|!=)\s*row\.(\w+)/);
|
|
255
|
+
if (arithmeticCompare) {
|
|
256
|
+
return `"${arithmeticCompare[1]}" ${arithmeticCompare[2]} "${arithmeticCompare[3]}" ${arithmeticCompare[4]} "${arithmeticCompare[5]}"`;
|
|
257
|
+
}
|
|
258
|
+
// Handle: row.field < value (constant)
|
|
259
|
+
const fieldValueCompare = funcStr.match(/row\.(\w+)\s*(>=?|<=?|==|!=)\s*(\d+)/);
|
|
260
|
+
if (fieldValueCompare) {
|
|
261
|
+
return `"${fieldValueCompare[1]}" ${fieldValueCompare[2]} ${fieldValueCompare[3]}`;
|
|
262
|
+
}
|
|
263
|
+
// Handle: value < row.field (constant)
|
|
264
|
+
const valueFieldCompare = funcStr.match(/(\d+)\s*(>=?|<=?|==|!=)\s*row\.(\w+)/);
|
|
265
|
+
if (valueFieldCompare) {
|
|
266
|
+
return `${valueFieldCompare[1]} ${valueFieldCompare[2]} "${valueFieldCompare[3]}"`;
|
|
267
|
+
}
|
|
268
|
+
// Fallback: for complex conditions, throw an error suggesting manual SQL
|
|
269
|
+
throw new Error('Complex CHECK conditions require manual SQL. Use @ColumnConstraint with type: "check" and explicit check string.');
|
|
270
|
+
}
|
|
271
|
+
// ==================== Decorators ====================
|
|
272
|
+
/**
|
|
273
|
+
* @Check decorator — attach CHECK constraints to a column or multiple columns.
|
|
274
|
+
*
|
|
275
|
+
* For column-level use (on class property):
|
|
276
|
+
* @Check({ age: { $gte: 18 } })
|
|
277
|
+
* age: number;
|
|
278
|
+
*
|
|
279
|
+
* For multiple checks:
|
|
280
|
+
* @Check({
|
|
281
|
+
* status: { $in: ['active', 'pending'] },
|
|
282
|
+
* email: { $regex: '^[^@]+@[^@]+$' }
|
|
283
|
+
* })
|
|
284
|
+
* email: string;
|
|
285
|
+
*
|
|
286
|
+
* For row-level condition:
|
|
287
|
+
* @Check({
|
|
288
|
+
* condition: (row) => row.startDate < row.endDate,
|
|
289
|
+
* message: 'Start date must be before end date'
|
|
290
|
+
* })
|
|
291
|
+
* startDate: Date;
|
|
292
|
+
*/
|
|
293
|
+
function Check(options) {
|
|
294
|
+
return (target, propertyKey) => {
|
|
295
|
+
const fieldName = String(propertyKey);
|
|
296
|
+
if (!target[CHECK_CONSTRAINTS_KEY]) {
|
|
297
|
+
target[CHECK_CONSTRAINTS_KEY] = [];
|
|
298
|
+
}
|
|
299
|
+
const constraints = target[CHECK_CONSTRAINTS_KEY];
|
|
300
|
+
// Check if this is a row-level condition (condition function)
|
|
301
|
+
if ('condition' in options) {
|
|
302
|
+
const rowCheck = options;
|
|
303
|
+
constraints.push({
|
|
304
|
+
type: 'table',
|
|
305
|
+
name: '',
|
|
306
|
+
condition: rowCheck.condition,
|
|
307
|
+
message: rowCheck.message,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// Field-level checks
|
|
312
|
+
for (const [field, operator] of Object.entries(options)) {
|
|
313
|
+
if ((typeof operator === 'object' && '$eq' in operator) || '$gte' in operator) {
|
|
314
|
+
// Skip if it's a special property, not a field check
|
|
315
|
+
if (field === 'condition' || field === 'message')
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (typeof operator === 'object' && operator !== null) {
|
|
319
|
+
constraints.push({
|
|
320
|
+
type: 'column',
|
|
321
|
+
name: '',
|
|
322
|
+
field: field,
|
|
323
|
+
operator: operator,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* @CheckColumn decorator — explicit column-level CHECK constraint.
|
|
332
|
+
*
|
|
333
|
+
* @CheckColumn('price', { $gte: 0 }, 'Price must be positive')
|
|
334
|
+
* price: number;
|
|
335
|
+
*/
|
|
336
|
+
function CheckColumn(column, operator, message) {
|
|
337
|
+
return (target, propertyKey) => {
|
|
338
|
+
if (!target[CHECK_CONSTRAINTS_KEY]) {
|
|
339
|
+
target[CHECK_CONSTRAINTS_KEY] = [];
|
|
340
|
+
}
|
|
341
|
+
target[CHECK_CONSTRAINTS_KEY].push({
|
|
342
|
+
type: 'column',
|
|
343
|
+
name: '',
|
|
344
|
+
field: column,
|
|
345
|
+
operator,
|
|
346
|
+
message,
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* @CheckTable decorator — table-level CHECK constraint with multi-column conditions.
|
|
352
|
+
*
|
|
353
|
+
* @CheckTable({
|
|
354
|
+
* condition: (row) => row.quantity * row.price >= row.minimumOrder,
|
|
355
|
+
* message: 'Total must meet minimum order amount'
|
|
356
|
+
* })
|
|
357
|
+
*/
|
|
358
|
+
function CheckTable(options) {
|
|
359
|
+
return (target) => {
|
|
360
|
+
if (!target[TABLE_CHECK_CONSTRAINTS_KEY]) {
|
|
361
|
+
target[TABLE_CHECK_CONSTRAINTS_KEY] = [];
|
|
362
|
+
}
|
|
363
|
+
target[TABLE_CHECK_CONSTRAINTS_KEY].push({
|
|
364
|
+
type: 'table',
|
|
365
|
+
name: options.name || '',
|
|
366
|
+
condition: options.condition,
|
|
367
|
+
message: options.message,
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
// ==================== Helpers ====================
|
|
372
|
+
/**
|
|
373
|
+
* Get all CHECK constraints from a model class or instance
|
|
374
|
+
*/
|
|
375
|
+
function getCheckConstraints(target) {
|
|
376
|
+
return target[CHECK_CONSTRAINTS_KEY] || [];
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get all table-level CHECK constraints from a model class
|
|
380
|
+
*/
|
|
381
|
+
function getTableCheckConstraints(target) {
|
|
382
|
+
return target[TABLE_CHECK_CONSTRAINTS_KEY] || [];
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get all CHECK constraints (both column and table level)
|
|
386
|
+
*/
|
|
387
|
+
function getAllCheckConstraints(target) {
|
|
388
|
+
return [...getCheckConstraints(target), ...getTableCheckConstraints(target)];
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Build CREATE TABLE CHECK constraint SQL
|
|
392
|
+
*/
|
|
393
|
+
function buildCheckConstraintSQL(tableName, constraint, dialect = 'postgres') {
|
|
394
|
+
const name = constraint.name || generateCheckName(tableName, constraint.field);
|
|
395
|
+
if (constraint.type === 'column' && constraint.operator && constraint.field) {
|
|
396
|
+
const sqlExpr = operatorToSQL(constraint.field, constraint.operator, dialect);
|
|
397
|
+
return `CONSTRAINT "${name}" CHECK (${sqlExpr})`;
|
|
398
|
+
}
|
|
399
|
+
if (constraint.type === 'table' && constraint.condition) {
|
|
400
|
+
// Extract field names from the model
|
|
401
|
+
const fields = extractFieldNames(constraint.condition);
|
|
402
|
+
const sqlExpr = conditionToSQL(constraint.condition, fields);
|
|
403
|
+
return `CONSTRAINT "${name}" CHECK (${sqlExpr})`;
|
|
404
|
+
}
|
|
405
|
+
// If sql is already provided (manual SQL), use it directly
|
|
406
|
+
if (constraint.sql) {
|
|
407
|
+
return `CONSTRAINT "${name}" CHECK (${constraint.sql})`;
|
|
408
|
+
}
|
|
409
|
+
throw new Error('Invalid check constraint: missing operator or condition');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Extract parameter names from a condition function
|
|
413
|
+
*/
|
|
414
|
+
function extractFieldNames(condition) {
|
|
415
|
+
const funcStr = condition.toString();
|
|
416
|
+
const fieldMatches = funcStr.match(/row\.(\w+)/g);
|
|
417
|
+
if (!fieldMatches)
|
|
418
|
+
return [];
|
|
419
|
+
return [...new Set(fieldMatches.map((m) => m.replace('row.', '')))];
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Build all CHECK constraints for a table
|
|
423
|
+
*/
|
|
424
|
+
function buildAllCheckConstraints(tableName, constraints, dialect = 'postgres') {
|
|
425
|
+
return constraints
|
|
426
|
+
.filter((c) => c.type === 'column' || c.type === 'table')
|
|
427
|
+
.map((c) => buildCheckConstraintSQL(tableName, c, dialect));
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Clear CHECK constraints (useful for testing)
|
|
431
|
+
*/
|
|
432
|
+
function clearCheckConstraints(target) {
|
|
433
|
+
delete target[CHECK_CONSTRAINTS_KEY];
|
|
434
|
+
delete target[TABLE_CHECK_CONSTRAINTS_KEY];
|
|
435
|
+
}
|