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.
Files changed (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,852 @@
1
+ "use strict";
2
+ /**
3
+ * Association classes for the ORM
4
+ * Supports hasOne, hasMany, belongsTo, and belongsToMany relationships
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BelongsToManyAssociation = exports.BelongsToAssociation = exports.HasManyAssociation = exports.HasOneAssociation = void 0;
8
+ exports.createAssociationMixins = createAssociationMixins;
9
+ exports.processNestedIncludes = processNestedIncludes;
10
+ // Helper to lower-case the first character of a model name so inferred foreign
11
+ // key column names follow the lowerCamelCase convention used everywhere else
12
+ // in this codebase (e.g. `userId`, not `UserId`). Every test that pre-defines
13
+ // a foreign key column and relies on an association's default inference
14
+ // (rather than passing an explicit `foreignKey` option) uses lowerCamelCase,
15
+ // and downstream lookups (e.g. eager-load default-key resolution in
16
+ // prorm.ts) already lowercase the first character - the association classes
17
+ // below were the one place still producing PascalCase defaults, so the
18
+ // inferred key never matched the actual (lowerCamelCase) column, silently
19
+ // breaking foreign-key constraint enforcement and default-association CRUD
20
+ // helpers (e.g. `createPost`) whenever no explicit `foreignKey` was given.
21
+ function lowerFirst(name) {
22
+ if (!name)
23
+ return name;
24
+ return name.charAt(0).toLowerCase() + name.slice(1);
25
+ }
26
+ // Helper to extract foreign key name(s) from various formats
27
+ // Supports composite foreign keys: foreignKey: ['field1', 'field2']
28
+ function extractForeignKeyName(foreignKey, defaultKey) {
29
+ if (!foreignKey) {
30
+ return defaultKey;
31
+ }
32
+ // Handle array format for composite foreign keys
33
+ if (Array.isArray(foreignKey)) {
34
+ return foreignKey;
35
+ }
36
+ if (typeof foreignKey === 'string') {
37
+ return foreignKey;
38
+ }
39
+ // Handle object form: { name: 'profile_id' } or { allowNull: false }
40
+ return foreignKey.name || defaultKey;
41
+ }
42
+ /**
43
+ * Check if foreign key is a composite key (array)
44
+ */
45
+ function isCompositeForeignKey(foreignKey) {
46
+ return Array.isArray(foreignKey) && foreignKey.length > 1;
47
+ }
48
+ /**
49
+ * Format foreign key for SQL (handles both single and composite).
50
+ * Always returns a parenthesized column list ready to embed directly after
51
+ * `FOREIGN KEY` / table name in a constraint clause, e.g. `(id)` or `(a, b)`.
52
+ */
53
+ function formatForeignKeyForSql(foreignKey) {
54
+ if (Array.isArray(foreignKey)) {
55
+ return `(${foreignKey.join(', ')})`;
56
+ }
57
+ return `(${foreignKey})`;
58
+ }
59
+ // Helper to extract allowNull from foreignKey options
60
+ function extractAllowNull(foreignKey, defaultValue = true) {
61
+ if (!foreignKey || typeof foreignKey === 'string') {
62
+ return defaultValue;
63
+ }
64
+ return foreignKey.allowNull ?? defaultValue;
65
+ }
66
+ // Base Association class
67
+ class BaseAssociation {
68
+ constructor(source, target, options = {}) {
69
+ this.source = source;
70
+ this.target = target;
71
+ // Handle string, array, and object forms of foreignKey
72
+ // e.g., 'profile_id' or ['order_id', 'product_id'] or { name: 'profile_id', allowNull: false }
73
+ this.foreignKey = extractForeignKeyName(options.foreignKey, this.inferForeignKey());
74
+ this.as = options.as || this.target.name.toLowerCase();
75
+ this.options = options;
76
+ this.type = this.getType();
77
+ }
78
+ /**
79
+ * Get the association config for queries
80
+ */
81
+ getAssociationConfig() {
82
+ return {
83
+ model: this.target,
84
+ as: this.as,
85
+ foreignKey: this.foreignKey,
86
+ targetKey: this.options.targetKey,
87
+ sourceKey: this.options.sourceKey,
88
+ constraints: this.options.constraints,
89
+ onDelete: this.options.onDelete,
90
+ onUpdate: this.options.onUpdate,
91
+ };
92
+ }
93
+ }
94
+ /**
95
+ * Flatten nested includes to a flat array with path information
96
+ */
97
+ class HasOneAssociation extends BaseAssociation {
98
+ constructor(source, target, options = {}) {
99
+ super(source, target, options);
100
+ }
101
+ inferForeignKey() {
102
+ // Default foreign key is target model name + Id (e.g., userId)
103
+ return `${lowerFirst(this.target.name)}Id`;
104
+ }
105
+ getType() {
106
+ return 'hasOne';
107
+ }
108
+ /**
109
+ * Get the foreign key constraint SQL
110
+ */
111
+ getConstraintSql(tableName) {
112
+ const targetKey = this.options.targetKey || 'id';
113
+ // Handle composite foreign keys (string | string[])
114
+ const fk = formatForeignKeyForSql(this.foreignKey);
115
+ const tk = formatForeignKeyForSql(targetKey);
116
+ let sql = `FOREIGN KEY ${fk} REFERENCES ${tableName}${tk}`;
117
+ if (this.options.onDelete) {
118
+ sql += ` ON DELETE ${this.options.onDelete}`;
119
+ }
120
+ if (this.options.onUpdate) {
121
+ sql += ` ON UPDATE ${this.options.onUpdate}`;
122
+ }
123
+ return sql;
124
+ }
125
+ }
126
+ exports.HasOneAssociation = HasOneAssociation;
127
+ // HasMany Association - one-to-many where source has many targets
128
+ class HasManyAssociation extends BaseAssociation {
129
+ constructor(source, target, options = {}) {
130
+ super(source, target, options);
131
+ // Override to use plural form for hasMany
132
+ if (!options.as) {
133
+ this.as = this.target.name.toLowerCase() + 's';
134
+ }
135
+ }
136
+ inferForeignKey() {
137
+ return `${lowerFirst(this.source.name)}Id`;
138
+ }
139
+ getType() {
140
+ return 'hasMany';
141
+ }
142
+ /**
143
+ * Get the foreign key constraint SQL
144
+ */
145
+ getConstraintSql(tableName) {
146
+ const targetKey = this.options.targetKey || 'id';
147
+ const fk = formatForeignKeyForSql(this.foreignKey);
148
+ const tk = formatForeignKeyForSql(targetKey);
149
+ let sql = `FOREIGN KEY ${fk} REFERENCES ${tableName}${tk}`;
150
+ if (this.options.onDelete) {
151
+ sql += ` ON DELETE ${this.options.onDelete}`;
152
+ }
153
+ if (this.options.onUpdate) {
154
+ sql += ` ON UPDATE ${this.options.onUpdate}`;
155
+ }
156
+ return sql;
157
+ }
158
+ }
159
+ exports.HasManyAssociation = HasManyAssociation;
160
+ // BelongsTo Association - one-to-one where source belongs to target
161
+ class BelongsToAssociation extends BaseAssociation {
162
+ constructor(source, target, options = {}) {
163
+ super(source, target, options);
164
+ }
165
+ inferForeignKey() {
166
+ // Default foreign key is target model name + Id (e.g., userId)
167
+ return `${lowerFirst(this.target.name)}Id`;
168
+ }
169
+ getType() {
170
+ return 'belongsTo';
171
+ }
172
+ /**
173
+ * Get the foreign key constraint SQL with onDelete and onUpdate support
174
+ */
175
+ getConstraintSql(tableName) {
176
+ const targetKey = this.options.targetKey || 'id';
177
+ const fk = formatForeignKeyForSql(this.foreignKey);
178
+ const tk = formatForeignKeyForSql(targetKey);
179
+ let sql = `FOREIGN KEY ${fk} REFERENCES ${tableName}${tk}`;
180
+ if (this.options.onDelete) {
181
+ sql += ` ON DELETE ${this.options.onDelete}`;
182
+ }
183
+ if (this.options.onUpdate) {
184
+ sql += ` ON UPDATE ${this.options.onUpdate}`;
185
+ }
186
+ return sql;
187
+ }
188
+ /**
189
+ * Get the full foreignKey options including allowNull from object form
190
+ */
191
+ getForeignKeyOptions() {
192
+ const fk = this.options.foreignKey;
193
+ if (!fk || typeof fk === 'string') {
194
+ return undefined;
195
+ }
196
+ return fk;
197
+ }
198
+ }
199
+ exports.BelongsToAssociation = BelongsToAssociation;
200
+ // BelongsToMany Association - many-to-many through a junction table
201
+ class BelongsToManyAssociation extends BaseAssociation {
202
+ constructor(source, target, options = {}) {
203
+ super(source, target, options);
204
+ this.throughModel = null;
205
+ // Override to use plural form for belongsToMany
206
+ if (!options.as) {
207
+ this.as = this.target.name.toLowerCase() + 's';
208
+ }
209
+ // Determine the through table/model
210
+ if (typeof options.through === 'string') {
211
+ this.through = options.through;
212
+ }
213
+ else if (options.through && typeof options.through === 'object') {
214
+ // `options.through` is typed as `string | ThroughOptions`, but at runtime callers may
215
+ // also pass a full ModelStatic directly (e.g. `through: UserProject`). Treat it as an
216
+ // untyped record here so we can duck-type which shape we actually received, then cast
217
+ // to the appropriate typed value for each branch below (avoids invalid casts to
218
+ // ModelStatic<any> from a non-overlapping ThroughOptions type).
219
+ const throughVal = options.through;
220
+ // Check if through is a model directly (not an options object)
221
+ if (throughVal.name && throughVal.rawAttributes) {
222
+ // It's a model
223
+ this.through = throughVal.tableName || `${source.name}${target.name}`;
224
+ this.throughModel = throughVal;
225
+ }
226
+ else {
227
+ // It's an options object (ThroughOptions)
228
+ const throughOptions = throughVal;
229
+ this.through =
230
+ typeof throughOptions.model === 'string'
231
+ ? throughOptions.model
232
+ : throughOptions.model?.tableName ||
233
+ `${source.name}${target.name}`;
234
+ this.throughModel = throughOptions.model || null;
235
+ }
236
+ }
237
+ else if (options.through) {
238
+ // Handle case where through is passed directly as a model
239
+ this.through = options.through.tableName || `${source.name}${target.name}`;
240
+ this.throughModel = options.through;
241
+ }
242
+ else {
243
+ // Default through table name
244
+ this.through = `${source.name}${target.name}`;
245
+ }
246
+ // Other key in the junction table. Supports both string ('roleId') and object
247
+ // ({ name: 'roleId', allowNull: false }) forms, same as top-level foreignKey.
248
+ const throughOtherKey = options.through && typeof options.through === 'object'
249
+ ? options.through.otherKey
250
+ : undefined;
251
+ // throughOtherKey can only be string | AssociationForeignKeyOptions (no composite array form),
252
+ // so the result of extractForeignKeyName is always a plain string here.
253
+ this._otherKey = extractForeignKeyName(throughOtherKey, `${lowerFirst(target.name)}Id`);
254
+ }
255
+ inferForeignKey() {
256
+ return `${lowerFirst(this.source.name)}Id`;
257
+ }
258
+ getType() {
259
+ return 'belongsToMany';
260
+ }
261
+ getThrough() {
262
+ return this.through;
263
+ }
264
+ getOtherKey() {
265
+ return this._otherKey;
266
+ }
267
+ setThroughModel(model) {
268
+ this.throughModel = model;
269
+ }
270
+ getThroughModel() {
271
+ return this.throughModel;
272
+ }
273
+ }
274
+ exports.BelongsToManyAssociation = BelongsToManyAssociation;
275
+ // Factory function to create association mixin methods
276
+ function createAssociationMixins(source, sourceModelName, instancePrototype) {
277
+ // Use provided instance prototype, or fall back to source.prototype, or source itself
278
+ const prototypeTarget = instancePrototype || source.prototype || source;
279
+ return {
280
+ hasOne: (target, options = {}) => {
281
+ const association = new HasOneAssociation(source, target, options);
282
+ // Add to source associations
283
+ source.associations[association.as] = association;
284
+ // Get the prototype target - use source.prototype if it exists, otherwise use source
285
+ // Add the getter to the source model
286
+ prototypeTarget[`get${capitalize(association.as)}`] = function (options) {
287
+ const pk = getPrimaryKeyValue(this);
288
+ return target.findOne({
289
+ where: { [association.foreignKey]: pk },
290
+ ...options,
291
+ });
292
+ };
293
+ // Add the setter - supports instance, null, or foreign key value
294
+ prototypeTarget[`set${capitalize(association.as)}`] = async function (value, options) {
295
+ const pk = getPrimaryKeyValue(this);
296
+ // Handle different value types
297
+ if (value === null) {
298
+ // Remove association by setting foreign key to null
299
+ await target.update({ [association.foreignKey]: null }, { where: { [association.options.targetKey || 'id']: pk }, ...options });
300
+ }
301
+ else if (typeof value === 'object' &&
302
+ value !== null &&
303
+ value[association.options.targetKey || 'id'] !== undefined) {
304
+ // It's an instance - set the foreign key to its primary key
305
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
306
+ await target.update({ [association.foreignKey]: pk }, { where: { [association.options.targetKey || 'id']: targetPk }, ...options });
307
+ }
308
+ else {
309
+ // It's a raw foreign key value (number/string)
310
+ await target.update({ [association.foreignKey]: value }, { where: { [association.options.targetKey || 'id']: pk }, ...options });
311
+ }
312
+ };
313
+ // Add the create method - creates and associates in one step
314
+ prototypeTarget[`create${capitalize(association.as)}`] = async function (values, options) {
315
+ const pk = getPrimaryKeyValue(this);
316
+ return target.create({ ...values, [association.foreignKey]: pk }, options);
317
+ };
318
+ // Add the has method - check if associated record exists
319
+ prototypeTarget[`has${capitalize(association.as)}`] = async function (options) {
320
+ const pk = getPrimaryKeyValue(this);
321
+ const count = await target.count({
322
+ where: { [association.foreignKey]: pk },
323
+ ...options,
324
+ });
325
+ return count > 0;
326
+ };
327
+ // Add the count method - count associated records (will be 0 or 1 for hasOne)
328
+ prototypeTarget[`count${capitalize(association.as)}`] = async function (options) {
329
+ const pk = getPrimaryKeyValue(this);
330
+ return target.count({ where: { [association.foreignKey]: pk }, ...options });
331
+ };
332
+ return association;
333
+ },
334
+ hasMany: (target, options = {}) => {
335
+ const association = new HasManyAssociation(source, target, options);
336
+ // Add to source associations
337
+ source.associations[association.as] = association;
338
+ // Get the prototype target - use source.prototype if it exists, otherwise use source
339
+ // Add the getter
340
+ prototypeTarget[`get${capitalize(association.as)}`] = function (options) {
341
+ const pk = getPrimaryKeyValue(this);
342
+ return target.findAll({
343
+ where: { [association.foreignKey]: pk },
344
+ ...options,
345
+ });
346
+ };
347
+ // Add the setter
348
+ prototypeTarget[`set${capitalize(association.as)}`] = async function (values, options) {
349
+ const pk = getPrimaryKeyValue(this);
350
+ // Remove existing and add new
351
+ await target.destroy({ where: { [association.foreignKey]: pk }, ...options });
352
+ for (const value of values) {
353
+ await target.create({ ...value, [association.foreignKey]: pk }, options);
354
+ }
355
+ };
356
+ // Add the add method
357
+ prototypeTarget[`add${capitalize(association.as)}`] = async function (values, options) {
358
+ const pk = getPrimaryKeyValue(this);
359
+ if (Array.isArray(values)) {
360
+ for (const value of values) {
361
+ await target.update({ [association.foreignKey]: pk }, { where: { [association.options.targetKey || 'id']: value }, ...options });
362
+ }
363
+ }
364
+ else {
365
+ await target.update({ [association.foreignKey]: pk }, { where: { [association.options.targetKey || 'id']: values }, ...options });
366
+ }
367
+ };
368
+ // Add the create method
369
+ prototypeTarget[`create${capitalize(association.as)}`] = async function (values, options) {
370
+ const pk = getPrimaryKeyValue(this);
371
+ return target.create({ ...values, [association.foreignKey]: pk }, options);
372
+ };
373
+ // Add the remove method
374
+ prototypeTarget[`remove${capitalize(association.as)}`] = async function (values, options) {
375
+ const pk = getPrimaryKeyValue(this);
376
+ if (Array.isArray(values)) {
377
+ for (const value of values) {
378
+ await target.update({ [association.foreignKey]: null }, { where: { [association.options.targetKey || 'id']: value }, ...options });
379
+ }
380
+ }
381
+ else {
382
+ await target.update({ [association.foreignKey]: null }, { where: { [association.options.targetKey || 'id']: values }, ...options });
383
+ }
384
+ };
385
+ // Add the count method
386
+ prototypeTarget[`count${capitalize(association.as)}`] = async function (options) {
387
+ const pk = getPrimaryKeyValue(this);
388
+ return target.count({ where: { [association.foreignKey]: pk }, ...options });
389
+ };
390
+ // Add the has method - check if instance has associated record(s)
391
+ prototypeTarget[`has${capitalize(association.as)}`] = async function (values, options) {
392
+ const pk = getPrimaryKeyValue(this);
393
+ if (values === undefined) {
394
+ // Check if any associated record exists
395
+ const count = await target.count({
396
+ where: { [association.foreignKey]: pk },
397
+ ...options,
398
+ });
399
+ return count > 0;
400
+ }
401
+ // Check if specific value(s) are associated
402
+ if (Array.isArray(values)) {
403
+ for (const value of values) {
404
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
405
+ const exists = await target.findOne({
406
+ where: {
407
+ [association.options.targetKey || 'id']: targetPk,
408
+ [association.foreignKey]: pk,
409
+ },
410
+ ...options,
411
+ });
412
+ if (!exists)
413
+ return false;
414
+ }
415
+ return true;
416
+ }
417
+ else {
418
+ const targetPk = values[association.options.targetKey || 'id'] || values;
419
+ const exists = await target.findOne({
420
+ where: {
421
+ [association.options.targetKey || 'id']: targetPk,
422
+ [association.foreignKey]: pk,
423
+ },
424
+ ...options,
425
+ });
426
+ return exists !== null;
427
+ }
428
+ };
429
+ // Add singular alias for add (e.g., addRole for addRoles)
430
+ const singularAs = association.as.replace(/s$/, '').replace(/ies$/, 'y');
431
+ prototypeTarget[`add${capitalize(singularAs)}`] = async function (value, options) {
432
+ const pk = getPrimaryKeyValue(this);
433
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
434
+ await target.update({ [association.foreignKey]: pk }, { where: { [association.options.targetKey || 'id']: targetPk }, ...options });
435
+ };
436
+ // Add singular alias for remove (e.g., removeRole for removeRoles)
437
+ prototypeTarget[`remove${capitalize(singularAs)}`] = async function (value, options) {
438
+ const pk = getPrimaryKeyValue(this);
439
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
440
+ await target.update({ [association.foreignKey]: null }, { where: { [association.options.targetKey || 'id']: targetPk }, ...options });
441
+ };
442
+ // Add singular alias for has (e.g., hasRole for hasRoles)
443
+ prototypeTarget[`has${capitalize(singularAs)}`] = async function (value, options) {
444
+ const pk = getPrimaryKeyValue(this);
445
+ const targetPk = value[association.options.targetKey || 'id'] || value;
446
+ const exists = await target.findOne({
447
+ where: {
448
+ [association.options.targetKey || 'id']: targetPk,
449
+ [association.foreignKey]: pk,
450
+ },
451
+ ...options,
452
+ });
453
+ return exists !== null;
454
+ };
455
+ // Add singular alias for create (e.g., createRole for createRoles)
456
+ prototypeTarget[`create${capitalize(singularAs)}`] = async function (values, options) {
457
+ const pk = getPrimaryKeyValue(this);
458
+ return target.create({ ...values, [association.foreignKey]: pk }, options);
459
+ };
460
+ return association;
461
+ },
462
+ belongsTo: (target, options = {}) => {
463
+ const association = new BelongsToAssociation(source, target, options);
464
+ // Add to source associations
465
+ source.associations[association.as] = association;
466
+ // Get the prototype target - use source.prototype if it exists, otherwise use source
467
+ // Add the getter
468
+ prototypeTarget[`get${capitalize(association.as)}`] = function (options) {
469
+ const fkValue = this.getDataValue(association.foreignKey);
470
+ return target.findOne({
471
+ where: { [association.options.targetKey || 'id']: fkValue },
472
+ ...options,
473
+ });
474
+ };
475
+ // Add the setter
476
+ prototypeTarget[`set${capitalize(association.as)}`] = async function (value, options) {
477
+ const pk = value ? value[association.options.targetKey || 'id'] || value.id : null;
478
+ this.setDataValue(association.foreignKey, pk);
479
+ if (options?.save !== false) {
480
+ await this.save(options);
481
+ }
482
+ };
483
+ // Add the create method
484
+ prototypeTarget[`create${capitalize(association.as)}`] = async function (values, options) {
485
+ const created = await target.create(values, options);
486
+ this.setDataValue(association.foreignKey, created[association.options.targetKey || 'id']);
487
+ if (options?.save !== false) {
488
+ await this.save(options);
489
+ }
490
+ return created;
491
+ };
492
+ // Add the count method - count associated records (will be 0 or 1 for belongsTo)
493
+ prototypeTarget[`count${capitalize(association.as)}`] = async function (options) {
494
+ const fkValue = this.getDataValue(association.foreignKey);
495
+ if (fkValue === null || fkValue === undefined) {
496
+ return 0;
497
+ }
498
+ return target.count({
499
+ where: { [association.options.targetKey || 'id']: fkValue },
500
+ ...options,
501
+ });
502
+ };
503
+ // Add the has method - check if has associated record
504
+ prototypeTarget[`has${capitalize(association.as)}`] = async function (options) {
505
+ const fkValue = this.getDataValue(association.foreignKey);
506
+ if (fkValue === null || fkValue === undefined) {
507
+ return false;
508
+ }
509
+ const count = await target.count({
510
+ where: { [association.options.targetKey || 'id']: fkValue },
511
+ ...options,
512
+ });
513
+ return count > 0;
514
+ };
515
+ return association;
516
+ },
517
+ belongsToMany: (target, options = {}) => {
518
+ const association = new BelongsToManyAssociation(source, target, options);
519
+ // Add to source associations
520
+ source.associations[association.as] = association;
521
+ // Create the through model if needed (use user-provided model if available)
522
+ let throughModel = association.getThroughModel();
523
+ if (!throughModel) {
524
+ throughModel = createThroughModel(source, target, association);
525
+ association.setThroughModel(throughModel);
526
+ }
527
+ // Get the prototype target - use source.prototype if it exists, otherwise use source
528
+ // Add the getter
529
+ prototypeTarget[`get${capitalize(association.as)}`] = function (options) {
530
+ const pk = getPrimaryKeyValue(this);
531
+ return target.findAll({
532
+ include: [
533
+ {
534
+ model: throughModel,
535
+ where: { [association.foreignKey]: pk },
536
+ as: association.getThrough(),
537
+ },
538
+ ],
539
+ ...options,
540
+ });
541
+ };
542
+ // Add the setter
543
+ prototypeTarget[`set${capitalize(association.as)}`] = async function (values, options) {
544
+ const pk = getPrimaryKeyValue(this);
545
+ // Remove existing associations
546
+ await throughModel.destroy({
547
+ where: { [association.foreignKey]: pk },
548
+ ...options,
549
+ });
550
+ // Add new associations
551
+ for (const value of values) {
552
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
553
+ await throughModel.create({
554
+ [association.foreignKey]: pk,
555
+ [association.getOtherKey()]: targetPk,
556
+ }, options);
557
+ }
558
+ };
559
+ // Add the add method
560
+ prototypeTarget[`add${capitalize(association.as)}`] = async function (values, options) {
561
+ const pk = getPrimaryKeyValue(this);
562
+ if (Array.isArray(values)) {
563
+ for (const value of values) {
564
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
565
+ await throughModel.findOrCreate({
566
+ where: {
567
+ [association.foreignKey]: pk,
568
+ [association.getOtherKey()]: targetPk,
569
+ },
570
+ ...options,
571
+ });
572
+ }
573
+ }
574
+ else {
575
+ const targetPk = values[association.options.targetKey || 'id'] || values.id;
576
+ await throughModel.findOrCreate({
577
+ where: {
578
+ [association.foreignKey]: pk,
579
+ [association.getOtherKey()]: targetPk,
580
+ },
581
+ ...options,
582
+ });
583
+ }
584
+ };
585
+ // Add the remove method
586
+ prototypeTarget[`remove${capitalize(association.as)}`] = async function (values, options) {
587
+ const pk = getPrimaryKeyValue(this);
588
+ if (Array.isArray(values)) {
589
+ for (const value of values) {
590
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
591
+ await throughModel.destroy({
592
+ where: {
593
+ [association.foreignKey]: pk,
594
+ [association.getOtherKey()]: targetPk,
595
+ },
596
+ ...options,
597
+ });
598
+ }
599
+ }
600
+ else {
601
+ const targetPk = values[association.options.targetKey || 'id'] || values.id;
602
+ await throughModel.destroy({
603
+ where: {
604
+ [association.foreignKey]: pk,
605
+ [association.getOtherKey()]: targetPk,
606
+ },
607
+ ...options,
608
+ });
609
+ }
610
+ };
611
+ // Add the create method
612
+ prototypeTarget[`create${capitalize(association.as)}`] = async function (values, options) {
613
+ const pk = getPrimaryKeyValue(this);
614
+ const created = await target.create(values, options);
615
+ await throughModel.create({
616
+ [association.foreignKey]: pk,
617
+ [association.getOtherKey()]: created[association.options.targetKey || 'id'],
618
+ }, options);
619
+ return created;
620
+ };
621
+ // Add the count method
622
+ prototypeTarget[`count${capitalize(association.as)}`] = async function (options) {
623
+ const pk = getPrimaryKeyValue(this);
624
+ return throughModel.count({
625
+ where: { [association.foreignKey]: pk },
626
+ ...options,
627
+ });
628
+ };
629
+ // Add the has method - check if instance has associated record(s) through junction table
630
+ prototypeTarget[`has${capitalize(association.as)}`] = async function (values, options) {
631
+ const pk = getPrimaryKeyValue(this);
632
+ if (values === undefined) {
633
+ // Check if any associated record exists
634
+ const count = await throughModel.count({
635
+ where: { [association.foreignKey]: pk },
636
+ ...options,
637
+ });
638
+ return count > 0;
639
+ }
640
+ // Check if specific value(s) are associated
641
+ if (Array.isArray(values)) {
642
+ for (const value of values) {
643
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
644
+ const exists = await throughModel.findOne({
645
+ where: { [association.foreignKey]: pk, [association.getOtherKey()]: targetPk },
646
+ ...options,
647
+ });
648
+ if (!exists)
649
+ return false;
650
+ }
651
+ return true;
652
+ }
653
+ else {
654
+ const targetPk = values[association.options.targetKey || 'id'] || values;
655
+ const exists = await throughModel.findOne({
656
+ where: { [association.foreignKey]: pk, [association.getOtherKey()]: targetPk },
657
+ ...options,
658
+ });
659
+ return exists !== null;
660
+ }
661
+ };
662
+ // Add singular alias for add (e.g., addRole for addRoles)
663
+ const btmSingularAs = association.as.replace(/s$/, '').replace(/ies$/, 'y');
664
+ prototypeTarget[`add${capitalize(btmSingularAs)}`] = async function (value, options) {
665
+ const pk = getPrimaryKeyValue(this);
666
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
667
+ await throughModel.findOrCreate({
668
+ where: {
669
+ [association.foreignKey]: pk,
670
+ [association.getOtherKey()]: targetPk,
671
+ },
672
+ ...options,
673
+ });
674
+ };
675
+ // Add singular alias for remove (e.g., removeRole for removeRoles)
676
+ prototypeTarget[`remove${capitalize(btmSingularAs)}`] = async function (value, options) {
677
+ const pk = getPrimaryKeyValue(this);
678
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
679
+ await throughModel.destroy({
680
+ where: {
681
+ [association.foreignKey]: pk,
682
+ [association.getOtherKey()]: targetPk,
683
+ },
684
+ ...options,
685
+ });
686
+ };
687
+ // Add singular alias for has (e.g., hasRole for hasRoles)
688
+ prototypeTarget[`has${capitalize(btmSingularAs)}`] = async function (value, options) {
689
+ const pk = getPrimaryKeyValue(this);
690
+ const targetPk = value[association.options.targetKey || 'id'] || value;
691
+ const exists = await throughModel.findOne({
692
+ where: { [association.foreignKey]: pk, [association.getOtherKey()]: targetPk },
693
+ ...options,
694
+ });
695
+ return exists !== null;
696
+ };
697
+ // Add singular alias for create (e.g., createRole for createRoles)
698
+ prototypeTarget[`create${capitalize(btmSingularAs)}`] = async function (values, options) {
699
+ const pk = getPrimaryKeyValue(this);
700
+ const created = await target.create(values, options);
701
+ await throughModel.create({
702
+ [association.foreignKey]: pk,
703
+ [association.getOtherKey()]: created[association.options.targetKey || 'id'],
704
+ }, options);
705
+ return created;
706
+ };
707
+ // Add the addModel method - alias for add (adds associated records)
708
+ prototypeTarget[`add${capitalize(association.as)}Model`] = async function (values, options) {
709
+ const pk = getPrimaryKeyValue(this);
710
+ if (Array.isArray(values)) {
711
+ for (const value of values) {
712
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
713
+ await throughModel.findOrCreate({
714
+ where: {
715
+ [association.foreignKey]: pk,
716
+ [association.getOtherKey()]: targetPk,
717
+ },
718
+ ...options,
719
+ });
720
+ }
721
+ }
722
+ else {
723
+ const targetPk = values[association.options.targetKey || 'id'] || values.id;
724
+ await throughModel.findOrCreate({
725
+ where: {
726
+ [association.foreignKey]: pk,
727
+ [association.getOtherKey()]: targetPk,
728
+ },
729
+ ...options,
730
+ });
731
+ }
732
+ };
733
+ // Add the removeModel method - alias for remove (removes associated records)
734
+ prototypeTarget[`remove${capitalize(association.as)}Model`] = async function (values, options) {
735
+ const pk = getPrimaryKeyValue(this);
736
+ if (Array.isArray(values)) {
737
+ for (const value of values) {
738
+ const targetPk = value[association.options.targetKey || 'id'] || value.id;
739
+ await throughModel.destroy({
740
+ where: {
741
+ [association.foreignKey]: pk,
742
+ [association.getOtherKey()]: targetPk,
743
+ },
744
+ ...options,
745
+ });
746
+ }
747
+ }
748
+ else {
749
+ const targetPk = values[association.options.targetKey || 'id'] || values.id;
750
+ await throughModel.destroy({
751
+ where: {
752
+ [association.foreignKey]: pk,
753
+ [association.getOtherKey()]: targetPk,
754
+ },
755
+ ...options,
756
+ });
757
+ }
758
+ };
759
+ return association;
760
+ },
761
+ };
762
+ }
763
+ // Helper to create a through model for belongsToMany
764
+ function createThroughModel(source, target, association) {
765
+ const throughName = `${source.name}${target.name}`;
766
+ return {
767
+ name: throughName,
768
+ tableName: association.getThrough(),
769
+ rawAttributes: {
770
+ [association.foreignKey]: {
771
+ type: 'INTEGER',
772
+ allowNull: false,
773
+ references: { model: source.name, key: 'id' },
774
+ },
775
+ [association.getOtherKey()]: {
776
+ type: 'INTEGER',
777
+ allowNull: false,
778
+ references: { model: target.name, key: 'id' },
779
+ },
780
+ },
781
+ associations: {},
782
+ };
783
+ }
784
+ // Helper to get primary key value from an instance
785
+ function getPrimaryKeyValue(instance) {
786
+ // Prefer the model's declared primary key attribute (models can define a custom
787
+ // primary key, e.g. `sku`, not just `id`). Falls back to name-guessing below for
788
+ // instances that don't expose their model's rawAttributes.
789
+ const rawAttributes = instance?.model?.rawAttributes || instance?.constructor?.rawAttributes;
790
+ if (rawAttributes) {
791
+ for (const [attrName, attr] of Object.entries(rawAttributes)) {
792
+ if (attr?.primaryKey) {
793
+ if (instance.getDataValue && instance.getDataValue(attrName) !== undefined) {
794
+ return instance.getDataValue(attrName);
795
+ }
796
+ if (instance[attrName] !== undefined) {
797
+ return instance[attrName];
798
+ }
799
+ }
800
+ }
801
+ }
802
+ // Try common primary key names
803
+ for (const key of ['id', 'Id', 'ID', 'uuid', 'UUID']) {
804
+ if (instance.getDataValue && instance.getDataValue(key) !== undefined) {
805
+ return instance.getDataValue(key);
806
+ }
807
+ if (instance[key] !== undefined) {
808
+ return instance[key];
809
+ }
810
+ }
811
+ return instance.id;
812
+ }
813
+ // Helper to capitalize first letter
814
+ function capitalize(str) {
815
+ return str.charAt(0).toUpperCase() + str.slice(1);
816
+ }
817
+ // ==================== Polymorphic Associations ====================
818
+ /**
819
+ * Options for polymorphic associations
820
+ */
821
+ function processNestedIncludes(includes, parentModel = null, parentAlias = '') {
822
+ const result = [];
823
+ function processInclude(inc, parent, path, level) {
824
+ const model = inc.model;
825
+ const tableName = model?.tableName || model?.name || '';
826
+ const alias = inc.as || tableName;
827
+ const fullPath = path ? `${path}->${alias}` : alias;
828
+ result.push({
829
+ include: inc,
830
+ parentAlias: parent,
831
+ path: fullPath,
832
+ nestedLevel: level,
833
+ });
834
+ // Process nested includes
835
+ if (inc.include && Array.isArray(inc.include)) {
836
+ for (const nested of inc.include) {
837
+ processInclude(nested, fullPath, fullPath, level + 1);
838
+ }
839
+ }
840
+ }
841
+ for (const inc of includes) {
842
+ processInclude(inc, parentAlias, '', 0);
843
+ }
844
+ return result;
845
+ }
846
+ exports.default = {
847
+ HasOneAssociation,
848
+ HasManyAssociation,
849
+ BelongsToAssociation,
850
+ BelongsToManyAssociation,
851
+ createAssociationMixins,
852
+ };