ts-prorm-orm 1.1.0 → 1.2.1
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 +95 -0
- package/README.md +194 -72
- package/dist/audit/history-query.d.ts +35 -0
- package/dist/audit/index.d.ts +16 -0
- package/dist/audit/logger.d.ts +126 -0
- package/dist/cache/cache-manager.d.ts +341 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/redis-cluster-cache.d.ts +164 -0
- package/dist/cache/redis-cluster-cache.js +19 -36
- package/dist/cli/cloud.d.ts +55 -0
- package/dist/cli/cloud.js +318 -0
- package/dist/cli/commands/auth.d.ts +2 -0
- package/dist/cli/commands/auth.js +345 -0
- package/dist/cli/commands/billing.d.ts +2 -0
- package/dist/cli/commands/billing.js +135 -0
- package/dist/cli/commands/code.d.ts +2 -0
- package/dist/cli/commands/code.js +168 -0
- package/dist/cli/commands/config.d.ts +2 -0
- package/dist/cli/commands/config.js +154 -0
- package/dist/cli/commands/db.d.ts +2 -0
- package/dist/cli/commands/db.js +189 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +22 -0
- package/dist/cli/commands/keys.d.ts +2 -0
- package/dist/cli/commands/keys.js +100 -0
- package/dist/cli/commands/misc.d.ts +2 -0
- package/dist/cli/commands/misc.js +282 -0
- package/dist/cli/commands/pull.d.ts +2 -0
- package/dist/cli/commands/pull.js +306 -0
- package/dist/cli/commands/runners.d.ts +2 -0
- package/dist/cli/commands/runners.js +218 -0
- package/dist/cli/commands/scaffold.d.ts +2 -0
- package/dist/cli/commands/scaffold.js +383 -0
- package/dist/cli/commands/secrets.d.ts +2 -0
- package/dist/cli/commands/secrets.js +162 -0
- package/dist/cli/commands/store.d.ts +2 -0
- package/dist/cli/commands/store.js +274 -0
- package/dist/cli/commands/system.d.ts +10 -0
- package/dist/cli/commands/system.js +169 -0
- package/dist/cli/commands/teams.d.ts +2 -0
- package/dist/cli/commands/teams.js +189 -0
- package/dist/cli/commands/user.d.ts +2 -0
- package/dist/cli/commands/user.js +171 -0
- package/dist/cli/framework.d.ts +103 -0
- package/dist/cli/framework.js +436 -0
- package/dist/cli/io.d.ts +26 -0
- package/dist/cli/io.js +181 -0
- package/dist/cli/keychain.d.ts +10 -0
- package/dist/cli/keychain.js +101 -0
- package/dist/cli/prompt.d.ts +20 -0
- package/dist/cli/prompt.js +111 -0
- package/dist/cli.d.ts +214 -0
- package/dist/cli.js +72 -5
- package/dist/compliance/audit-trail.d.ts +66 -0
- package/dist/compliance/backup-verification.d.ts +279 -0
- package/dist/compliance/breach-detector.d.ts +197 -0
- package/dist/compliance/consent-record.d.ts +107 -0
- package/dist/compliance/consent-versioning.d.ts +169 -0
- package/dist/compliance/cross-border-log.d.ts +254 -0
- package/dist/compliance/data-classifier.d.ts +127 -0
- package/dist/compliance/data-lineage.d.ts +268 -0
- package/dist/compliance/data-masker.d.ts +110 -0
- package/dist/compliance/data-portability.d.ts +98 -0
- package/dist/compliance/data-retention.d.ts +108 -0
- package/dist/compliance/dsar-workflow.d.ts +175 -0
- package/dist/compliance/field-encryption.d.ts +84 -0
- package/dist/compliance/immutable-record.d.ts +84 -0
- package/dist/compliance/index.d.ts +93 -0
- package/dist/compliance/privacy-impact-assessment.d.ts +196 -0
- package/dist/compliance/pseudonymization.d.ts +96 -0
- package/dist/compliance/query-firewall.d.ts +661 -0
- package/dist/compliance/rate-limiter.d.ts +203 -0
- package/dist/compliance/right-to-erasure.d.ts +96 -0
- package/dist/compliance/row-level-security.d.ts +152 -0
- package/dist/compliance/security-decorator.d.ts +388 -0
- package/dist/compliance/security-monitor.d.ts +170 -0
- package/dist/compliance/sensitive-data-discovery.d.ts +151 -0
- package/dist/compliance/session-isolation.d.ts +286 -0
- package/dist/compliance/tls-enforcer.d.ts +51 -0
- package/dist/compliance/worm-storage.d.ts +225 -0
- package/dist/connection-manager.d.ts +130 -0
- package/dist/connection-pool.d.ts +311 -0
- package/dist/decorators/audit.d.ts +49 -0
- package/dist/decorators/check.d.ts +218 -0
- package/dist/decorators/collate.d.ts +346 -0
- package/dist/decorators/comment.d.ts +51 -0
- package/dist/decorators/database-settings.d.ts +193 -0
- package/dist/decorators/default.d.ts +148 -0
- package/dist/decorators/encryption.d.ts +175 -0
- package/dist/decorators/engine.d.ts +97 -0
- package/dist/decorators/fk-constraints.d.ts +387 -0
- package/dist/decorators/foreign-table.d.ts +57 -0
- package/dist/decorators/generated.d.ts +232 -0
- package/dist/decorators/hstore.d.ts +96 -0
- package/dist/decorators/index.d.ts +57 -0
- package/dist/decorators/index.js +14 -30
- package/dist/decorators/json-column.d.ts +47 -0
- package/dist/decorators/jsonb.d.ts +83 -0
- package/dist/decorators/orm-decorators.d.ts +274 -0
- package/dist/decorators/permissions.d.ts +192 -0
- package/dist/decorators/procedure.d.ts +168 -0
- package/dist/decorators/procedure.js +31 -7
- package/dist/decorators/query-options.d.ts +527 -0
- package/dist/decorators/range.d.ts +122 -0
- package/dist/decorators/set-column.d.ts +43 -0
- package/dist/decorators/spatial.d.ts +60 -0
- package/dist/decorators/storage.d.ts +561 -0
- package/dist/decorators/timezone.d.ts +319 -0
- package/dist/decorators/trigger.d.ts +50 -0
- package/dist/decorators/uuid.d.ts +102 -0
- package/dist/decorators/view.d.ts +213 -0
- package/dist/diagrams/chen-diagram.d.ts +70 -0
- package/dist/diagrams/chen-diagram.js +33 -31
- package/dist/diagrams/class-diagram.d.ts +78 -0
- package/dist/diagrams/class-diagram.js +23 -27
- package/dist/diagrams/dependency-diagram.d.ts +82 -0
- package/dist/diagrams/dependency-diagram.js +18 -9
- package/dist/diagrams/er-diagram.d.ts +82 -0
- package/dist/diagrams/er-diagram.js +174 -231
- package/dist/diagrams/flow-diagram.d.ts +70 -0
- package/dist/diagrams/flow-diagram.js +23 -18
- package/dist/diagrams/gantt-diagram.d.ts +67 -0
- package/dist/diagrams/gantt-diagram.js +20 -18
- package/dist/diagrams/index-diagram.d.ts +61 -0
- package/dist/diagrams/index-diagram.js +36 -31
- package/dist/diagrams/index.d.ts +155 -0
- package/dist/diagrams/index.js +7 -1
- package/dist/diagrams/migration-diagram.d.ts +67 -0
- package/dist/diagrams/migration-diagram.js +45 -57
- package/dist/diagrams/model-diagram.d.ts +85 -0
- package/dist/diagrams/model-diagram.js +225 -287
- package/dist/diagrams/package-diagram.d.ts +69 -0
- package/dist/diagrams/package-diagram.js +30 -32
- package/dist/diagrams/palette.d.ts +138 -0
- package/dist/diagrams/palette.js +134 -25
- package/dist/diagrams/relational-diagram.d.ts +82 -0
- package/dist/diagrams/relational-diagram.js +51 -59
- package/dist/diagrams/schemadoc-diagram.d.ts +57 -0
- package/dist/diagrams/schemadoc-diagram.js +36 -28
- package/dist/diagrams/sequence-diagram.d.ts +61 -0
- package/dist/diagrams/sequence-diagram.js +20 -11
- package/dist/diagrams/state-diagram.d.ts +85 -0
- package/dist/diagrams/state-diagram.js +21 -17
- package/dist/diagrams/svg-dom.d.ts +62 -0
- package/dist/diagrams/svg-dom.js +11 -0
- package/dist/diagrams/tree-diagram.d.ts +47 -0
- package/dist/diagrams/tree-diagram.js +19 -10
- package/dist/dialects/clickhouse/index.d.ts +660 -0
- package/dist/dialects/clickhouse/index.js +107 -2
- package/dist/dialects/cockroachdb/index.d.ts +1708 -0
- package/dist/dialects/cockroachdb/index.js +139 -10
- package/dist/dialects/cratedb/index.d.ts +262 -0
- package/dist/dialects/cratedb/index.js +148 -0
- package/dist/dialects/databricks/index.d.ts +397 -0
- package/dist/dialects/databricks/index.js +233 -11
- package/dist/dialects/db2/index.d.ts +729 -0
- package/dist/dialects/db2/index.js +152 -2
- package/dist/dialects/dialect.d.ts +1653 -0
- package/dist/dialects/duckdb/index.d.ts +440 -0
- package/dist/dialects/duckdb/index.js +21 -37
- package/dist/dialects/exasol/index.d.ts +197 -0
- package/dist/dialects/exasol/index.js +14 -0
- package/dist/dialects/firebird/index.d.ts +286 -0
- package/dist/dialects/firebird/index.js +135 -0
- package/dist/dialects/greenplum/index.d.ts +336 -0
- package/dist/dialects/greenplum/index.js +168 -0
- package/dist/dialects/hana/index.d.ts +512 -0
- package/dist/dialects/hana/index.js +17 -35
- package/dist/dialects/mariadb/index.d.ts +1241 -0
- package/dist/dialects/mariadb/index.js +181 -43
- package/dist/dialects/mssql/index.d.ts +729 -0
- package/dist/dialects/mssql/index.js +76 -7
- package/dist/dialects/mysql/index.d.ts +1233 -0
- package/dist/dialects/mysql/index.js +87 -19
- package/dist/dialects/oracle/index.d.ts +1222 -0
- package/dist/dialects/oracle/index.js +210 -9
- package/dist/dialects/postgres/index.d.ts +1598 -0
- package/dist/dialects/postgres/index.js +34 -13
- package/dist/dialects/query-stream-helper.d.ts +58 -0
- package/dist/dialects/questdb/index.d.ts +430 -0
- package/dist/dialects/questdb/index.js +292 -0
- package/dist/dialects/redshift/index.d.ts +933 -0
- package/dist/dialects/redshift/index.js +17 -5
- package/dist/dialects/singlestore/index.d.ts +233 -0
- package/dist/dialects/singlestore/index.js +53 -0
- package/dist/dialects/snowflake/index.d.ts +727 -0
- package/dist/dialects/snowflake/index.js +154 -5
- package/dist/dialects/spanner/index.d.ts +660 -0
- package/dist/dialects/spanner/index.js +43 -21
- package/dist/dialects/sqlite/index.d.ts +1133 -0
- package/dist/dialects/sqlite/index.js +168 -30
- package/dist/dialects/tidb/index.d.ts +226 -0
- package/dist/dialects/tidb/index.js +10 -0
- package/dist/dialects/timescaledb/index.d.ts +286 -0
- package/dist/dialects/timescaledb/index.js +179 -0
- package/dist/dialects/trino/index.d.ts +230 -0
- package/dist/dialects/trino/index.js +85 -10
- package/dist/dialects/turso/index.d.ts +98 -0
- package/dist/dialects/turso/index.js +10 -0
- package/dist/dialects/vertica/index.d.ts +321 -0
- package/dist/dialects/vertica/index.js +161 -0
- package/dist/dialects/yugabytedb/index.d.ts +276 -0
- package/dist/dialects/yugabytedb/index.js +121 -0
- package/dist/errors/index.d.ts +378 -0
- package/dist/errors/index.js +29 -1
- package/dist/errors/utils.d.ts +53 -0
- package/dist/errors.d.ts +5 -0
- package/dist/extensions/catalog/cloud-warehouse-features.d.ts +27 -0
- package/dist/extensions/catalog/mssql-features.d.ts +20 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.d.ts +18 -0
- package/dist/extensions/catalog/oracle-db2-features.d.ts +17 -0
- package/dist/extensions/catalog/postgres-extensions.d.ts +15 -0
- package/dist/extensions/index.d.ts +34 -0
- package/dist/extensions/types.d.ts +43 -0
- package/dist/external-fields/adapters.d.ts +50 -0
- package/dist/external-fields/adapters.js +227 -0
- package/dist/external-fields/decorator.d.ts +77 -0
- package/dist/external-fields/decorator.js +80 -0
- package/dist/external-fields/index.d.ts +10 -0
- package/dist/external-fields/index.js +19 -0
- package/dist/external-fields/runtime.d.ts +39 -0
- package/dist/external-fields/runtime.js +221 -0
- package/dist/foreign-data.d.ts +168 -0
- package/dist/graph/base-graph-dialect.d.ts +250 -0
- package/dist/graph/base-graph-dialect.js +539 -0
- package/dist/graph/dgraph/index.d.ts +65 -0
- package/dist/graph/dgraph/index.js +559 -0
- package/dist/graph/gremlin/index.d.ts +59 -0
- package/dist/graph/gremlin/index.js +378 -0
- package/dist/graph/index.d.ts +60 -0
- package/dist/graph/index.js +67 -0
- package/dist/graph/neo4j/index.d.ts +50 -0
- package/dist/graph/neo4j/index.js +462 -0
- package/dist/graph/types.d.ts +213 -0
- package/dist/graph/types.js +234 -0
- package/dist/hooks/hooks-manager.d.ts +189 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/index.d.ts +713 -0
- package/dist/index.js +36 -4
- package/dist/logging.d.ts +183 -0
- package/dist/migrations/index.d.ts +9 -0
- package/dist/migrations/migration.d.ts +143 -0
- package/dist/migrations/migrator.d.ts +199 -0
- package/dist/migrations/prormmigration.d.ts +131 -0
- package/dist/migrations/query-interface.d.ts +335 -0
- package/dist/migrations/seeder.d.ts +167 -0
- package/dist/models/associations.d.ts +131 -0
- package/dist/models/associations.js +7 -2
- package/dist/models/constraints.d.ts +175 -0
- package/dist/models/data-types.d.ts +1656 -0
- package/dist/models/decorators.d.ts +328 -0
- package/dist/models/decorators.js +61 -82
- package/dist/models/eager-load.d.ts +86 -0
- package/dist/models/eager-load.js +369 -0
- package/dist/models/index.d.ts +13 -0
- package/dist/models/indexes.d.ts +325 -0
- package/dist/models/methods.d.ts +419 -0
- package/dist/models/model-manager.d.ts +76 -0
- package/dist/models/model.d.ts +820 -0
- package/dist/models/model.js +15 -101
- package/dist/models/operators.d.ts +29 -0
- package/dist/models/scopes.d.ts +71 -0
- package/dist/models/typescript-types.d.ts +177 -0
- package/dist/nosql/aerospike/index.d.ts +126 -0
- package/dist/nosql/allegrograph/index.d.ts +86 -0
- package/dist/nosql/arangodb/index.d.ts +285 -0
- package/dist/nosql/arangodb/index.js +274 -2
- package/dist/nosql/arangodb/where.d.ts +38 -0
- package/dist/nosql/arangodb/where.js +94 -0
- package/dist/nosql/azure-blob/index.d.ts +104 -0
- package/dist/nosql/beanstalkd/index.d.ts +88 -0
- package/dist/nosql/beequeue/index.d.ts +99 -0
- package/dist/nosql/bigchaindb/index.d.ts +113 -0
- package/dist/nosql/bigtable/index.d.ts +144 -0
- package/dist/nosql/blazegraph/index.d.ts +88 -0
- package/dist/nosql/bullmq/index.d.ts +83 -0
- package/dist/nosql/cassandra/index.d.ts +119 -0
- package/dist/nosql/chroma/index.d.ts +151 -0
- package/dist/nosql/cloudflare-kv/index.d.ts +120 -0
- package/dist/nosql/coherence/index.d.ts +91 -0
- package/dist/nosql/cosmosdb/index.d.ts +78 -0
- package/dist/nosql/couchbase/index.d.ts +103 -0
- package/dist/nosql/dax/index.d.ts +118 -0
- package/dist/nosql/deno-kv/index.d.ts +109 -0
- package/dist/nosql/dgraph/index.d.ts +80 -0
- package/dist/nosql/doris/index.d.ts +88 -0
- package/dist/nosql/druid/index.d.ts +95 -0
- package/dist/nosql/dynamodb/index.d.ts +643 -0
- package/dist/nosql/dynamodb/index.js +423 -0
- package/dist/nosql/elasticsearch/index.d.ts +239 -0
- package/dist/nosql/etcd/index.d.ts +243 -0
- package/dist/nosql/eventhubs/index.d.ts +89 -0
- package/dist/nosql/eventstore/index.d.ts +102 -0
- package/dist/nosql/faunadb/index.d.ts +141 -0
- package/dist/nosql/faunadb/index.js +329 -0
- package/dist/nosql/firestore/index.d.ts +67 -0
- package/dist/nosql/fluree/index.d.ts +88 -0
- package/dist/nosql/fuseki/index.d.ts +94 -0
- package/dist/nosql/gcs/index.d.ts +62 -0
- package/dist/nosql/gearman/index.d.ts +77 -0
- package/dist/nosql/geode/index.d.ts +93 -0
- package/dist/nosql/graphdb/index.d.ts +86 -0
- package/dist/nosql/graylog/index.d.ts +118 -0
- package/dist/nosql/gridgain/index.d.ts +78 -0
- package/dist/nosql/hazelcast/index.d.ts +73 -0
- package/dist/nosql/hbase/index.d.ts +123 -0
- package/dist/nosql/ignite/index.d.ts +80 -0
- package/dist/nosql/immudb/index.d.ts +131 -0
- package/dist/nosql/index.d.ts +215 -0
- package/dist/nosql/infinispan/index.d.ts +95 -0
- package/dist/nosql/influxdb/flux-compiler.d.ts +23 -0
- package/dist/nosql/influxdb/flux-compiler.js +170 -0
- package/dist/nosql/influxdb/index.d.ts +300 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.d.ts +113 -0
- package/dist/nosql/keyspaces/index.d.ts +122 -0
- package/dist/nosql/kinesis/index.d.ts +121 -0
- package/dist/nosql/leveldb/index.d.ts +112 -0
- package/dist/nosql/leveldb/index.js +103 -1
- package/dist/nosql/lmdb/index.d.ts +76 -0
- package/dist/nosql/loki/index.d.ts +126 -0
- package/dist/nosql/marklogic/index.d.ts +76 -0
- package/dist/nosql/materialize/index.d.ts +91 -0
- package/dist/nosql/meilisearch/index.d.ts +76 -0
- package/dist/nosql/memcached/index.d.ts +138 -0
- package/dist/nosql/milvus/index.d.ts +167 -0
- package/dist/nosql/minio/index.d.ts +122 -0
- package/dist/nosql/momento/index.d.ts +99 -0
- package/dist/nosql/mongodb/index.d.ts +265 -0
- package/dist/nosql/mongodb/index.js +139 -0
- package/dist/nosql/mongodb/where.d.ts +8 -0
- package/dist/nosql/mongodb/where.js +181 -0
- package/dist/nosql/nats/index.d.ts +139 -0
- package/dist/nosql/nedb/index.d.ts +79 -0
- package/dist/nosql/neo4j/index.d.ts +217 -0
- package/dist/nosql/neo4j/index.js +26 -14
- package/dist/nosql/neptune/index.d.ts +234 -0
- package/dist/nosql/neptune/index.js +23 -9
- package/dist/nosql/nsq/index.d.ts +93 -0
- package/dist/nosql/opensearch/index.d.ts +97 -0
- package/dist/nosql/orientdb/index.d.ts +72 -0
- package/dist/nosql/papertrail/index.d.ts +120 -0
- package/dist/nosql/pinecone/index.d.ts +148 -0
- package/dist/nosql/pinot/index.d.ts +90 -0
- package/dist/nosql/pouchdb/index.d.ts +95 -0
- package/dist/nosql/prometheus/index.d.ts +112 -0
- package/dist/nosql/provendb/index.d.ts +143 -0
- package/dist/nosql/provendb/index.js +262 -0
- package/dist/nosql/pubsub/index.d.ts +83 -0
- package/dist/nosql/pulsar/index.d.ts +105 -0
- package/dist/nosql/qdrant/index.d.ts +231 -0
- package/dist/nosql/qldb/index.d.ts +182 -0
- package/dist/nosql/qldb/index.js +302 -1
- package/dist/nosql/r2/index.d.ts +160 -0
- package/dist/nosql/rabbitmq/index.d.ts +131 -0
- package/dist/nosql/ravendb/index.d.ts +138 -0
- package/dist/nosql/ravendb/index.js +323 -9
- package/dist/nosql/ravendb/where.d.ts +38 -0
- package/dist/nosql/ravendb/where.js +94 -0
- package/dist/nosql/redis/index.d.ts +267 -0
- package/dist/nosql/redis/index.js +19 -5
- package/dist/nosql/redpanda/index.d.ts +117 -0
- package/dist/nosql/resque/index.d.ts +91 -0
- package/dist/nosql/rethinkdb/index.d.ts +194 -0
- package/dist/nosql/rethinkdb/index.js +290 -0
- package/dist/nosql/rethinkdb/where.d.ts +38 -0
- package/dist/nosql/rethinkdb/where.js +94 -0
- package/dist/nosql/rocksdb/document-layer.d.ts +162 -0
- package/dist/nosql/rocksdb/document-layer.js +338 -0
- package/dist/nosql/rocksdb/index.d.ts +112 -0
- package/dist/nosql/rocksdb/index.js +103 -1
- package/dist/nosql/rocksdb/where-match.d.ts +21 -0
- package/dist/nosql/rocksdb/where-match.js +238 -0
- package/dist/nosql/rockset/index.d.ts +84 -0
- package/dist/nosql/s3/index.d.ts +170 -0
- package/dist/nosql/scylladb/index.d.ts +117 -0
- package/dist/nosql/signoz/index.d.ts +116 -0
- package/dist/nosql/sns/index.d.ts +99 -0
- package/dist/nosql/solr/index.d.ts +129 -0
- package/dist/nosql/splunk/index.d.ts +125 -0
- package/dist/nosql/sqs/index.d.ts +116 -0
- package/dist/nosql/stardog/index.d.ts +86 -0
- package/dist/nosql/starrocks/index.d.ts +89 -0
- package/dist/nosql/store.d.ts +23 -0
- package/dist/nosql/sumologic/index.d.ts +119 -0
- package/dist/nosql/surrealdb/index.d.ts +156 -0
- package/dist/nosql/surrealdb/index.js +174 -1
- package/dist/nosql/surrealdb/query-compiler.d.ts +26 -0
- package/dist/nosql/surrealdb/query-compiler.js +193 -0
- package/dist/nosql/terminusdb/index.d.ts +95 -0
- package/dist/nosql/tigergraph/index.d.ts +314 -0
- package/dist/nosql/typesense/index.d.ts +82 -0
- package/dist/nosql/unqlite/index.d.ts +68 -0
- package/dist/nosql/upstash/index.d.ts +80 -0
- package/dist/nosql/vercel-kv/index.d.ts +82 -0
- package/dist/nosql/victoriametrics/index.d.ts +129 -0
- package/dist/nosql/virtuoso/index.d.ts +87 -0
- package/dist/nosql/weaviate/index.d.ts +196 -0
- package/dist/operators/index.d.ts +7 -0
- package/dist/operators.d.ts +2947 -0
- package/dist/operators.js +46 -1
- package/dist/prisma-migrate/index.d.ts +28 -0
- package/dist/prisma-migrate/migration-generator.d.ts +21 -0
- package/dist/prisma-migrate/model-generator.d.ts +12 -0
- package/dist/prisma-migrate/relations.d.ts +28 -0
- package/dist/prisma-migrate/schema-parser.d.ts +42 -0
- package/dist/prisma-migrate/type-mapper.d.ts +20 -0
- package/dist/prorm.d.ts +2093 -0
- package/dist/prorm.js +721 -302
- package/dist/query-builders/cte-builder.d.ts +104 -0
- package/dist/query-builders/functions/aggregate.d.ts +199 -0
- package/dist/query-builders/functions/conditional.d.ts +195 -0
- package/dist/query-builders/functions/datetime.d.ts +202 -0
- package/dist/query-builders/functions/fulltext.d.ts +220 -0
- package/dist/query-builders/functions/index.d.ts +17 -0
- package/dist/query-builders/functions/json.d.ts +157 -0
- package/dist/query-builders/functions/math.d.ts +215 -0
- package/dist/query-builders/functions/string.d.ts +233 -0
- package/dist/query-builders/functions/window.d.ts +209 -0
- package/dist/query-builders/include-builder.d.ts +85 -0
- package/dist/query-builders/index-expression-builder.d.ts +153 -0
- package/dist/query-builders/index.d.ts +121 -0
- package/dist/query-builders/index.js +10 -0
- package/dist/query-builders/insert-builder.d.ts +62 -0
- package/dist/query-builders/model-helpers.d.ts +28 -0
- package/dist/query-builders/order-limit-builder.d.ts +84 -0
- package/dist/query-builders/sql-compiler.d.ts +562 -0
- package/dist/query-builders/subquery-builder.d.ts +133 -0
- package/dist/query-builders/update-builder.d.ts +71 -0
- package/dist/query-builders/view-builder.d.ts +189 -0
- package/dist/query-builders/where-builder.d.ts +124 -0
- package/dist/query-interface.d.ts +407 -0
- package/dist/query-interface.js +84 -0
- package/dist/query-optimizers/batch-optimizer.d.ts +175 -0
- package/dist/query-optimizers/explain-plans.d.ts +187 -0
- package/dist/query-optimizers/index.d.ts +27 -0
- package/dist/query-optimizers/prepared-statement-cache.d.ts +252 -0
- package/dist/query-optimizers/query-hints.d.ts +201 -0
- package/dist/query-optimizers/query-optimizer.d.ts +162 -0
- package/dist/query-optimizers/slow-query-logger.d.ts +186 -0
- package/dist/replica-manager.d.ts +235 -0
- package/dist/schema/index.d.ts +7 -0
- package/dist/schema/migration-generator.d.ts +74 -0
- package/dist/schema/schema-differ.d.ts +114 -0
- package/dist/schema/types.d.ts +246 -0
- package/dist/schema-objects.d.ts +78 -0
- package/dist/schema-objects.js +203 -0
- package/dist/sql-constants.d.ts +240 -0
- package/dist/sqlite-advanced.d.ts +572 -0
- package/dist/streams/index.d.ts +5 -0
- package/dist/streams/transforms.d.ts +120 -0
- package/dist/transaction.d.ts +221 -0
- package/dist/types/index.d.ts +3123 -0
- package/dist/user-management.d.ts +141 -0
- package/dist/utils/date.d.ts +149 -0
- package/dist/utils/index.d.ts +359 -0
- package/dist/utils/string.d.ts +88 -0
- package/dist/validators/index.d.ts +7 -0
- package/dist/validators/validator.d.ts +306 -0
- package/package.json +20 -5
package/dist/prorm.js
CHANGED
|
@@ -60,7 +60,13 @@ const hana_1 = require("./dialects/hana");
|
|
|
60
60
|
const spanner_1 = require("./dialects/spanner");
|
|
61
61
|
const connection_pool_1 = require("./connection-pool");
|
|
62
62
|
const models_1 = require("./models");
|
|
63
|
+
const eager_load_1 = require("./models/eager-load");
|
|
63
64
|
const decorators_1 = require("./models/decorators");
|
|
65
|
+
const view_1 = require("./decorators/view");
|
|
66
|
+
const view_2 = require("./decorators/view");
|
|
67
|
+
const trigger_1 = require("./decorators/trigger");
|
|
68
|
+
const procedure_1 = require("./decorators/procedure");
|
|
69
|
+
const external_fields_1 = require("./external-fields");
|
|
64
70
|
const logging_1 = require("./logging");
|
|
65
71
|
const utils_1 = require("./errors/utils");
|
|
66
72
|
const validator_1 = require("./validators/validator");
|
|
@@ -455,6 +461,67 @@ function resolveModelPrimaryKeyAttr(m) {
|
|
|
455
461
|
}
|
|
456
462
|
return 'id';
|
|
457
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Derive a model's table name from its name and options.
|
|
466
|
+
*
|
|
467
|
+
* This is the single definition of that rule. It previously existed in six
|
|
468
|
+
* places, two of which (`syncCreate` and the table-name resolver) omitted the
|
|
469
|
+
* pluralization entirely and returned the bare model name - so `define('User')`
|
|
470
|
+
* followed by a plain `sync()` looked for a table called `User`, never found the
|
|
471
|
+
* `users` table that `define()` had already created, and created a second empty
|
|
472
|
+
* table beside it on every call. `syncForce`/`syncAlter` used the correct form,
|
|
473
|
+
* which is why the bug was invisible to anyone testing with `{ force: true }`.
|
|
474
|
+
*/
|
|
475
|
+
function deriveTableName(modelName, options) {
|
|
476
|
+
if (options?.tableName)
|
|
477
|
+
return options.tableName;
|
|
478
|
+
return options?.freezeTableName ? modelName : (0, utils_2.pluralize)(modelName.toLowerCase());
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Resolve the column type a synthesized foreign key should use so that it
|
|
482
|
+
* matches the column it references.
|
|
483
|
+
*
|
|
484
|
+
* A foreign key column must be type-compatible with its referenced key. This
|
|
485
|
+
* used to be hardcoded to INTEGER, which meant any model with a UUID or STRING
|
|
486
|
+
* primary key produced a child column that Postgres rejects outright at CREATE
|
|
487
|
+
* TABLE (`foreign key constraint cannot be implemented`) and SQLite accepts
|
|
488
|
+
* while silently coercing the stored values.
|
|
489
|
+
*
|
|
490
|
+
* Auto-increment types are mapped to their referenceable equivalent - a column
|
|
491
|
+
* pointing at a SERIAL key is an INTEGER, not another SERIAL.
|
|
492
|
+
*/
|
|
493
|
+
function resolveForeignKeyColumnType(refModel, refField) {
|
|
494
|
+
const attrs = refModel?.rawAttributes || refModel?.attributes;
|
|
495
|
+
const attr = attrs?.[refField];
|
|
496
|
+
// Unwrap the several shapes an attribute can take: `{ type: X }`, a DataType
|
|
497
|
+
// instance, or a bare factory function.
|
|
498
|
+
let type = attr && typeof attr === 'object' && 'type' in attr ? attr.type : attr;
|
|
499
|
+
if (typeof type === 'function') {
|
|
500
|
+
try {
|
|
501
|
+
const built = type();
|
|
502
|
+
if (built && typeof built === 'object')
|
|
503
|
+
type = built;
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
type = undefined;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const key = String(type?.key ?? type?.toString?.() ?? type ?? '').toUpperCase();
|
|
510
|
+
if (!key || key === 'UNDEFINED') {
|
|
511
|
+
return 'INTEGER';
|
|
512
|
+
}
|
|
513
|
+
// A reference to an auto-incrementing key is a plain integer of matching width.
|
|
514
|
+
if (key.startsWith('BIGSERIAL'))
|
|
515
|
+
return 'BIGINT';
|
|
516
|
+
if (key.startsWith('SMALLSERIAL'))
|
|
517
|
+
return 'SMALLINT';
|
|
518
|
+
if (key.startsWith('SERIAL'))
|
|
519
|
+
return 'INTEGER';
|
|
520
|
+
if (attr?.autoIncrement && key.startsWith('INTEGER'))
|
|
521
|
+
return 'INTEGER';
|
|
522
|
+
// Otherwise mirror the referenced column's own type (UUID, STRING(n), BIGINT, ...).
|
|
523
|
+
return (type ?? 'INTEGER');
|
|
524
|
+
}
|
|
458
525
|
// Tracks (via Node's AsyncLocalStorage, which correctly follows a logical
|
|
459
526
|
// call chain across `await`s regardless of how concurrent operations
|
|
460
527
|
// interleave) whether the currently-executing code is running inside an
|
|
@@ -556,6 +623,20 @@ class Prorm extends events_1.EventEmitter {
|
|
|
556
623
|
this._rootTransactionQueue = Promise.resolve();
|
|
557
624
|
this.modelDefinitions = new Map();
|
|
558
625
|
this.associations = new Map();
|
|
626
|
+
/**
|
|
627
|
+
* Decorator-declared associations waiting for their target model to be
|
|
628
|
+
* registered. See `_flushPendingAssociations()`.
|
|
629
|
+
*/
|
|
630
|
+
this._pendingAssociations = [];
|
|
631
|
+
/** Model classes registered via `addModel`, consulted for @View/@Trigger DDL. */
|
|
632
|
+
this._decoratedClasses = [];
|
|
633
|
+
/** Classes registered via `registerProcedures`, for @Procedure/@SqlFunction DDL. */
|
|
634
|
+
this._procedureClasses = [];
|
|
635
|
+
/**
|
|
636
|
+
* Object/key-value stores backing @ExternalField properties. Public so the
|
|
637
|
+
* instance accessors built in `_wrapInstance` can resolve them.
|
|
638
|
+
*/
|
|
639
|
+
this.externalStores = new external_fields_1.ExternalStoreRegistry();
|
|
559
640
|
// Tracks the fire-and-forget auto-create-table promises kicked off by
|
|
560
641
|
// `define()` (see below). `sync()` awaits these before doing its own
|
|
561
642
|
// drop/create pass so that a `define()` call followed synchronously by
|
|
@@ -1401,7 +1482,7 @@ class Prorm extends events_1.EventEmitter {
|
|
|
1401
1482
|
}
|
|
1402
1483
|
throw new Error(`Model '${modelName}' not found`);
|
|
1403
1484
|
}
|
|
1404
|
-
const tableName = modelDef.options
|
|
1485
|
+
const tableName = deriveTableName(modelName, modelDef.options);
|
|
1405
1486
|
const schema = modelDef.options.schema;
|
|
1406
1487
|
const schemaDelimiter = modelDef.options.schemaDelimiter || '.';
|
|
1407
1488
|
return schema ? `${schema}${schemaDelimiter}${tableName}` : tableName;
|
|
@@ -1549,7 +1630,7 @@ class Prorm extends events_1.EventEmitter {
|
|
|
1549
1630
|
// Store the model definition
|
|
1550
1631
|
this.modelDefinitions.set(modelName, { attributes, options });
|
|
1551
1632
|
// Determine table name and schema
|
|
1552
|
-
const tableName =
|
|
1633
|
+
const tableName = deriveTableName(modelName, options);
|
|
1553
1634
|
const schema = options.schema;
|
|
1554
1635
|
const schemaDelimiter = options.schemaDelimiter || '.';
|
|
1555
1636
|
// Build the full table name with schema (e.g., "schema.tableName" or "database.table")
|
|
@@ -1871,6 +1952,14 @@ class Prorm extends events_1.EventEmitter {
|
|
|
1871
1952
|
'save',
|
|
1872
1953
|
'destroy',
|
|
1873
1954
|
'isNewRecord',
|
|
1955
|
+
// Bookkeeping properties, not columns. These are enumerable own
|
|
1956
|
+
// properties of the instance, so without them here the sweep below
|
|
1957
|
+
// put them in the UPDATE and every
|
|
1958
|
+
// `instance.field = x; await instance.save()` failed with
|
|
1959
|
+
// "no such column: _previousDataValues". Any future underscore-
|
|
1960
|
+
// prefixed internal is excluded by the check in the loop too.
|
|
1961
|
+
'_previousDataValues',
|
|
1962
|
+
'_changed',
|
|
1874
1963
|
]);
|
|
1875
1964
|
if (pk !== undefined) {
|
|
1876
1965
|
const saveVals = {};
|
|
@@ -1878,6 +1967,8 @@ class Prorm extends events_1.EventEmitter {
|
|
|
1878
1967
|
for (const k of Object.keys(rowInstance)) {
|
|
1879
1968
|
if (reserved.has(k))
|
|
1880
1969
|
continue;
|
|
1970
|
+
if (k.startsWith('_'))
|
|
1971
|
+
continue; // internal bookkeeping, not a column
|
|
1881
1972
|
if (typeof rowInstance[k] === 'function')
|
|
1882
1973
|
continue;
|
|
1883
1974
|
if (k !== 'id') {
|
|
@@ -1885,8 +1976,11 @@ class Prorm extends events_1.EventEmitter {
|
|
|
1885
1976
|
dataValues[k] = rowInstance[k];
|
|
1886
1977
|
}
|
|
1887
1978
|
}
|
|
1888
|
-
// Also include current dataValues (minus id, minus virtual fields
|
|
1979
|
+
// Also include current dataValues (minus id, minus virtual fields,
|
|
1980
|
+
// minus @ExternalField payloads which live in a store, not a column)
|
|
1889
1981
|
for (const [k, v] of Object.entries(dataValues)) {
|
|
1982
|
+
if (rowInstance.__externalFields?.has(k))
|
|
1983
|
+
continue;
|
|
1890
1984
|
if (k !== 'id' && !reserved.has(k)) {
|
|
1891
1985
|
const attr = attributes[k];
|
|
1892
1986
|
if (attr) {
|
|
@@ -2162,7 +2256,10 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2162
2256
|
const sourceMatch = assoc.source === modelRef || assoc.source?.name === modelName;
|
|
2163
2257
|
if (!sourceMatch)
|
|
2164
2258
|
continue;
|
|
2165
|
-
|
|
2259
|
+
// Prefer the association's resolved alias, which is what the
|
|
2260
|
+
// eager-loader attaches results under, so `user.getPosts()` and
|
|
2261
|
+
// `include: [Post]` agree on the name.
|
|
2262
|
+
const assocAlias = assoc.as || assoc.options?.as || assoc.target?.name || '';
|
|
2166
2263
|
const singularAlias = assocAlias.endsWith('s') ? assocAlias.slice(0, -1) : assocAlias;
|
|
2167
2264
|
const pluralAlias = assocAlias.endsWith('s') ? assocAlias : assocAlias + 's';
|
|
2168
2265
|
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -2172,7 +2269,13 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2172
2269
|
const { where: optWhere, ...rest } = opts || {};
|
|
2173
2270
|
return assoc.target.findAll({
|
|
2174
2271
|
...rest,
|
|
2175
|
-
|
|
2272
|
+
// An association `scope` applies every time the association is
|
|
2273
|
+
// traversed; a caller-supplied where can narrow it further.
|
|
2274
|
+
where: {
|
|
2275
|
+
...(assoc.options?.scope || {}),
|
|
2276
|
+
[fk]: dataValues.id,
|
|
2277
|
+
...(optWhere || {}),
|
|
2278
|
+
},
|
|
2176
2279
|
});
|
|
2177
2280
|
};
|
|
2178
2281
|
rowInstance[`add${capitalize(singularAlias)}`] = async (instance) => {
|
|
@@ -2239,16 +2342,56 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2239
2342
|
rowInstance[fk] = targetId;
|
|
2240
2343
|
await modelRef.update({ [fk]: targetId }, { where: { id: dataValues.id } });
|
|
2241
2344
|
};
|
|
2345
|
+
// create/has/count were missing here while the equivalent mixins in
|
|
2346
|
+
// models/associations.ts defined them, so which helpers existed on an
|
|
2347
|
+
// instance depended on how the model had been defined.
|
|
2348
|
+
rowInstance[`create${capitalize(assocAlias)}`] = async (values, options) => {
|
|
2349
|
+
const created = await assoc.target.create(values, options);
|
|
2350
|
+
const targetId = created?.id ?? created;
|
|
2351
|
+
dataValues[fk] = targetId;
|
|
2352
|
+
rowInstance[fk] = targetId;
|
|
2353
|
+
await modelRef.update({ [fk]: targetId }, { where: { id: dataValues.id } });
|
|
2354
|
+
return created;
|
|
2355
|
+
};
|
|
2356
|
+
rowInstance[`has${capitalize(assocAlias)}`] = async () => {
|
|
2357
|
+
const fkVal = dataValues[fk];
|
|
2358
|
+
if (fkVal === null || fkVal === undefined)
|
|
2359
|
+
return false;
|
|
2360
|
+
return (await assoc.target.count({ where: { id: fkVal } })) > 0;
|
|
2361
|
+
};
|
|
2362
|
+
rowInstance[`count${capitalize(assocAlias)}`] = async () => {
|
|
2363
|
+
const fkVal = dataValues[fk];
|
|
2364
|
+
if (fkVal === null || fkVal === undefined)
|
|
2365
|
+
return 0;
|
|
2366
|
+
return assoc.target.count({ where: { id: fkVal } });
|
|
2367
|
+
};
|
|
2242
2368
|
}
|
|
2243
2369
|
else if (assoc.type === 'hasOne') {
|
|
2244
2370
|
const fk = assoc.foreignKey;
|
|
2245
2371
|
rowInstance[`get${capitalize(assocAlias)}`] = async (opts) => {
|
|
2246
|
-
|
|
2372
|
+
const { where: optWhere, ...rest } = opts || {};
|
|
2373
|
+
return assoc.target.findOne({
|
|
2374
|
+
...rest,
|
|
2375
|
+
where: {
|
|
2376
|
+
...(assoc.options?.scope || {}),
|
|
2377
|
+
[fk]: dataValues.id,
|
|
2378
|
+
...(optWhere || {}),
|
|
2379
|
+
},
|
|
2380
|
+
});
|
|
2247
2381
|
};
|
|
2248
2382
|
rowInstance[`set${capitalize(assocAlias)}`] = async (instance) => {
|
|
2249
2383
|
const targetId = instance?.id ?? instance;
|
|
2250
2384
|
await assoc.target.update({ [fk]: dataValues.id }, { where: { id: targetId } });
|
|
2251
2385
|
};
|
|
2386
|
+
rowInstance[`create${capitalize(assocAlias)}`] = async (values, options) => {
|
|
2387
|
+
return assoc.target.create({ ...values, [fk]: dataValues.id }, options);
|
|
2388
|
+
};
|
|
2389
|
+
rowInstance[`has${capitalize(assocAlias)}`] = async () => {
|
|
2390
|
+
return (await assoc.target.count({ where: { [fk]: dataValues.id } })) > 0;
|
|
2391
|
+
};
|
|
2392
|
+
rowInstance[`count${capitalize(assocAlias)}`] = async () => {
|
|
2393
|
+
return assoc.target.count({ where: { [fk]: dataValues.id } });
|
|
2394
|
+
};
|
|
2252
2395
|
}
|
|
2253
2396
|
else if (assoc.type === 'belongsToMany') {
|
|
2254
2397
|
const fk = assoc.options?.foreignKey || assoc.foreignKey;
|
|
@@ -2375,12 +2518,24 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2375
2518
|
return true;
|
|
2376
2519
|
}
|
|
2377
2520
|
}
|
|
2521
|
+
// @ExternalField payloads are not columns - route the assignment to
|
|
2522
|
+
// the field's own accessor (which records it for the next save) and
|
|
2523
|
+
// keep it out of dataValues, which save() sweeps into the UPDATE.
|
|
2524
|
+
if (target.__externalFields?.has(prop)) {
|
|
2525
|
+
target[prop] = value;
|
|
2526
|
+
return true;
|
|
2527
|
+
}
|
|
2378
2528
|
// Regular field
|
|
2379
2529
|
dataValues[prop] = value;
|
|
2380
2530
|
target[prop] = value;
|
|
2381
2531
|
return true;
|
|
2382
2532
|
},
|
|
2383
2533
|
});
|
|
2534
|
+
// Attach @ExternalField accessors. These are defined on the underlying
|
|
2535
|
+
// rowInstance (not the proxy) as non-enumerable properties, so save()'s
|
|
2536
|
+
// sweep over the instance's own keys never tries to persist a payload
|
|
2537
|
+
// into a column that doesn't exist - only the key column is written.
|
|
2538
|
+
(0, external_fields_1.attachExternalFields)(rowInstance, dataValues, modelName, fullTableName, self.externalStores);
|
|
2384
2539
|
return proxyInstance;
|
|
2385
2540
|
};
|
|
2386
2541
|
const model = {
|
|
@@ -2713,6 +2868,32 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2713
2868
|
[deletedAt]: { $isNull: true },
|
|
2714
2869
|
};
|
|
2715
2870
|
}
|
|
2871
|
+
// Apply INNER-JOIN semantics for filtering includes.
|
|
2872
|
+
//
|
|
2873
|
+
// `include: [{ model: Post, required: true }]` and an include carrying
|
|
2874
|
+
// its own `where` are documented to restrict the parent rows. That was
|
|
2875
|
+
// never implemented - `required` was read by no live code path, so these
|
|
2876
|
+
// queries returned every parent regardless. Resolve the matching parent
|
|
2877
|
+
// keys first and constrain the main query with them, so the restriction
|
|
2878
|
+
// is applied before LIMIT/OFFSET rather than after.
|
|
2879
|
+
if (findOptions.include && findOptions.include.length > 0) {
|
|
2880
|
+
for (const rawInclude of findOptions.include) {
|
|
2881
|
+
const filter = await (0, eager_load_1.resolveRequiredIncludeFilter)(self.eagerLoadContext(), model, modelName, rawInclude);
|
|
2882
|
+
if (!filter)
|
|
2883
|
+
continue;
|
|
2884
|
+
if (filter.values.length === 0) {
|
|
2885
|
+
// No parent can satisfy a required include with no matches.
|
|
2886
|
+
return [];
|
|
2887
|
+
}
|
|
2888
|
+
const existing = where?.[filter.parentAttr];
|
|
2889
|
+
where = {
|
|
2890
|
+
...where,
|
|
2891
|
+
[filter.parentAttr]: existing
|
|
2892
|
+
? { $and: [existing, { $in: filter.values }] }
|
|
2893
|
+
: { $in: filter.values },
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2716
2897
|
// Translate attributes.exclude to explicit list (SQLite doesn't support "* EXCEPT(...)")
|
|
2717
2898
|
let resolvedAttributes = findOptions.attributes;
|
|
2718
2899
|
if (resolvedAttributes &&
|
|
@@ -2791,232 +2972,12 @@ class Prorm extends events_1.EventEmitter {
|
|
|
2791
2972
|
const wrappedRows = result.rows.map((row) => {
|
|
2792
2973
|
return _wrapInstance(row, modelRef, modelHooksRef);
|
|
2793
2974
|
});
|
|
2794
|
-
//
|
|
2795
|
-
//
|
|
2796
|
-
//
|
|
2797
|
-
|
|
2798
|
-
for (const rawIncludeOpt of includeList) {
|
|
2799
|
-
// Support both the object form (`{ model: Post, as: 'posts' }`) and
|
|
2800
|
-
// the bare-model shorthand (`Post`) used throughout the test suite
|
|
2801
|
-
// and docs (e.g. `findAll({ include: [Post] })`). A bare model has
|
|
2802
|
-
// no `.model` property of its own but does carry `.rawAttributes`/
|
|
2803
|
-
// `.tableName`, so detect that shape and wrap it before use -
|
|
2804
|
-
// otherwise `includeModel` was always undefined for the shorthand
|
|
2805
|
-
// form and the include was silently skipped.
|
|
2806
|
-
const includeOpt = rawIncludeOpt && !rawIncludeOpt.model && rawIncludeOpt.rawAttributes
|
|
2807
|
-
? { model: rawIncludeOpt }
|
|
2808
|
-
: rawIncludeOpt;
|
|
2809
|
-
const includeModel = includeOpt.model;
|
|
2810
|
-
if (!includeModel)
|
|
2811
|
-
continue;
|
|
2812
|
-
// Find the association definition to get the foreign key (and, for
|
|
2813
|
-
// belongsToMany, the junction table info).
|
|
2814
|
-
let foreignKey;
|
|
2815
|
-
let associationType;
|
|
2816
|
-
let assocOtherKey;
|
|
2817
|
-
let assocThrough;
|
|
2818
|
-
let assocThroughModel;
|
|
2819
|
-
let assocAs;
|
|
2820
|
-
for (const [, assoc] of self.associations.entries()) {
|
|
2821
|
-
const sourceMatch = assoc.source === parentModel || assoc.source?.name === parentModelName;
|
|
2822
|
-
const targetMatch = assoc.target === includeModel || assoc.target?.name === includeModel.name;
|
|
2823
|
-
const aliasMatch = !includeOpt.as || assoc.options?.as === includeOpt.as;
|
|
2824
|
-
if (sourceMatch && targetMatch && aliasMatch) {
|
|
2825
|
-
foreignKey = assoc.foreignKey;
|
|
2826
|
-
associationType = assoc.type;
|
|
2827
|
-
assocOtherKey = assoc.otherKey;
|
|
2828
|
-
assocThrough = assoc.through;
|
|
2829
|
-
assocThroughModel = assoc.throughModel;
|
|
2830
|
-
assocAs = assoc.as;
|
|
2831
|
-
break;
|
|
2832
|
-
}
|
|
2833
|
-
// Also check reverse (belongsTo) - check alias match too
|
|
2834
|
-
const revSourceMatch = assoc.source === includeModel || assoc.source?.name === includeModel.name;
|
|
2835
|
-
const revTargetMatch = assoc.target === parentModel || assoc.target?.name === parentModelName;
|
|
2836
|
-
const revAliasMatch = !includeOpt.as || assoc.options?.as === includeOpt.as;
|
|
2837
|
-
if (revSourceMatch && revTargetMatch && revAliasMatch) {
|
|
2838
|
-
foreignKey = assoc.foreignKey;
|
|
2839
|
-
associationType = assoc.type;
|
|
2840
|
-
assocOtherKey = assoc.otherKey;
|
|
2841
|
-
assocThrough = assoc.through;
|
|
2842
|
-
assocThroughModel = assoc.throughModel;
|
|
2843
|
-
assocAs = assoc.as;
|
|
2844
|
-
break;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
// Prefer the association's own default alias (e.g. `user` for a
|
|
2848
|
-
// singular belongsTo/hasOne, `posts` for a plural hasMany) over
|
|
2849
|
-
// the raw table name - using the table name here meant an
|
|
2850
|
-
// unaliased `include: [User]` populated `row.users` instead of
|
|
2851
|
-
// the expected `row.user`, silently breaking any include that
|
|
2852
|
-
// didn't pass an explicit `as`.
|
|
2853
|
-
const includeAlias = includeOpt.as || assocAs || includeModel.tableName || includeModel.name;
|
|
2854
|
-
// Resolve the actual primary-key attribute for each side of the association
|
|
2855
|
-
// (instead of assuming 'id'), since custom primary keys may differ per model.
|
|
2856
|
-
const sourcePkAttr = resolveModelPrimaryKeyAttr(parentModel);
|
|
2857
|
-
const targetPkAttr = resolveModelPrimaryKeyAttr(includeModel);
|
|
2858
|
-
// Get primary keys from parent rows
|
|
2859
|
-
const mainPks = parentRows
|
|
2860
|
-
.map((r) => r[sourcePkAttr])
|
|
2861
|
-
.filter((v) => v !== undefined && v !== null);
|
|
2862
|
-
if (mainPks.length === 0)
|
|
2863
|
-
continue;
|
|
2864
|
-
// For hasMany/hasOne: foreignKey is on target table pointing to source's pk
|
|
2865
|
-
// For belongsTo: foreignKey is on source table pointing to target's pk
|
|
2866
|
-
// For belongsToMany: foreignKey is on the junction table pointing to source's pk
|
|
2867
|
-
if (!foreignKey) {
|
|
2868
|
-
// Try to infer foreign key from model name
|
|
2869
|
-
foreignKey = `${parentModelName.charAt(0).toLowerCase() + parentModelName.slice(1)}Id`;
|
|
2870
|
-
}
|
|
2871
|
-
let fetchedRows = [];
|
|
2872
|
-
try {
|
|
2873
|
-
const includeWhere = { ...includeOpt.where };
|
|
2874
|
-
if (associationType === 'belongsTo') {
|
|
2875
|
-
// foreignKey is on this model, not the target
|
|
2876
|
-
// The association goes: this.foreignKey references target's pk
|
|
2877
|
-
const fkValues = parentRows
|
|
2878
|
-
.map((r) => r[foreignKey])
|
|
2879
|
-
.filter((v) => v !== undefined && v !== null);
|
|
2880
|
-
if (fkValues.length === 0) {
|
|
2881
|
-
for (const row of parentRows) {
|
|
2882
|
-
row[includeAlias] = null;
|
|
2883
|
-
if (row.dataValues)
|
|
2884
|
-
row.dataValues[includeAlias] = null;
|
|
2885
|
-
}
|
|
2886
|
-
continue;
|
|
2887
|
-
}
|
|
2888
|
-
const targetRows = await includeModel.findAll({
|
|
2889
|
-
where: { ...includeWhere, [targetPkAttr]: { $in: fkValues } },
|
|
2890
|
-
...(includeOpt.attributes ? { attributes: includeOpt.attributes } : {}),
|
|
2891
|
-
});
|
|
2892
|
-
fetchedRows = targetRows;
|
|
2893
|
-
const targetMap = new Map();
|
|
2894
|
-
for (const tr of targetRows) {
|
|
2895
|
-
targetMap.set(tr[targetPkAttr], tr);
|
|
2896
|
-
}
|
|
2897
|
-
for (const row of parentRows) {
|
|
2898
|
-
const fkVal = row[foreignKey];
|
|
2899
|
-
const associated = fkVal !== undefined && fkVal !== null ? targetMap.get(fkVal) || null : null;
|
|
2900
|
-
row[includeAlias] = associated;
|
|
2901
|
-
if (row.dataValues)
|
|
2902
|
-
row.dataValues[includeAlias] = associated;
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
else if (associationType === 'belongsToMany') {
|
|
2906
|
-
// Many-to-many: the foreign key linking to the source lives on the junction
|
|
2907
|
-
// (through) table, not on the target table directly. Mirrors the logic used by
|
|
2908
|
-
// the row-instance `get<Alias>()` methods: find the through model, query junction
|
|
2909
|
-
// rows matching the parent rows' PKs, then query target rows by the resulting ids.
|
|
2910
|
-
let throughModel = assocThroughModel;
|
|
2911
|
-
if (!throughModel && assocThrough) {
|
|
2912
|
-
for (const m of Object.values(self.models)) {
|
|
2913
|
-
if (m.tableName === assocThrough ||
|
|
2914
|
-
m.name === assocThrough) {
|
|
2915
|
-
throughModel = m;
|
|
2916
|
-
break;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
if (!throughModel || typeof throughModel.findAll !== 'function') {
|
|
2921
|
-
throw new Error(`Cannot resolve through model for belongsToMany include "${includeAlias}"`);
|
|
2922
|
-
}
|
|
2923
|
-
const otherKey = assocOtherKey || `${includeModel.name}Id`;
|
|
2924
|
-
const junctionRows = await throughModel.findAll({
|
|
2925
|
-
where: { [foreignKey]: { $in: mainPks } },
|
|
2926
|
-
});
|
|
2927
|
-
const targetIdsBySource = new Map();
|
|
2928
|
-
const allTargetIds = [];
|
|
2929
|
-
for (const jr of junctionRows) {
|
|
2930
|
-
const srcVal = jr[foreignKey];
|
|
2931
|
-
const tgtVal = jr[otherKey];
|
|
2932
|
-
if (tgtVal === undefined || tgtVal === null)
|
|
2933
|
-
continue;
|
|
2934
|
-
if (!targetIdsBySource.has(srcVal))
|
|
2935
|
-
targetIdsBySource.set(srcVal, []);
|
|
2936
|
-
targetIdsBySource.get(srcVal).push(tgtVal);
|
|
2937
|
-
allTargetIds.push(tgtVal);
|
|
2938
|
-
}
|
|
2939
|
-
if (allTargetIds.length === 0) {
|
|
2940
|
-
for (const row of parentRows) {
|
|
2941
|
-
row[includeAlias] = [];
|
|
2942
|
-
if (row.dataValues)
|
|
2943
|
-
row.dataValues[includeAlias] = [];
|
|
2944
|
-
}
|
|
2945
|
-
continue;
|
|
2946
|
-
}
|
|
2947
|
-
const targetRows = await includeModel.findAll({
|
|
2948
|
-
where: { ...includeWhere, [targetPkAttr]: { $in: allTargetIds } },
|
|
2949
|
-
...(includeOpt.attributes ? { attributes: includeOpt.attributes } : {}),
|
|
2950
|
-
...(includeOpt.order ? { order: includeOpt.order } : {}),
|
|
2951
|
-
});
|
|
2952
|
-
fetchedRows = targetRows;
|
|
2953
|
-
const targetMap = new Map();
|
|
2954
|
-
for (const tr of targetRows) {
|
|
2955
|
-
targetMap.set(tr[targetPkAttr], tr);
|
|
2956
|
-
}
|
|
2957
|
-
for (const row of parentRows) {
|
|
2958
|
-
const srcPk = row[sourcePkAttr];
|
|
2959
|
-
const targetIds = targetIdsBySource.get(srcPk) || [];
|
|
2960
|
-
const associated = targetIds
|
|
2961
|
-
.map((tid) => targetMap.get(tid))
|
|
2962
|
-
.filter((v) => v !== undefined);
|
|
2963
|
-
row[includeAlias] = associated;
|
|
2964
|
-
if (row.dataValues)
|
|
2965
|
-
row.dataValues[includeAlias] = associated;
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
else {
|
|
2969
|
-
// hasMany/hasOne: foreignKey is on target table
|
|
2970
|
-
if (includeOpt.where)
|
|
2971
|
-
Object.assign(includeWhere, includeOpt.where);
|
|
2972
|
-
includeWhere[foreignKey] = { $in: mainPks };
|
|
2973
|
-
const assocRows = await includeModel.findAll({
|
|
2974
|
-
where: includeWhere,
|
|
2975
|
-
...(includeOpt.attributes ? { attributes: includeOpt.attributes } : {}),
|
|
2976
|
-
...(includeOpt.order ? { order: includeOpt.order } : {}),
|
|
2977
|
-
});
|
|
2978
|
-
fetchedRows = assocRows;
|
|
2979
|
-
// Group by foreignKey value
|
|
2980
|
-
const groupedRows = new Map();
|
|
2981
|
-
for (const ar of assocRows) {
|
|
2982
|
-
const fkVal = ar[foreignKey];
|
|
2983
|
-
if (!groupedRows.has(fkVal))
|
|
2984
|
-
groupedRows.set(fkVal, []);
|
|
2985
|
-
groupedRows.get(fkVal).push(ar);
|
|
2986
|
-
}
|
|
2987
|
-
// Attach to parent rows
|
|
2988
|
-
for (const row of parentRows) {
|
|
2989
|
-
const pk = row[sourcePkAttr];
|
|
2990
|
-
const associated = groupedRows.get(pk) || [];
|
|
2991
|
-
// For hasOne, use the first result or null
|
|
2992
|
-
const attachValue = associationType === 'hasOne' ? associated[0] || null : associated;
|
|
2993
|
-
row[includeAlias] = attachValue;
|
|
2994
|
-
if (row.dataValues)
|
|
2995
|
-
row.dataValues[includeAlias] = attachValue;
|
|
2996
|
-
}
|
|
2997
|
-
}
|
|
2998
|
-
// Recurse into nested includes, treating the just-fetched rows as the new
|
|
2999
|
-
// "parent" rows for the next level down.
|
|
3000
|
-
const nestedIncludes = includeOpt.include;
|
|
3001
|
-
if (nestedIncludes && nestedIncludes.length > 0 && fetchedRows.length > 0) {
|
|
3002
|
-
await loadIncludesRecursive(fetchedRows, includeModel, includeModel.name, nestedIncludes);
|
|
3003
|
-
}
|
|
3004
|
-
}
|
|
3005
|
-
catch {
|
|
3006
|
-
// If include query fails, set empty
|
|
3007
|
-
for (const row of parentRows) {
|
|
3008
|
-
if (row[includeAlias] === undefined) {
|
|
3009
|
-
row[includeAlias] =
|
|
3010
|
-
associationType === 'hasMany' || associationType === 'belongsToMany' ? [] : null;
|
|
3011
|
-
if (row.dataValues)
|
|
3012
|
-
row.dataValues[includeAlias] = row[includeAlias];
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
2975
|
+
// Eager-load associations. The implementation is shared with the
|
|
2976
|
+
// class-based Model API (see models/eager-load.ts) - it used to be
|
|
2977
|
+
// duplicated here and in models/model.ts, and the two copies had
|
|
2978
|
+
// drifted apart in how they handled belongsTo and belongsToMany.
|
|
3018
2979
|
if (findOptions.include && findOptions.include.length > 0) {
|
|
3019
|
-
await
|
|
2980
|
+
await (0, eager_load_1.loadIncludes)(self.eagerLoadContext(), wrappedRows, model, modelName, findOptions.include);
|
|
3020
2981
|
}
|
|
3021
2982
|
return wrappedRows;
|
|
3022
2983
|
},
|
|
@@ -3025,13 +2986,12 @@ class Prorm extends events_1.EventEmitter {
|
|
|
3025
2986
|
*/
|
|
3026
2987
|
async findAndCountAll(findOptions = {}) {
|
|
3027
2988
|
const rows = await this.findAll(findOptions);
|
|
3028
|
-
// Count
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
};
|
|
3033
|
-
const
|
|
3034
|
-
const count = allRows.length;
|
|
2989
|
+
// Count with an aggregate rather than by re-running the full query
|
|
2990
|
+
// without a limit and measuring the array. `limit`/`offset` are dropped
|
|
2991
|
+
// (the count describes the whole matching set), everything else that
|
|
2992
|
+
// narrows the set - including filtering includes - is preserved.
|
|
2993
|
+
const { limit: _limit, offset: _offset, ...countable } = this._mergeScopes(findOptions);
|
|
2994
|
+
const count = await self._countRows(model, modelName, countable);
|
|
3035
2995
|
return { rows, count };
|
|
3036
2996
|
},
|
|
3037
2997
|
/**
|
|
@@ -3056,11 +3016,45 @@ class Prorm extends events_1.EventEmitter {
|
|
|
3056
3016
|
if (existing) {
|
|
3057
3017
|
return [existing, false];
|
|
3058
3018
|
}
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3019
|
+
// Only equality conditions from `where` describe a row that can be
|
|
3020
|
+
// created. Operator objects (`{ email: { $like: 'a%' } }`) used to be
|
|
3021
|
+
// spread into the INSERT verbatim, which tried to store the operator
|
|
3022
|
+
// object itself as the column value.
|
|
3023
|
+
const equalityFromWhere = {};
|
|
3024
|
+
for (const [key, value] of Object.entries((findOptions.where || {}))) {
|
|
3025
|
+
if (key.startsWith('$'))
|
|
3026
|
+
continue; // $and / $or / $not - not a column
|
|
3027
|
+
const isOperatorObject = value !== null &&
|
|
3028
|
+
typeof value === 'object' &&
|
|
3029
|
+
!(value instanceof Date) &&
|
|
3030
|
+
!Array.isArray(value) &&
|
|
3031
|
+
Object.keys(value).some((k) => k.startsWith('$'));
|
|
3032
|
+
if (!isOperatorObject)
|
|
3033
|
+
equalityFromWhere[key] = value;
|
|
3034
|
+
}
|
|
3035
|
+
const createValues = {
|
|
3036
|
+
...findOptions.defaults,
|
|
3037
|
+
...equalityFromWhere,
|
|
3038
|
+
};
|
|
3039
|
+
try {
|
|
3040
|
+
const created = await this.create(createValues, {
|
|
3041
|
+
transaction: findOptions.transaction,
|
|
3042
|
+
});
|
|
3043
|
+
return [created, true];
|
|
3044
|
+
}
|
|
3045
|
+
catch (error) {
|
|
3046
|
+
// The find and the create are two statements, so a concurrent caller
|
|
3047
|
+
// can insert between them. When the database rejects our insert on a
|
|
3048
|
+
// unique/primary key, the row we wanted now exists - return it rather
|
|
3049
|
+
// than surfacing a constraint error from an idempotent-looking call.
|
|
3050
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3051
|
+
if (!/unique|duplicate|constraint/i.test(message))
|
|
3052
|
+
throw error;
|
|
3053
|
+
const raced = await this.findOne(findOptions);
|
|
3054
|
+
if (raced)
|
|
3055
|
+
return [raced, false];
|
|
3056
|
+
throw error;
|
|
3057
|
+
}
|
|
3064
3058
|
},
|
|
3065
3059
|
/**
|
|
3066
3060
|
* Find a record or build (但不保存) it if not found
|
|
@@ -3158,6 +3152,18 @@ class Prorm extends events_1.EventEmitter {
|
|
|
3158
3152
|
const validationErrors = [];
|
|
3159
3153
|
for (const [attrName, attr] of Object.entries(attributes)) {
|
|
3160
3154
|
const val = hookInstance[attrName];
|
|
3155
|
+
// An auto-increment key is assigned by the database on insert, so it
|
|
3156
|
+
// is legitimately absent here. Requiring it would make every
|
|
3157
|
+
// `@PrimaryKey @AutoIncrement` column reject its own INSERT.
|
|
3158
|
+
if ((val === undefined || val === null) && attr.autoIncrement)
|
|
3159
|
+
continue;
|
|
3160
|
+
// A column with a default (including a DB-side default) is likewise
|
|
3161
|
+
// allowed to be absent - the database supplies the value.
|
|
3162
|
+
if ((val === undefined || val === null) &&
|
|
3163
|
+
attr.defaultValue !== undefined &&
|
|
3164
|
+
attr.allowNull === false) {
|
|
3165
|
+
continue;
|
|
3166
|
+
}
|
|
3161
3167
|
// Check allowNull regardless of validate option
|
|
3162
3168
|
if ((val === undefined || val === null) && attr.allowNull === false) {
|
|
3163
3169
|
validationErrors.push({
|
|
@@ -3576,9 +3582,18 @@ class Prorm extends events_1.EventEmitter {
|
|
|
3576
3582
|
/**
|
|
3577
3583
|
* Count records
|
|
3578
3584
|
*/
|
|
3585
|
+
/**
|
|
3586
|
+
* Count matching rows with a real aggregate query.
|
|
3587
|
+
*
|
|
3588
|
+
* This used to delegate to `findAndCountAll`, which fetched every
|
|
3589
|
+
* matching row (including eager-loading its associations) and returned
|
|
3590
|
+
* `rows.length` - so counting a large table transferred the whole table,
|
|
3591
|
+
* and `distinct`, `col` and `group` were silently discarded.
|
|
3592
|
+
*/
|
|
3579
3593
|
async count(countOptions = {}) {
|
|
3580
|
-
|
|
3581
|
-
|
|
3594
|
+
// Apply defaultScope / named scopes exactly as findAll does, so
|
|
3595
|
+
// `User.scope('admin').count()` counts the scoped set.
|
|
3596
|
+
return self._countRows(model, modelName, this._mergeScopes(countOptions));
|
|
3582
3597
|
},
|
|
3583
3598
|
/**
|
|
3584
3599
|
* Truncate table
|
|
@@ -4208,31 +4223,28 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4208
4223
|
};
|
|
4209
4224
|
// Call define to register the model
|
|
4210
4225
|
const registeredModel = this.define(modelName, attributes, options);
|
|
4211
|
-
//
|
|
4226
|
+
// Queue associations declared via @HasOne/@HasMany/@BelongsTo/@BelongsToMany.
|
|
4227
|
+
//
|
|
4228
|
+
// These are NOT bound immediately. `@BelongsTo(() => User)` uses a thunk so
|
|
4229
|
+
// the reference survives a circular import, but binding used to happen right
|
|
4230
|
+
// here - so if `Post` was added before `User`, the association bound against
|
|
4231
|
+
// a class that had never been through `define()`: no `tableName`, no
|
|
4232
|
+
// `rawAttributes`, and `createTableFromModel` then silently skipped the
|
|
4233
|
+
// REFERENCES clause. Whether foreign keys were created depended on the order
|
|
4234
|
+
// the model files happened to be imported in. Queue instead, and flush every
|
|
4235
|
+
// association whose target is registered after each `addModel`.
|
|
4212
4236
|
const assocMeta = (0, decorators_1.getAssociationMetadata)(modelName);
|
|
4213
4237
|
if (assocMeta) {
|
|
4214
4238
|
for (const assoc of assocMeta) {
|
|
4215
|
-
|
|
4216
|
-
const assocOptions = {
|
|
4217
|
-
as: assoc.as,
|
|
4218
|
-
foreignKey: assoc.foreignKey,
|
|
4219
|
-
onDelete: assoc.onDelete,
|
|
4220
|
-
onUpdate: assoc.onUpdate,
|
|
4221
|
-
hooks: assoc.hooks,
|
|
4222
|
-
};
|
|
4223
|
-
if (assoc.type === 'hasOne' || assoc.type === 'hasMany') {
|
|
4224
|
-
assocOptions.sourceKey = assoc.sourceKey;
|
|
4225
|
-
}
|
|
4226
|
-
if (assoc.type === 'belongsTo') {
|
|
4227
|
-
assocOptions.targetKey = assoc.targetKey;
|
|
4228
|
-
}
|
|
4229
|
-
if (assoc.type === 'belongsToMany') {
|
|
4230
|
-
assocOptions.through = assoc.through;
|
|
4231
|
-
assocOptions.otherKey = assoc.otherKey;
|
|
4232
|
-
}
|
|
4233
|
-
registeredModel[assoc.type](targetModel, assocOptions);
|
|
4239
|
+
this._pendingAssociations.push({ sourceName: modelName, source: registeredModel, assoc });
|
|
4234
4240
|
}
|
|
4235
4241
|
}
|
|
4242
|
+
this._flushPendingAssociations();
|
|
4243
|
+
// Remember the class so sync() can apply its @View / @MaterializedView /
|
|
4244
|
+
// @Trigger annotations.
|
|
4245
|
+
if (!this._decoratedClasses.includes(modelClass)) {
|
|
4246
|
+
this._decoratedClasses.push(modelClass);
|
|
4247
|
+
}
|
|
4236
4248
|
// Replay any scopes queued on the class via addScope() before it was
|
|
4237
4249
|
// registered (associations are replayed elsewhere; scopes were not).
|
|
4238
4250
|
const pendingScopes = modelClass._pendingScopes;
|
|
@@ -4247,7 +4259,7 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4247
4259
|
Object.setPrototypeOf(modelClass, registeredModelObj);
|
|
4248
4260
|
// Also ensure the original class has the right rawAttributes, tableName, and options
|
|
4249
4261
|
modelClass.rawAttributes = attributes;
|
|
4250
|
-
modelClass.tableName =
|
|
4262
|
+
modelClass.tableName = deriveTableName(modelName, options);
|
|
4251
4263
|
modelClass.options = options;
|
|
4252
4264
|
// Set the prorm reference
|
|
4253
4265
|
modelClass.prorm = this;
|
|
@@ -4327,6 +4339,9 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4327
4339
|
comment: attr?.comment,
|
|
4328
4340
|
};
|
|
4329
4341
|
}
|
|
4342
|
+
// Composite foreign keys can't be expressed as an inline column REFERENCES,
|
|
4343
|
+
// so they accumulate here and are merged into the table-level constraints below.
|
|
4344
|
+
const synthesizedFkConstraints = [];
|
|
4330
4345
|
// Wire up foreign-key constraints implied by hasOne/hasMany/belongsTo
|
|
4331
4346
|
// associations declared on this or other models. Associations only add
|
|
4332
4347
|
// runtime getter/setter/create helpers (see models/associations.ts) - they
|
|
@@ -4344,45 +4359,99 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4344
4359
|
for (const assoc of this.associations.values()) {
|
|
4345
4360
|
const assocAny = assoc;
|
|
4346
4361
|
const fk = assocAny.foreignKey;
|
|
4347
|
-
if (
|
|
4348
|
-
continue;
|
|
4362
|
+
if (!fk)
|
|
4363
|
+
continue;
|
|
4349
4364
|
const sourceName = assocAny.source?.name;
|
|
4350
4365
|
const targetName = assocAny.target?.name;
|
|
4366
|
+
// Which side of the association owns the foreign key column decides
|
|
4367
|
+
// both the table it references and which option names the referenced key.
|
|
4368
|
+
let refModel;
|
|
4369
|
+
let refKeyOption;
|
|
4351
4370
|
if (assocAny.type === 'belongsTo' && sourceName === modelName) {
|
|
4352
4371
|
// FK column lives on this (source) table, referencing the target's table
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
columns[fk] = {
|
|
4356
|
-
...(columns[fk] || { type: 'INTEGER', allowNull: true }),
|
|
4357
|
-
references: {
|
|
4358
|
-
table: refTable,
|
|
4359
|
-
field: assocAny.options?.targetKey || 'id',
|
|
4360
|
-
onDelete: assocAny.options?.onDelete,
|
|
4361
|
-
onUpdate: assocAny.options?.onUpdate,
|
|
4362
|
-
},
|
|
4363
|
-
};
|
|
4364
|
-
}
|
|
4372
|
+
refModel = assocAny.target;
|
|
4373
|
+
refKeyOption = assocAny.options?.targetKey;
|
|
4365
4374
|
}
|
|
4366
4375
|
else if ((assocAny.type === 'hasMany' || assocAny.type === 'hasOne') &&
|
|
4367
4376
|
targetName === modelName) {
|
|
4368
4377
|
// FK column lives on this (target) table, referencing the source's table
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4378
|
+
refModel = assocAny.source;
|
|
4379
|
+
refKeyOption = assocAny.options?.sourceKey;
|
|
4380
|
+
}
|
|
4381
|
+
else {
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
const refTable = refModel?.tableName;
|
|
4385
|
+
if (!refTable)
|
|
4386
|
+
continue;
|
|
4387
|
+
// Composite foreign keys (`foreignKey: ['orgId', 'userId']`) are a
|
|
4388
|
+
// supported option shape and were previously dropped outright here, so
|
|
4389
|
+
// they produced neither a column nor a constraint. Normalize both forms
|
|
4390
|
+
// to arrays and pair each local column with its referenced key.
|
|
4391
|
+
const fkCols = Array.isArray(fk) ? fk : [fk];
|
|
4392
|
+
const refKeys = Array.isArray(refKeyOption)
|
|
4393
|
+
? refKeyOption
|
|
4394
|
+
: refKeyOption
|
|
4395
|
+
? [refKeyOption]
|
|
4396
|
+
: fkCols.length === 1
|
|
4397
|
+
? [resolveModelPrimaryKeyAttr(refModel)]
|
|
4398
|
+
: [];
|
|
4399
|
+
if (refKeys.length !== fkCols.length) {
|
|
4400
|
+
throw new errors_1.AssociationError(`${assocAny.type} association between ${sourceName} and ${targetName} declares ` +
|
|
4401
|
+
`${fkCols.length} foreign key column(s) (${fkCols.join(', ')}) but ` +
|
|
4402
|
+
`${refKeys.length} referenced key(s)` +
|
|
4403
|
+
(refKeys.length ? ` (${refKeys.join(', ')})` : '') +
|
|
4404
|
+
`. Pass a matching ${assocAny.type === 'belongsTo' ? 'targetKey' : 'sourceKey'} array.`, { association: assocAny.as });
|
|
4405
|
+
}
|
|
4406
|
+
// Synthesize any missing column with a type that actually matches the
|
|
4407
|
+
// key it references, rather than assuming INTEGER.
|
|
4408
|
+
fkCols.forEach((col, i) => {
|
|
4409
|
+
if (!columns[col]) {
|
|
4410
|
+
columns[col] = {
|
|
4411
|
+
type: resolveForeignKeyColumnType(refModel, refKeys[i]),
|
|
4412
|
+
allowNull: true,
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
});
|
|
4416
|
+
if (fkCols.length === 1) {
|
|
4417
|
+
// Single-column keys stay as an inline column-level REFERENCES.
|
|
4418
|
+
if (!columns[fkCols[0]].references) {
|
|
4419
|
+
columns[fkCols[0]] = {
|
|
4420
|
+
...columns[fkCols[0]],
|
|
4373
4421
|
references: {
|
|
4374
4422
|
table: refTable,
|
|
4375
|
-
field:
|
|
4423
|
+
field: refKeys[0],
|
|
4376
4424
|
onDelete: assocAny.options?.onDelete,
|
|
4377
4425
|
onUpdate: assocAny.options?.onUpdate,
|
|
4378
4426
|
},
|
|
4379
4427
|
};
|
|
4380
4428
|
}
|
|
4381
4429
|
}
|
|
4430
|
+
else {
|
|
4431
|
+
// A composite key can only be expressed as a table-level constraint.
|
|
4432
|
+
const constraintName = `fk_${tableName}_${fkCols.join('_')}`;
|
|
4433
|
+
synthesizedFkConstraints.push({
|
|
4434
|
+
name: constraintName,
|
|
4435
|
+
type: 'FOREIGN KEY',
|
|
4436
|
+
fields: fkCols,
|
|
4437
|
+
references: {
|
|
4438
|
+
table: refTable,
|
|
4439
|
+
field: refKeys,
|
|
4440
|
+
onDelete: assocAny.options?.onDelete,
|
|
4441
|
+
onUpdate: assocAny.options?.onUpdate,
|
|
4442
|
+
},
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4382
4445
|
}
|
|
4383
4446
|
}
|
|
4384
|
-
// Add auto-id column if no primary key is defined in attributes
|
|
4385
|
-
|
|
4447
|
+
// Add auto-id column if no primary key is defined in attributes.
|
|
4448
|
+
// A composite primary key can only be declared as a table-level constraint,
|
|
4449
|
+
// so that counts as "has a primary key" too - otherwise an `id` column was
|
|
4450
|
+
// injected alongside it and the table came out with two primary keys.
|
|
4451
|
+
const hasTablePrimaryKey = (options.constraints || []).some((c) => c?.type === 'PRIMARY KEY');
|
|
4452
|
+
if (!Object.values(columns).some((col) => col.primaryKey) &&
|
|
4453
|
+
!columns['id'] &&
|
|
4454
|
+
!hasTablePrimaryKey) {
|
|
4386
4455
|
const withId = {
|
|
4387
4456
|
id: {
|
|
4388
4457
|
type: 'INTEGER',
|
|
@@ -4444,6 +4513,10 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4444
4513
|
: undefined,
|
|
4445
4514
|
check: c.check,
|
|
4446
4515
|
}));
|
|
4516
|
+
const allTableConstraints = [
|
|
4517
|
+
...(tableConstraints || []),
|
|
4518
|
+
...synthesizedFkConstraints,
|
|
4519
|
+
];
|
|
4447
4520
|
await this.dialect.createTable(tableName, columns, {
|
|
4448
4521
|
ifNotExists: true,
|
|
4449
4522
|
engine: options.engine,
|
|
@@ -4454,7 +4527,7 @@ class Prorm extends events_1.EventEmitter {
|
|
|
4454
4527
|
initialAutoIncrement: options.initialAutoIncrement,
|
|
4455
4528
|
uniqueKeys: options.uniqueKeys,
|
|
4456
4529
|
indexes: tableIndexes,
|
|
4457
|
-
constraints:
|
|
4530
|
+
constraints: allTableConstraints,
|
|
4458
4531
|
});
|
|
4459
4532
|
}
|
|
4460
4533
|
/**
|
|
@@ -5986,6 +6059,15 @@ $$ LANGUAGE plpgsql;`;
|
|
|
5986
6059
|
this.pendingTableCreates = [];
|
|
5987
6060
|
await Promise.all(pending);
|
|
5988
6061
|
}
|
|
6062
|
+
// Bind any decorator associations whose target became available after the
|
|
6063
|
+
// source model was added, then surface the ones that never resolved rather
|
|
6064
|
+
// than creating tables with silently missing foreign keys.
|
|
6065
|
+
this._flushPendingAssociations();
|
|
6066
|
+
const unresolved = this._unresolvedAssociationTargets();
|
|
6067
|
+
if (unresolved.length > 0) {
|
|
6068
|
+
throw new errors_1.AssociationError(`Cannot sync: ${unresolved.length} association(s) reference a model that was never ` +
|
|
6069
|
+
`registered (${unresolved.join('; ')}). Pass every model to addModel()/define().`);
|
|
6070
|
+
}
|
|
5989
6071
|
const { force = false, alter = false, match, logging = false, hooks = true, indexes = true, constraints = true, } = options;
|
|
5990
6072
|
// Default options
|
|
5991
6073
|
const syncOptions = {
|
|
@@ -6033,6 +6115,9 @@ $$ LANGUAGE plpgsql;`;
|
|
|
6033
6115
|
// Default mode: create missing tables only
|
|
6034
6116
|
await this.syncCreate();
|
|
6035
6117
|
}
|
|
6118
|
+
// Apply annotation-declared DDL (@View / @MaterializedView / @Trigger /
|
|
6119
|
+
// @Procedure) now that the tables they depend on exist.
|
|
6120
|
+
await this.syncSchemaObjects();
|
|
6036
6121
|
// Run afterSync hook if enabled
|
|
6037
6122
|
if (hooks) {
|
|
6038
6123
|
// Run global afterSync hooks
|
|
@@ -6055,6 +6140,342 @@ $$ LANGUAGE plpgsql;`;
|
|
|
6055
6140
|
}
|
|
6056
6141
|
return this;
|
|
6057
6142
|
}
|
|
6143
|
+
/**
|
|
6144
|
+
* Register a class carrying `@Procedure` / `@SqlFunction` annotations so that
|
|
6145
|
+
* `sync()` creates them.
|
|
6146
|
+
*
|
|
6147
|
+
* Procedures are declared on plain classes rather than models, so there is no
|
|
6148
|
+
* other point at which the ORM would learn about them.
|
|
6149
|
+
*/
|
|
6150
|
+
/**
|
|
6151
|
+
* Register an object or key-value store for use by `@ExternalField`.
|
|
6152
|
+
*
|
|
6153
|
+
* Accepts any of the shipped stores (S3, MinIO, R2, GCS, Azure Blob, Redis,
|
|
6154
|
+
* ...) directly - the differing method names are normalized internally - or
|
|
6155
|
+
* any object implementing `ExternalStoreAdapter`.
|
|
6156
|
+
*
|
|
6157
|
+
* ```typescript
|
|
6158
|
+
* const s3 = new S3Store({ region: 'us-east-1' });
|
|
6159
|
+
* await s3.connect();
|
|
6160
|
+
* prorm.registerStore('assets', s3, { defaultBucket: 'avatars' });
|
|
6161
|
+
* ```
|
|
6162
|
+
*/
|
|
6163
|
+
registerStore(name, store, options = {}) {
|
|
6164
|
+
this.externalStores.register(name, store, options);
|
|
6165
|
+
return this;
|
|
6166
|
+
}
|
|
6167
|
+
registerProcedures(procedureClass) {
|
|
6168
|
+
if (!this._procedureClasses.includes(procedureClass)) {
|
|
6169
|
+
this._procedureClasses.push(procedureClass);
|
|
6170
|
+
}
|
|
6171
|
+
return this;
|
|
6172
|
+
}
|
|
6173
|
+
/**
|
|
6174
|
+
* Apply the DDL declared by annotations: `@View`, `@MaterializedView`,
|
|
6175
|
+
* `@Trigger` on model classes, and `@Procedure` / `@SqlFunction` on classes
|
|
6176
|
+
* passed to `registerProcedures()`.
|
|
6177
|
+
*
|
|
6178
|
+
* These registries previously generated correct SQL that nothing executed -
|
|
6179
|
+
* `ViewRegistry`, `getTriggers` and `ProcedureRegistry` were referenced only
|
|
6180
|
+
* from inside `src/decorators/`, so annotating a class produced a string the
|
|
6181
|
+
* user still had to fetch and hand to `prorm.query()` themselves.
|
|
6182
|
+
*/
|
|
6183
|
+
async syncSchemaObjects() {
|
|
6184
|
+
if (!this.dialect)
|
|
6185
|
+
return;
|
|
6186
|
+
const dialectName = this.dialect.name;
|
|
6187
|
+
for (const modelClass of this._decoratedClasses) {
|
|
6188
|
+
// Views / materialized views
|
|
6189
|
+
try {
|
|
6190
|
+
if ((0, view_1.getViewOptions)(modelClass)) {
|
|
6191
|
+
await this.query(view_1.ViewRegistry.getSQL(modelClass, dialectName));
|
|
6192
|
+
this.logger.info(`Created view for ${modelClass.name}`);
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
catch (error) {
|
|
6196
|
+
this.logger.error(`Failed to create view for ${modelClass.name}`, { error });
|
|
6197
|
+
throw error;
|
|
6198
|
+
}
|
|
6199
|
+
try {
|
|
6200
|
+
if ((0, view_2.getMaterializedViewOptions)(modelClass)) {
|
|
6201
|
+
await this.query(view_2.MaterializedViewRegistry.getSQL(modelClass, dialectName));
|
|
6202
|
+
this.logger.info(`Created materialized view for ${modelClass.name}`);
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
catch (error) {
|
|
6206
|
+
this.logger.error(`Failed to create materialized view for ${modelClass.name}`, { error });
|
|
6207
|
+
throw error;
|
|
6208
|
+
}
|
|
6209
|
+
// Triggers
|
|
6210
|
+
const triggers = (0, trigger_1.getTriggers)(modelClass);
|
|
6211
|
+
if (triggers.length > 0) {
|
|
6212
|
+
const tableName = modelClass.tableName || deriveTableName(modelClass.name, undefined);
|
|
6213
|
+
for (const trigger of triggers) {
|
|
6214
|
+
try {
|
|
6215
|
+
const sql = (0, trigger_1.buildCreateTriggerSQL)(tableName, trigger, dialectName);
|
|
6216
|
+
// The postgres builder returns the function and the trigger joined
|
|
6217
|
+
// by ';\n\n'; most drivers reject multi-statement strings.
|
|
6218
|
+
for (const stmt of sql.split(/;\s*\n\s*\n/)) {
|
|
6219
|
+
const trimmed = stmt.trim().replace(/;$/, '');
|
|
6220
|
+
if (trimmed)
|
|
6221
|
+
await this.query(trimmed);
|
|
6222
|
+
}
|
|
6223
|
+
this.logger.info(`Created trigger ${trigger.name} on ${tableName}`);
|
|
6224
|
+
}
|
|
6225
|
+
catch (error) {
|
|
6226
|
+
this.logger.error(`Failed to create trigger ${trigger.name}`, { error });
|
|
6227
|
+
throw error;
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
// Stored procedures and SQL functions
|
|
6233
|
+
for (const procClass of this._procedureClasses) {
|
|
6234
|
+
for (const meta of procedure_1.ProcedureRegistry.getAll(procClass)) {
|
|
6235
|
+
try {
|
|
6236
|
+
const sql = procedure_1.ProcedureRegistry.getSQL(procClass, meta.methodKey);
|
|
6237
|
+
await this.query(sql);
|
|
6238
|
+
this.logger.info(`Created ${meta.kind ?? 'procedure'} ${meta.options?.name ?? meta.methodKey}`);
|
|
6239
|
+
}
|
|
6240
|
+
catch (error) {
|
|
6241
|
+
this.logger.error(`Failed to create procedure ${meta.methodKey}`, { error });
|
|
6242
|
+
throw error;
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
/**
|
|
6248
|
+
* Bind every queued decorator association whose target model is registered.
|
|
6249
|
+
*
|
|
6250
|
+
* Called after each `addModel()`, and again by `sync()` so that a model graph
|
|
6251
|
+
* registered in any order ends up fully wired before tables are created.
|
|
6252
|
+
* Associations whose target is still unregistered stay queued.
|
|
6253
|
+
*/
|
|
6254
|
+
_flushPendingAssociations() {
|
|
6255
|
+
if (this._pendingAssociations.length === 0)
|
|
6256
|
+
return;
|
|
6257
|
+
const stillPending = [];
|
|
6258
|
+
for (const entry of this._pendingAssociations) {
|
|
6259
|
+
const { source, assoc } = entry;
|
|
6260
|
+
let targetModel;
|
|
6261
|
+
try {
|
|
6262
|
+
targetModel = assoc.target();
|
|
6263
|
+
}
|
|
6264
|
+
catch {
|
|
6265
|
+
// The thunk can still throw while a circular import is mid-flight.
|
|
6266
|
+
stillPending.push(entry);
|
|
6267
|
+
continue;
|
|
6268
|
+
}
|
|
6269
|
+
const targetName = targetModel?.name;
|
|
6270
|
+
const registeredTarget = targetName ? this._models.get(targetName) : undefined;
|
|
6271
|
+
if (!registeredTarget) {
|
|
6272
|
+
stillPending.push(entry);
|
|
6273
|
+
continue;
|
|
6274
|
+
}
|
|
6275
|
+
const assocOptions = {
|
|
6276
|
+
as: assoc.as,
|
|
6277
|
+
foreignKey: assoc.foreignKey,
|
|
6278
|
+
onDelete: assoc.onDelete,
|
|
6279
|
+
onUpdate: assoc.onUpdate,
|
|
6280
|
+
hooks: assoc.hooks,
|
|
6281
|
+
};
|
|
6282
|
+
if (assoc.type === 'hasOne' || assoc.type === 'hasMany') {
|
|
6283
|
+
assocOptions.sourceKey = assoc.sourceKey;
|
|
6284
|
+
}
|
|
6285
|
+
if (assoc.type === 'belongsTo') {
|
|
6286
|
+
assocOptions.targetKey = assoc.targetKey;
|
|
6287
|
+
}
|
|
6288
|
+
if (assoc.type === 'belongsToMany') {
|
|
6289
|
+
assocOptions.through = assoc.through;
|
|
6290
|
+
assocOptions.otherKey = assoc.otherKey;
|
|
6291
|
+
}
|
|
6292
|
+
source[assoc.type](registeredTarget, assocOptions);
|
|
6293
|
+
}
|
|
6294
|
+
this._pendingAssociations = stillPending;
|
|
6295
|
+
}
|
|
6296
|
+
/**
|
|
6297
|
+
* Names of models referenced by a decorator association that were never
|
|
6298
|
+
* registered, so callers can report them rather than silently skipping.
|
|
6299
|
+
*/
|
|
6300
|
+
_unresolvedAssociationTargets() {
|
|
6301
|
+
const names = new Set();
|
|
6302
|
+
for (const { sourceName, assoc } of this._pendingAssociations) {
|
|
6303
|
+
let targetName = 'unknown';
|
|
6304
|
+
try {
|
|
6305
|
+
targetName = assoc.target()?.name ?? 'unknown';
|
|
6306
|
+
}
|
|
6307
|
+
catch {
|
|
6308
|
+
/* keep 'unknown' */
|
|
6309
|
+
}
|
|
6310
|
+
names.add(`${sourceName}.${assoc.as ?? assoc.type} -> ${targetName}`);
|
|
6311
|
+
}
|
|
6312
|
+
return Array.from(names);
|
|
6313
|
+
}
|
|
6314
|
+
/**
|
|
6315
|
+
* Issue a real `SELECT COUNT(...)` for a model.
|
|
6316
|
+
*
|
|
6317
|
+
* Honors `where`, paranoid filtering, filtering (`required`) includes,
|
|
6318
|
+
* `distinct`/`col` and `group`:
|
|
6319
|
+
* - `col` picks the counted expression (`COUNT("col")`)
|
|
6320
|
+
* - `distinct: true` counts distinct values of `col`, or of the primary key
|
|
6321
|
+
* - `group` returns the number of groups
|
|
6322
|
+
*/
|
|
6323
|
+
async _countRows(model, modelName, options = {}) {
|
|
6324
|
+
if (!this.dialect) {
|
|
6325
|
+
throw new Error('Database not connected');
|
|
6326
|
+
}
|
|
6327
|
+
const dialect = this.dialect;
|
|
6328
|
+
const opts = options;
|
|
6329
|
+
const modelOptions = model.options;
|
|
6330
|
+
const isParanoid = modelOptions?.paranoid === true;
|
|
6331
|
+
const deletedAt = modelOptions?.deletedAt || 'deletedAt';
|
|
6332
|
+
let where = options.where;
|
|
6333
|
+
if (isParanoid && options.paranoid !== false && model.rawAttributes?.[deletedAt]) {
|
|
6334
|
+
where = { ...where, [deletedAt]: { $isNull: true } };
|
|
6335
|
+
}
|
|
6336
|
+
// Filtering includes restrict the counted set exactly as they restrict findAll.
|
|
6337
|
+
if (options.include && options.include.length > 0) {
|
|
6338
|
+
for (const rawInclude of options.include) {
|
|
6339
|
+
const filter = await (0, eager_load_1.resolveRequiredIncludeFilter)(this.eagerLoadContext(), model, modelName, rawInclude);
|
|
6340
|
+
if (!filter)
|
|
6341
|
+
continue;
|
|
6342
|
+
if (filter.values.length === 0)
|
|
6343
|
+
return 0;
|
|
6344
|
+
const existing = where?.[filter.parentAttr];
|
|
6345
|
+
where = {
|
|
6346
|
+
...where,
|
|
6347
|
+
[filter.parentAttr]: existing
|
|
6348
|
+
? { $and: [existing, { $in: filter.values }] }
|
|
6349
|
+
: { $in: filter.values },
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
const pkAttr = resolveModelPrimaryKeyAttr(model);
|
|
6354
|
+
const countCol = opts.col;
|
|
6355
|
+
const distinct = opts.distinct === true;
|
|
6356
|
+
let countExpr;
|
|
6357
|
+
if (distinct) {
|
|
6358
|
+
countExpr = `COUNT(DISTINCT ${dialect.escapeId(countCol || pkAttr)})`;
|
|
6359
|
+
}
|
|
6360
|
+
else if (countCol) {
|
|
6361
|
+
countExpr = `COUNT(${dialect.escapeId(countCol)})`;
|
|
6362
|
+
}
|
|
6363
|
+
else {
|
|
6364
|
+
countExpr = 'COUNT(*)';
|
|
6365
|
+
}
|
|
6366
|
+
const tableName = model.tableName;
|
|
6367
|
+
const qualified = model.schema
|
|
6368
|
+
? `${dialect.escapeId(model.schema)}.${dialect.escapeId(tableName)}`
|
|
6369
|
+
: dialect.escapeId(tableName);
|
|
6370
|
+
const groupFields = opts.group
|
|
6371
|
+
? (Array.isArray(opts.group) ? opts.group : [opts.group]).map((g) => typeof g === 'string' ? g : String(g))
|
|
6372
|
+
: undefined;
|
|
6373
|
+
let sql = `SELECT ${countExpr} AS ${dialect.escapeId('count')} FROM ${qualified}`;
|
|
6374
|
+
let values = [];
|
|
6375
|
+
if (where && Object.keys(where).length > 0) {
|
|
6376
|
+
const built = dialect.buildWhereClause(where);
|
|
6377
|
+
sql += ` WHERE ${built.sql}`;
|
|
6378
|
+
values = built.values;
|
|
6379
|
+
}
|
|
6380
|
+
if (groupFields?.length) {
|
|
6381
|
+
sql += ` GROUP BY ${groupFields.map((g) => dialect.escapeId(g)).join(', ')}`;
|
|
6382
|
+
}
|
|
6383
|
+
const finalSql = dialect.replaceReplacements(sql, values);
|
|
6384
|
+
const result = await dialect.query(finalSql);
|
|
6385
|
+
const rows = result.rows || [];
|
|
6386
|
+
// With GROUP BY the aggregate returns one row per group; the count is the
|
|
6387
|
+
// number of groups, matching how callers use `count({ group })`.
|
|
6388
|
+
if (groupFields?.length)
|
|
6389
|
+
return rows.length;
|
|
6390
|
+
if (!rows.length)
|
|
6391
|
+
return 0;
|
|
6392
|
+
const raw = rows[0].count ?? rows[0].COUNT ?? Object.values(rows[0])[0];
|
|
6393
|
+
return Number(raw) || 0;
|
|
6394
|
+
}
|
|
6395
|
+
/**
|
|
6396
|
+
* The view of this instance that the shared eager-loader needs.
|
|
6397
|
+
*/
|
|
6398
|
+
eagerLoadContext() {
|
|
6399
|
+
return {
|
|
6400
|
+
// Materialize to an array: the loader scans this once per include and
|
|
6401
|
+
// again for every nested level, and a Map iterator would be exhausted
|
|
6402
|
+
// after the first scan (leaving later includes with no associations to
|
|
6403
|
+
// match against).
|
|
6404
|
+
associations: Array.from(this.associations.values()),
|
|
6405
|
+
models: this._models,
|
|
6406
|
+
};
|
|
6407
|
+
}
|
|
6408
|
+
/**
|
|
6409
|
+
* Order models so that every table is created after the tables it references.
|
|
6410
|
+
*
|
|
6411
|
+
* Table creation used to follow `modelDefinitions` insertion order, i.e. the
|
|
6412
|
+
* order the user happened to call `define()` in, while `createTableFromModel`
|
|
6413
|
+
* simultaneously emitted REFERENCES clauses. On any dialect that validates
|
|
6414
|
+
* foreign keys at creation time, defining `Post` before `User` failed. The
|
|
6415
|
+
* ordering is derived from the association graph so declaration order stops
|
|
6416
|
+
* mattering.
|
|
6417
|
+
*
|
|
6418
|
+
* Cycles (two models referencing each other) can't be resolved by ordering
|
|
6419
|
+
* alone; those models are emitted last in declaration order, which is the same
|
|
6420
|
+
* behaviour as before for that case.
|
|
6421
|
+
*/
|
|
6422
|
+
orderModelsByDependency() {
|
|
6423
|
+
const entries = Array.from(this.modelDefinitions.entries());
|
|
6424
|
+
const byName = new Map(entries.map(([name]) => [name, true]));
|
|
6425
|
+
// dependsOn: model -> set of model names whose tables must exist first.
|
|
6426
|
+
const dependsOn = new Map();
|
|
6427
|
+
for (const [name] of entries)
|
|
6428
|
+
dependsOn.set(name, new Set());
|
|
6429
|
+
for (const assoc of this.associations.values()) {
|
|
6430
|
+
const a = assoc;
|
|
6431
|
+
const sourceName = a.source?.name;
|
|
6432
|
+
const targetName = a.target?.name;
|
|
6433
|
+
if (!sourceName || !targetName || sourceName === targetName)
|
|
6434
|
+
continue;
|
|
6435
|
+
if (!byName.has(sourceName) || !byName.has(targetName))
|
|
6436
|
+
continue;
|
|
6437
|
+
if (a.type === 'belongsTo') {
|
|
6438
|
+
// FK lives on the source, pointing at the target: target first.
|
|
6439
|
+
dependsOn.get(sourceName).add(targetName);
|
|
6440
|
+
}
|
|
6441
|
+
else if (a.type === 'hasMany' || a.type === 'hasOne') {
|
|
6442
|
+
// FK lives on the target, pointing at the source: source first.
|
|
6443
|
+
dependsOn.get(targetName).add(sourceName);
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
const ordered = [];
|
|
6447
|
+
const emitted = new Set();
|
|
6448
|
+
const defs = new Map(entries);
|
|
6449
|
+
// Repeatedly emit every model whose dependencies are already satisfied,
|
|
6450
|
+
// preserving declaration order within each pass for stable output.
|
|
6451
|
+
let progress = true;
|
|
6452
|
+
while (progress) {
|
|
6453
|
+
progress = false;
|
|
6454
|
+
for (const [name] of entries) {
|
|
6455
|
+
if (emitted.has(name))
|
|
6456
|
+
continue;
|
|
6457
|
+
const deps = dependsOn.get(name);
|
|
6458
|
+
let ready = true;
|
|
6459
|
+
for (const dep of deps) {
|
|
6460
|
+
if (!emitted.has(dep)) {
|
|
6461
|
+
ready = false;
|
|
6462
|
+
break;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
if (ready) {
|
|
6466
|
+
ordered.push([name, defs.get(name)]);
|
|
6467
|
+
emitted.add(name);
|
|
6468
|
+
progress = true;
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
// Anything left is part of a dependency cycle - emit in declaration order.
|
|
6473
|
+
for (const [name, def] of entries) {
|
|
6474
|
+
if (!emitted.has(name))
|
|
6475
|
+
ordered.push([name, def]);
|
|
6476
|
+
}
|
|
6477
|
+
return ordered;
|
|
6478
|
+
}
|
|
6058
6479
|
/**
|
|
6059
6480
|
* Default sync mode - creates missing tables only (safe)
|
|
6060
6481
|
*/
|
|
@@ -6062,16 +6483,14 @@ $$ LANGUAGE plpgsql;`;
|
|
|
6062
6483
|
if (!this.dialect) {
|
|
6063
6484
|
throw new Error('Database not connected');
|
|
6064
6485
|
}
|
|
6065
|
-
//
|
|
6066
|
-
|
|
6067
|
-
|
|
6486
|
+
// Read the existing table list once rather than once per model.
|
|
6487
|
+
const tables = new Set(await this.dialect.showTables());
|
|
6488
|
+
for (const [modelName, modelDef] of this.orderModelsByDependency()) {
|
|
6489
|
+
const tableName = deriveTableName(modelName, modelDef.options);
|
|
6068
6490
|
try {
|
|
6069
|
-
|
|
6070
|
-
const tables = await this.dialect.showTables();
|
|
6071
|
-
const tableExists = tables.includes(tableName);
|
|
6072
|
-
if (!tableExists) {
|
|
6073
|
-
// Create the table
|
|
6491
|
+
if (!tables.has(tableName)) {
|
|
6074
6492
|
await this.createTableFromModel(tableName, modelDef.attributes, modelDef.options, modelName);
|
|
6493
|
+
tables.add(tableName);
|
|
6075
6494
|
this.logger.info(`Created table: ${tableName}`);
|
|
6076
6495
|
}
|
|
6077
6496
|
else {
|
|
@@ -6103,9 +6522,9 @@ $$ LANGUAGE plpgsql;`;
|
|
|
6103
6522
|
this.logger.warn(`Failed to drop table ${tableName}`, { error });
|
|
6104
6523
|
}
|
|
6105
6524
|
}
|
|
6106
|
-
// Now create all tables fresh
|
|
6107
|
-
for (const [modelName, modelDef] of this.
|
|
6108
|
-
const tableName =
|
|
6525
|
+
// Now create all tables fresh, parents before children.
|
|
6526
|
+
for (const [modelName, modelDef] of this.orderModelsByDependency()) {
|
|
6527
|
+
const tableName = deriveTableName(modelName, modelDef.options);
|
|
6109
6528
|
try {
|
|
6110
6529
|
await this.createTableFromModel(tableName, modelDef.attributes, modelDef.options, modelName);
|
|
6111
6530
|
this.logger.info(`Created table: ${tableName}`);
|
|
@@ -6126,9 +6545,9 @@ $$ LANGUAGE plpgsql;`;
|
|
|
6126
6545
|
}
|
|
6127
6546
|
// Get all tables in the database
|
|
6128
6547
|
const existingTables = await this.dialect.showTables();
|
|
6129
|
-
// Process each model
|
|
6130
|
-
for (const [modelName, modelDef] of this.
|
|
6131
|
-
const tableName =
|
|
6548
|
+
// Process each model, parents before children.
|
|
6549
|
+
for (const [modelName, modelDef] of this.orderModelsByDependency()) {
|
|
6550
|
+
const tableName = deriveTableName(modelName, modelDef.options);
|
|
6132
6551
|
const modelAttributes = modelDef.attributes;
|
|
6133
6552
|
const modelOptions = modelDef.options;
|
|
6134
6553
|
try {
|