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,507 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Replica Manager for handling read replicas with lag detection
|
|
4
|
+
* Supports multiple database dialects with dialect-specific lag detection queries
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ReplicaManager = exports.ReplicaRoutingStrategy = void 0;
|
|
41
|
+
const events_1 = require("events");
|
|
42
|
+
/**
|
|
43
|
+
* Routing strategy for selecting a replica.
|
|
44
|
+
*
|
|
45
|
+
* Companion enum to the `RoutingStrategy` string-literal union below.
|
|
46
|
+
* Use either `ReplicaRoutingStrategy.RoundRobin` or the raw string
|
|
47
|
+
* `'round-robin'` (and so on) — both are accepted wherever a
|
|
48
|
+
* `RoutingStrategy` is expected.
|
|
49
|
+
*/
|
|
50
|
+
var ReplicaRoutingStrategy;
|
|
51
|
+
(function (ReplicaRoutingStrategy) {
|
|
52
|
+
ReplicaRoutingStrategy["RoundRobin"] = "round-robin";
|
|
53
|
+
ReplicaRoutingStrategy["LeastConnections"] = "least-connections";
|
|
54
|
+
ReplicaRoutingStrategy["LowestLag"] = "lowest-lag";
|
|
55
|
+
})(ReplicaRoutingStrategy || (exports.ReplicaRoutingStrategy = ReplicaRoutingStrategy = {}));
|
|
56
|
+
/**
|
|
57
|
+
* Replica connection pool wrapper
|
|
58
|
+
*/
|
|
59
|
+
class ReplicaPool {
|
|
60
|
+
constructor(config) {
|
|
61
|
+
this.name = config.name;
|
|
62
|
+
this.config = config;
|
|
63
|
+
}
|
|
64
|
+
async initialize(dialectModule) {
|
|
65
|
+
this.dialectModule = dialectModule;
|
|
66
|
+
// Create dialect and pool based on the replica's configured dialect
|
|
67
|
+
// (not the replica's *name*, which is an arbitrary label a caller could
|
|
68
|
+
// set to anything - e.g. a Postgres replica named "read-1" was previously
|
|
69
|
+
// silently treated as an unsupported dialect).
|
|
70
|
+
const dialectName = this.config.dialect.toLowerCase();
|
|
71
|
+
if (dialectName.includes('mysql') || dialectName.includes('mariadb')) {
|
|
72
|
+
const mysql = await Promise.resolve().then(() => __importStar(require('mysql2/promise')));
|
|
73
|
+
this.pool = mysql.createPool({
|
|
74
|
+
host: this.config.host,
|
|
75
|
+
port: this.config.port,
|
|
76
|
+
user: this.config.username,
|
|
77
|
+
password: this.config.password,
|
|
78
|
+
database: this.config.database,
|
|
79
|
+
waitForConnections: true,
|
|
80
|
+
connectionLimit: this.config.pool?.max || 10,
|
|
81
|
+
queueLimit: 0,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (dialectName.includes('postgres')) {
|
|
85
|
+
const { Pool } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
86
|
+
this.pool = new Pool({
|
|
87
|
+
host: this.config.host,
|
|
88
|
+
port: this.config.port,
|
|
89
|
+
user: this.config.username,
|
|
90
|
+
password: this.config.password,
|
|
91
|
+
database: this.config.database,
|
|
92
|
+
max: this.config.pool?.max || 10,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
getPool() {
|
|
97
|
+
return this.pool;
|
|
98
|
+
}
|
|
99
|
+
getName() {
|
|
100
|
+
return this.name;
|
|
101
|
+
}
|
|
102
|
+
getDialect() {
|
|
103
|
+
return this.config.dialect;
|
|
104
|
+
}
|
|
105
|
+
async getConnection() {
|
|
106
|
+
if (!this.pool) {
|
|
107
|
+
throw new Error(`Pool not initialized for replica ${this.name}`);
|
|
108
|
+
}
|
|
109
|
+
if (this.config.dialect.toLowerCase().includes('mysql') || this.config.dialect.toLowerCase().includes('mariadb')) {
|
|
110
|
+
return this.pool.getConnection();
|
|
111
|
+
}
|
|
112
|
+
else if (this.config.dialect.toLowerCase().includes('postgres')) {
|
|
113
|
+
return this.pool.connect();
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unsupported dialect for replica ${this.name}`);
|
|
116
|
+
}
|
|
117
|
+
async releaseConnection(connection) {
|
|
118
|
+
if (!this.pool)
|
|
119
|
+
return;
|
|
120
|
+
if (this.config.dialect.toLowerCase().includes('mysql') || this.config.dialect.toLowerCase().includes('mariadb')) {
|
|
121
|
+
connection.release();
|
|
122
|
+
}
|
|
123
|
+
else if (this.config.dialect.toLowerCase().includes('postgres')) {
|
|
124
|
+
connection.release();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async close() {
|
|
128
|
+
if (this.pool) {
|
|
129
|
+
await this.pool.end();
|
|
130
|
+
this.pool = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async query(sql, options) {
|
|
134
|
+
const connection = await this.getConnection();
|
|
135
|
+
try {
|
|
136
|
+
if (this.config.dialect.toLowerCase().includes('mysql') || this.config.dialect.toLowerCase().includes('mariadb')) {
|
|
137
|
+
const [rows] = await connection.query(sql);
|
|
138
|
+
return { rows };
|
|
139
|
+
}
|
|
140
|
+
else if (this.config.dialect.toLowerCase().includes('postgres')) {
|
|
141
|
+
const result = await connection.query(sql);
|
|
142
|
+
return { rows: result.rows };
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`Unsupported dialect for replica ${this.name}`);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
this.releaseConnection(connection);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* ReplicaManager class for managing read replicas with lag detection
|
|
153
|
+
*/
|
|
154
|
+
class ReplicaManager extends events_1.EventEmitter {
|
|
155
|
+
/**
|
|
156
|
+
* Create a new ReplicaManager
|
|
157
|
+
*/
|
|
158
|
+
constructor(options) {
|
|
159
|
+
super();
|
|
160
|
+
this.replicas = new Map();
|
|
161
|
+
this.replicaStats = new Map();
|
|
162
|
+
this.roundRobinIndex = 0;
|
|
163
|
+
this.monitorInterval = null;
|
|
164
|
+
this.masterConfig = null;
|
|
165
|
+
this.lagDetectionConfig = {
|
|
166
|
+
enabled: options?.lagDetection?.enabled ?? true,
|
|
167
|
+
interval: options?.lagDetection?.interval ?? 5000,
|
|
168
|
+
timeout: options?.lagDetection?.timeout ?? 5000,
|
|
169
|
+
maxAcceptableLag: options?.lagDetection?.maxAcceptableLag ?? 2000,
|
|
170
|
+
maxFailures: options?.lagDetection?.maxFailures ?? 3,
|
|
171
|
+
};
|
|
172
|
+
this.routingStrategy = options?.defaultRoutingStrategy ?? 'round-robin';
|
|
173
|
+
this.masterConfig = options?.master ?? null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Add a replica to the manager
|
|
177
|
+
*/
|
|
178
|
+
async addReplica(config) {
|
|
179
|
+
if (this.replicas.has(config.name)) {
|
|
180
|
+
throw new Error(`Replica with name ${config.name} already exists`);
|
|
181
|
+
}
|
|
182
|
+
const pool = new ReplicaPool(config);
|
|
183
|
+
pool.initialize({});
|
|
184
|
+
this.replicas.set(config.name, pool);
|
|
185
|
+
// Initialize status
|
|
186
|
+
this.replicaStats.set(config.name, {
|
|
187
|
+
name: config.name,
|
|
188
|
+
healthy: false,
|
|
189
|
+
lag: null,
|
|
190
|
+
activeConnections: 0,
|
|
191
|
+
lastCheck: new Date(),
|
|
192
|
+
});
|
|
193
|
+
this.emit('replica:added', config.name);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Remove a replica from the manager
|
|
197
|
+
*/
|
|
198
|
+
async removeReplica(name) {
|
|
199
|
+
const pool = this.replicas.get(name);
|
|
200
|
+
if (!pool) {
|
|
201
|
+
throw new Error(`Replica ${name} not found`);
|
|
202
|
+
}
|
|
203
|
+
await pool.close();
|
|
204
|
+
this.replicas.delete(name);
|
|
205
|
+
this.replicaStats.delete(name);
|
|
206
|
+
this.emit('replica:removed', name);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get a replica by name
|
|
210
|
+
*/
|
|
211
|
+
getReplica(name) {
|
|
212
|
+
return this.replicas.get(name);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get all replica names
|
|
216
|
+
*/
|
|
217
|
+
getReplicaNames() {
|
|
218
|
+
return Array.from(this.replicas.keys());
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check replication lag for a specific replica
|
|
222
|
+
* Uses dialect-specific queries for lag detection
|
|
223
|
+
*/
|
|
224
|
+
async checkLag(name) {
|
|
225
|
+
const pool = this.replicas.get(name);
|
|
226
|
+
if (!pool) {
|
|
227
|
+
throw new Error(`Replica ${name} not found`);
|
|
228
|
+
}
|
|
229
|
+
const replicaDialect = pool.getDialect().toLowerCase();
|
|
230
|
+
let lagQuery;
|
|
231
|
+
let lag = null;
|
|
232
|
+
try {
|
|
233
|
+
// Determine the lag detection query based on the replica's configured
|
|
234
|
+
// dialect (not its arbitrary display name - see the ReplicaPool fix
|
|
235
|
+
// above for why name-based dialect guessing is unreliable).
|
|
236
|
+
if (replicaDialect.includes('mysql') || replicaDialect.includes('mariadb')) {
|
|
237
|
+
// MySQL/MariaDB: Use SHOW SLAVE STATUS to get Seconds_Behind_Master
|
|
238
|
+
lagQuery = 'SHOW SLAVE STATUS';
|
|
239
|
+
const result = await pool.query(lagQuery);
|
|
240
|
+
if (result.rows && result.rows.length > 0) {
|
|
241
|
+
const slaveStatus = result.rows[0];
|
|
242
|
+
const secondsBehind = slaveStatus.Seconds_Behind_Master;
|
|
243
|
+
lag = secondsBehind !== null && secondsBehind !== undefined
|
|
244
|
+
? Number(secondsBehind) * 1000 // Convert seconds to milliseconds
|
|
245
|
+
: null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (replicaDialect.includes('postgres')) {
|
|
249
|
+
// PostgreSQL: Use pg_last_xact_replay_timestamp()
|
|
250
|
+
lagQuery = "SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) * 1000 AS lag";
|
|
251
|
+
const result = await pool.query(lagQuery);
|
|
252
|
+
if (result.rows && result.rows.length > 0) {
|
|
253
|
+
lag = result.rows[0]?.lag ? Math.round(Number(result.rows[0].lag)) : null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// For other dialects, return null (not supported)
|
|
258
|
+
console.warn(`Lag detection not supported for dialect: ${replicaDialect}`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
// Update stats
|
|
262
|
+
const stats = this.replicaStats.get(name);
|
|
263
|
+
if (stats) {
|
|
264
|
+
stats.lag = lag;
|
|
265
|
+
stats.lastCheck = new Date();
|
|
266
|
+
stats.error = undefined;
|
|
267
|
+
}
|
|
268
|
+
if (lag !== null) {
|
|
269
|
+
this.emit('replica:lag-detected', name, lag);
|
|
270
|
+
}
|
|
271
|
+
return lag;
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
// Update stats with error
|
|
275
|
+
const stats = this.replicaStats.get(name);
|
|
276
|
+
if (stats) {
|
|
277
|
+
stats.lag = null;
|
|
278
|
+
stats.lastCheck = new Date();
|
|
279
|
+
stats.error = error instanceof Error ? error.message : 'Unknown error';
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Start the lag monitoring loop
|
|
286
|
+
*/
|
|
287
|
+
startLagMonitor() {
|
|
288
|
+
if (this.monitorInterval) {
|
|
289
|
+
console.warn('Lag monitor already running');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const checkAllLags = async () => {
|
|
293
|
+
for (const name of Array.from(this.replicas.keys())) {
|
|
294
|
+
try {
|
|
295
|
+
const lag = await this.checkLag(name);
|
|
296
|
+
const stats = this.replicaStats.get(name);
|
|
297
|
+
if (stats) {
|
|
298
|
+
const wasHealthy = stats.healthy;
|
|
299
|
+
stats.healthy = lag !== null && lag <= this.lagDetectionConfig.maxAcceptableLag;
|
|
300
|
+
if (!wasHealthy && stats.healthy) {
|
|
301
|
+
this.emit('replica:healthy', name, stats);
|
|
302
|
+
}
|
|
303
|
+
else if (wasHealthy && !stats.healthy) {
|
|
304
|
+
this.emit('replica:unhealthy', name, stats);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Check if all replicas are unhealthy
|
|
308
|
+
const allStats = Array.from(this.replicaStats.values());
|
|
309
|
+
const allUnhealthy = allStats.every(s => !s.healthy);
|
|
310
|
+
if (allUnhealthy && this.replicas.size > 0) {
|
|
311
|
+
this.emit('all-replicas-unhealthy');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
console.error(`Error checking lag for replica ${name}:`, error);
|
|
316
|
+
// A failed lag check means we can't confirm this replica is caught
|
|
317
|
+
// up - and if the connection itself is down, it can't serve reads
|
|
318
|
+
// either. Mark it unhealthy rather than silently leaving stale
|
|
319
|
+
// (possibly "healthy") stats in place, which would keep routing
|
|
320
|
+
// reads to a replica that's actually unreachable.
|
|
321
|
+
const stats = this.replicaStats.get(name);
|
|
322
|
+
if (stats) {
|
|
323
|
+
const wasHealthy = stats.healthy;
|
|
324
|
+
stats.healthy = false;
|
|
325
|
+
stats.lastCheck = new Date();
|
|
326
|
+
stats.error = error instanceof Error ? error.message : 'Unknown error';
|
|
327
|
+
if (wasHealthy) {
|
|
328
|
+
this.emit('replica:unhealthy', name, stats);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const allStats = Array.from(this.replicaStats.values());
|
|
332
|
+
if (allStats.length > 0 && allStats.every((s) => !s.healthy) && this.replicas.size > 0) {
|
|
333
|
+
this.emit('all-replicas-unhealthy');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
// Run initial check
|
|
339
|
+
checkAllLags();
|
|
340
|
+
// Set up interval
|
|
341
|
+
this.monitorInterval = setInterval(checkAllLags, this.lagDetectionConfig.interval);
|
|
342
|
+
this.emit('monitor:started');
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Stop the lag monitoring loop
|
|
346
|
+
*/
|
|
347
|
+
stopLagMonitor() {
|
|
348
|
+
if (this.monitorInterval) {
|
|
349
|
+
clearInterval(this.monitorInterval);
|
|
350
|
+
this.monitorInterval = null;
|
|
351
|
+
this.emit('monitor:stopped');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get statistics for all replicas
|
|
356
|
+
*/
|
|
357
|
+
getReplicaStats() {
|
|
358
|
+
return new Map(this.replicaStats);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get statistics for a specific replica
|
|
362
|
+
*/
|
|
363
|
+
getReplicaStat(name) {
|
|
364
|
+
return this.replicaStats.get(name);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Set the routing strategy
|
|
368
|
+
*/
|
|
369
|
+
setRoutingStrategy(strategy) {
|
|
370
|
+
this.routingStrategy = strategy;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get the current routing strategy
|
|
374
|
+
*/
|
|
375
|
+
getRoutingStrategy() {
|
|
376
|
+
return this.routingStrategy;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Select a replica based on the current routing strategy
|
|
380
|
+
*/
|
|
381
|
+
async selectReplica() {
|
|
382
|
+
if (this.replicas.size === 0) {
|
|
383
|
+
throw new Error('No replicas available');
|
|
384
|
+
}
|
|
385
|
+
const names = Array.from(this.replicas.keys());
|
|
386
|
+
let selectedName;
|
|
387
|
+
// Round-robin should still skip known-unhealthy replicas where possible -
|
|
388
|
+
// previously it cycled through every name unconditionally, so an
|
|
389
|
+
// unhealthy replica still received its turn in rotation. Fall back to
|
|
390
|
+
// the full name list only if there are no healthy replicas at all (e.g.
|
|
391
|
+
// before the first lag-check cycle has run), so selection never throws.
|
|
392
|
+
const healthyNames = names.filter((name) => this.replicaStats.get(name)?.healthy !== false);
|
|
393
|
+
const roundRobinPool = healthyNames.length > 0 ? healthyNames : names;
|
|
394
|
+
switch (this.routingStrategy) {
|
|
395
|
+
case 'round-robin':
|
|
396
|
+
selectedName = roundRobinPool[this.roundRobinIndex % roundRobinPool.length];
|
|
397
|
+
this.roundRobinIndex++;
|
|
398
|
+
break;
|
|
399
|
+
case 'least-connections':
|
|
400
|
+
let minConnections = Infinity;
|
|
401
|
+
for (const name of names) {
|
|
402
|
+
const stats = this.replicaStats.get(name);
|
|
403
|
+
if (stats && stats.healthy && stats.activeConnections < minConnections) {
|
|
404
|
+
minConnections = stats.activeConnections;
|
|
405
|
+
selectedName = name;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (!selectedName) {
|
|
409
|
+
// Fallback to round-robin if all have same connections or none are healthy
|
|
410
|
+
selectedName = roundRobinPool[this.roundRobinIndex % roundRobinPool.length];
|
|
411
|
+
this.roundRobinIndex++;
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
case 'lowest-lag':
|
|
415
|
+
let minLag = Infinity;
|
|
416
|
+
for (const name of names) {
|
|
417
|
+
const stats = this.replicaStats.get(name);
|
|
418
|
+
if (stats && stats.healthy && stats.lag !== null && stats.lag < minLag) {
|
|
419
|
+
minLag = stats.lag;
|
|
420
|
+
selectedName = name;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (!selectedName) {
|
|
424
|
+
// Fallback to round-robin if all have same lag or none are healthy
|
|
425
|
+
selectedName = roundRobinPool[this.roundRobinIndex % roundRobinPool.length];
|
|
426
|
+
this.roundRobinIndex++;
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
default:
|
|
430
|
+
selectedName = roundRobinPool[this.roundRobinIndex % roundRobinPool.length];
|
|
431
|
+
this.roundRobinIndex++;
|
|
432
|
+
}
|
|
433
|
+
const pool = this.replicas.get(selectedName);
|
|
434
|
+
// Update active connections
|
|
435
|
+
const stats = this.replicaStats.get(selectedName);
|
|
436
|
+
if (stats) {
|
|
437
|
+
stats.activeConnections++;
|
|
438
|
+
}
|
|
439
|
+
this.emit('replica:selected', selectedName);
|
|
440
|
+
return { name: selectedName, pool };
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Execute a query on a replica (auto-selects replica based on routing strategy)
|
|
444
|
+
*/
|
|
445
|
+
async queryReplica(sql, options) {
|
|
446
|
+
const { name, pool } = await this.selectReplica();
|
|
447
|
+
const result = await pool.query(sql, options);
|
|
448
|
+
// Decrease active connections after query completes
|
|
449
|
+
const stats = this.replicaStats.get(name);
|
|
450
|
+
if (stats && stats.activeConnections > 0) {
|
|
451
|
+
stats.activeConnections--;
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get healthy replicas
|
|
457
|
+
*/
|
|
458
|
+
getHealthyReplicas() {
|
|
459
|
+
const healthy = [];
|
|
460
|
+
for (const [name, stats] of Array.from(this.replicaStats.entries())) {
|
|
461
|
+
if (stats.healthy) {
|
|
462
|
+
healthy.push(name);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return healthy;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get replica count
|
|
469
|
+
*/
|
|
470
|
+
getReplicaCount() {
|
|
471
|
+
return this.replicas.size;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Check if replication is enabled
|
|
475
|
+
*/
|
|
476
|
+
isReplicationEnabled() {
|
|
477
|
+
return this.replicas.size > 0;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Set lag detection configuration
|
|
481
|
+
*/
|
|
482
|
+
setLagDetectionConfig(config) {
|
|
483
|
+
this.lagDetectionConfig = {
|
|
484
|
+
...this.lagDetectionConfig,
|
|
485
|
+
...config,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get lag detection configuration
|
|
490
|
+
*/
|
|
491
|
+
getLagDetectionConfig() {
|
|
492
|
+
return { ...this.lagDetectionConfig };
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Close all replica connections
|
|
496
|
+
*/
|
|
497
|
+
async close() {
|
|
498
|
+
this.stopLagMonitor();
|
|
499
|
+
for (const pool of Array.from(this.replicas.values())) {
|
|
500
|
+
await pool.close();
|
|
501
|
+
}
|
|
502
|
+
this.replicas.clear();
|
|
503
|
+
this.replicaStats.clear();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
exports.ReplicaManager = ReplicaManager;
|
|
507
|
+
exports.default = ReplicaManager;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Schema Module - Automated schema diffing and migration generation
|
|
4
|
+
* Exports classes and functions for comparing model definitions against database schema
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createMigrationGenerator = exports.MigrationGenerator = exports.createSchemaDiffer = exports.SchemaDiffer = void 0;
|
|
8
|
+
// Re-export SchemaDiffer
|
|
9
|
+
var schema_differ_1 = require("./schema-differ");
|
|
10
|
+
Object.defineProperty(exports, "SchemaDiffer", { enumerable: true, get: function () { return schema_differ_1.SchemaDiffer; } });
|
|
11
|
+
Object.defineProperty(exports, "createSchemaDiffer", { enumerable: true, get: function () { return schema_differ_1.createSchemaDiffer; } });
|
|
12
|
+
// Re-export MigrationGenerator
|
|
13
|
+
var migration_generator_1 = require("./migration-generator");
|
|
14
|
+
Object.defineProperty(exports, "MigrationGenerator", { enumerable: true, get: function () { return migration_generator_1.MigrationGenerator; } });
|
|
15
|
+
Object.defineProperty(exports, "createMigrationGenerator", { enumerable: true, get: function () { return migration_generator_1.createMigrationGenerator; } });
|