metal-orm 1.0.15 → 1.0.16

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 (43) hide show
  1. package/README.md +34 -27
  2. package/dist/decorators/index.cjs +339 -153
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -5
  5. package/dist/decorators/index.d.ts +1 -5
  6. package/dist/decorators/index.js +339 -153
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +838 -484
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +17 -14
  11. package/dist/index.d.ts +17 -14
  12. package/dist/index.js +833 -483
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-Bkv8g8u_.d.cts → select-BKZrMRCQ.d.cts} +363 -28
  15. package/dist/{select-Bkv8g8u_.d.ts → select-BKZrMRCQ.d.ts} +363 -28
  16. package/package.json +1 -1
  17. package/src/codegen/naming-strategy.ts +64 -0
  18. package/src/codegen/typescript.ts +48 -53
  19. package/src/core/ddl/schema-generator.ts +3 -2
  20. package/src/core/ddl/schema-introspect.ts +1 -1
  21. package/src/core/dialect/abstract.ts +28 -0
  22. package/src/decorators/column.ts +13 -4
  23. package/src/index.ts +8 -1
  24. package/src/orm/entity-context.ts +30 -0
  25. package/src/orm/entity-meta.ts +2 -2
  26. package/src/orm/entity-metadata.ts +1 -6
  27. package/src/orm/entity.ts +88 -88
  28. package/src/orm/execute.ts +42 -25
  29. package/src/orm/execution-context.ts +12 -0
  30. package/src/orm/hydration-context.ts +14 -0
  31. package/src/orm/identity-map.ts +4 -0
  32. package/src/orm/interceptor-pipeline.ts +29 -0
  33. package/src/orm/lazy-batch.ts +6 -6
  34. package/src/orm/orm-session.ts +234 -0
  35. package/src/orm/orm.ts +58 -0
  36. package/src/orm/relation-change-processor.ts +5 -1
  37. package/src/orm/relations/belongs-to.ts +45 -44
  38. package/src/orm/relations/has-many.ts +44 -43
  39. package/src/orm/relations/has-one.ts +140 -139
  40. package/src/orm/relations/many-to-many.ts +46 -45
  41. package/src/orm/unit-of-work.ts +6 -1
  42. package/src/query-builder/select.ts +509 -3
  43. package/src/orm/orm-context.ts +0 -159
package/dist/index.js CHANGED
@@ -542,7 +542,7 @@ var StandardFunctionStrategy = class {
542
542
  };
543
543
 
544
544
  // src/core/dialect/abstract.ts
545
- var Dialect = class {
545
+ var Dialect = class _Dialect {
546
546
  /**
547
547
  * Compiles a SELECT query AST to SQL
548
548
  * @param ast - Query AST to compile
@@ -715,6 +715,35 @@ var Dialect = class {
715
715
  this.registerDefaultOperandCompilers();
716
716
  this.registerDefaultExpressionCompilers();
717
717
  }
718
+ /**
719
+ * Creates a new Dialect instance (for testing purposes)
720
+ * @param functionStrategy - Optional function strategy
721
+ * @returns New Dialect instance
722
+ */
723
+ static create(functionStrategy) {
724
+ class TestDialect extends _Dialect {
725
+ constructor() {
726
+ super(...arguments);
727
+ this.dialect = "sqlite";
728
+ }
729
+ quoteIdentifier(id) {
730
+ return `"${id}"`;
731
+ }
732
+ compileSelectAst() {
733
+ throw new Error("Not implemented");
734
+ }
735
+ compileInsertAst() {
736
+ throw new Error("Not implemented");
737
+ }
738
+ compileUpdateAst() {
739
+ throw new Error("Not implemented");
740
+ }
741
+ compileDeleteAst() {
742
+ throw new Error("Not implemented");
743
+ }
744
+ }
745
+ return new TestDialect(functionStrategy);
746
+ }
718
747
  /**
719
748
  * Registers an expression compiler for a specific node type
720
749
  * @param type - Expression node type
@@ -4107,31 +4136,43 @@ var flattenResults = (results) => {
4107
4136
  }
4108
4137
  return rows;
4109
4138
  };
4110
- async function executeHydrated(ctx, qb) {
4139
+ var executeWithEntityContext = async (entityCtx, qb) => {
4111
4140
  const ast = qb.getAST();
4112
- const compiled = ctx.dialect.compileSelect(ast);
4113
- const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
4141
+ const compiled = entityCtx.dialect.compileSelect(ast);
4142
+ const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
4114
4143
  const rows = flattenResults(executed);
4115
4144
  if (ast.setOps && ast.setOps.length > 0) {
4116
- return rows.map(
4117
- (row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
4118
- );
4145
+ return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4119
4146
  }
4120
4147
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
4121
- return hydrated.map(
4122
- (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
4123
- );
4148
+ return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4149
+ };
4150
+ async function executeHydrated(session, qb) {
4151
+ return executeWithEntityContext(session, qb);
4152
+ }
4153
+ async function executeHydratedWithContexts(_execCtx, hydCtx, qb) {
4154
+ const entityCtx = hydCtx.entityContext;
4155
+ if (!entityCtx) {
4156
+ throw new Error("Hydration context is missing an EntityContext");
4157
+ }
4158
+ return executeWithEntityContext(entityCtx, qb);
4124
4159
  }
4125
4160
 
4126
4161
  // src/query-builder/select.ts
4127
4162
  var SelectQueryBuilder = class _SelectQueryBuilder {
4128
4163
  /**
4129
- * Creates a new SelectQueryBuilder instance
4130
- * @param table - Table definition to query
4131
- * @param state - Optional initial query state
4132
- * @param hydration - Optional hydration manager
4133
- * @param dependencies - Optional query builder dependencies
4134
- */
4164
+
4165
+ * Creates a new SelectQueryBuilder instance
4166
+
4167
+ * @param table - Table definition to query
4168
+
4169
+ * @param state - Optional initial query state
4170
+
4171
+ * @param hydration - Optional hydration manager
4172
+
4173
+ * @param dependencies - Optional query builder dependencies
4174
+
4175
+ */
4135
4176
  constructor(table, state, hydration, dependencies, lazyRelations) {
4136
4177
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
4137
4178
  this.env = { table, deps };
@@ -4168,112 +4209,168 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4168
4209
  return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
4169
4210
  }
4170
4211
  /**
4171
- * Selects specific columns for the query
4172
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
4173
- * @returns New query builder instance with selected columns
4174
- */
4212
+
4213
+ * Selects specific columns for the query
4214
+
4215
+ * @param columns - Record of column definitions, function nodes, case expressions, or window functions
4216
+
4217
+ * @returns New query builder instance with selected columns
4218
+
4219
+ */
4175
4220
  select(columns) {
4176
4221
  return this.clone(this.columnSelector.select(this.context, columns));
4177
4222
  }
4178
4223
  /**
4179
- * Selects raw column expressions
4180
- * @param cols - Column expressions as strings
4181
- * @returns New query builder instance with raw column selections
4182
- */
4224
+
4225
+ * Selects raw column expressions
4226
+
4227
+ * @param cols - Column expressions as strings
4228
+
4229
+ * @returns New query builder instance with raw column selections
4230
+
4231
+ */
4183
4232
  selectRaw(...cols) {
4184
4233
  return this.clone(this.columnSelector.selectRaw(this.context, cols));
4185
4234
  }
4186
4235
  /**
4187
- * Adds a Common Table Expression (CTE) to the query
4188
- * @param name - Name of the CTE
4189
- * @param query - Query builder or query node for the CTE
4190
- * @param columns - Optional column names for the CTE
4191
- * @returns New query builder instance with the CTE
4192
- */
4236
+
4237
+ * Adds a Common Table Expression (CTE) to the query
4238
+
4239
+ * @param name - Name of the CTE
4240
+
4241
+ * @param query - Query builder or query node for the CTE
4242
+
4243
+ * @param columns - Optional column names for the CTE
4244
+
4245
+ * @returns New query builder instance with the CTE
4246
+
4247
+ */
4193
4248
  with(name, query, columns) {
4194
4249
  const subAst = this.resolveQueryNode(query);
4195
4250
  const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
4196
4251
  return this.clone(nextContext);
4197
4252
  }
4198
4253
  /**
4199
- * Adds a recursive Common Table Expression (CTE) to the query
4200
- * @param name - Name of the CTE
4201
- * @param query - Query builder or query node for the CTE
4202
- * @param columns - Optional column names for the CTE
4203
- * @returns New query builder instance with the recursive CTE
4204
- */
4254
+
4255
+ * Adds a recursive Common Table Expression (CTE) to the query
4256
+
4257
+ * @param name - Name of the CTE
4258
+
4259
+ * @param query - Query builder or query node for the CTE
4260
+
4261
+ * @param columns - Optional column names for the CTE
4262
+
4263
+ * @returns New query builder instance with the recursive CTE
4264
+
4265
+ */
4205
4266
  withRecursive(name, query, columns) {
4206
4267
  const subAst = this.resolveQueryNode(query);
4207
4268
  const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
4208
4269
  return this.clone(nextContext);
4209
4270
  }
4210
4271
  /**
4211
- * Selects a subquery as a column
4212
- * @param alias - Alias for the subquery column
4213
- * @param sub - Query builder or query node for the subquery
4214
- * @returns New query builder instance with the subquery selection
4215
- */
4272
+
4273
+ * Selects a subquery as a column
4274
+
4275
+ * @param alias - Alias for the subquery column
4276
+
4277
+ * @param sub - Query builder or query node for the subquery
4278
+
4279
+ * @returns New query builder instance with the subquery selection
4280
+
4281
+ */
4216
4282
  selectSubquery(alias, sub) {
4217
4283
  const query = this.resolveQueryNode(sub);
4218
4284
  return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
4219
4285
  }
4220
4286
  /**
4221
- * Adds an INNER JOIN to the query
4222
- * @param table - Table to join
4223
- * @param condition - Join condition expression
4224
- * @returns New query builder instance with the INNER JOIN
4225
- */
4287
+
4288
+ * Adds an INNER JOIN to the query
4289
+
4290
+ * @param table - Table to join
4291
+
4292
+ * @param condition - Join condition expression
4293
+
4294
+ * @returns New query builder instance with the INNER JOIN
4295
+
4296
+ */
4226
4297
  innerJoin(table, condition) {
4227
4298
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
4228
4299
  return this.clone(nextContext);
4229
4300
  }
4230
4301
  /**
4231
- * Adds a LEFT JOIN to the query
4232
- * @param table - Table to join
4233
- * @param condition - Join condition expression
4234
- * @returns New query builder instance with the LEFT JOIN
4235
- */
4302
+
4303
+ * Adds a LEFT JOIN to the query
4304
+
4305
+ * @param table - Table to join
4306
+
4307
+ * @param condition - Join condition expression
4308
+
4309
+ * @returns New query builder instance with the LEFT JOIN
4310
+
4311
+ */
4236
4312
  leftJoin(table, condition) {
4237
4313
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
4238
4314
  return this.clone(nextContext);
4239
4315
  }
4240
4316
  /**
4241
- * Adds a RIGHT JOIN to the query
4242
- * @param table - Table to join
4243
- * @param condition - Join condition expression
4244
- * @returns New query builder instance with the RIGHT JOIN
4245
- */
4317
+
4318
+ * Adds a RIGHT JOIN to the query
4319
+
4320
+ * @param table - Table to join
4321
+
4322
+ * @param condition - Join condition expression
4323
+
4324
+ * @returns New query builder instance with the RIGHT JOIN
4325
+
4326
+ */
4246
4327
  rightJoin(table, condition) {
4247
4328
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
4248
4329
  return this.clone(nextContext);
4249
4330
  }
4250
4331
  /**
4251
- * Matches records based on a relationship
4252
- * @param relationName - Name of the relationship to match
4253
- * @param predicate - Optional predicate expression
4254
- * @returns New query builder instance with the relationship match
4255
- */
4332
+
4333
+ * Matches records based on a relationship
4334
+
4335
+ * @param relationName - Name of the relationship to match
4336
+
4337
+ * @param predicate - Optional predicate expression
4338
+
4339
+ * @returns New query builder instance with the relationship match
4340
+
4341
+ */
4256
4342
  match(relationName, predicate) {
4257
4343
  const nextContext = this.relationManager.match(this.context, relationName, predicate);
4258
4344
  return this.clone(nextContext);
4259
4345
  }
4260
4346
  /**
4261
- * Joins a related table
4262
- * @param relationName - Name of the relationship to join
4263
- * @param joinKind - Type of join (defaults to INNER)
4264
- * @param extraCondition - Optional additional join condition
4265
- * @returns New query builder instance with the relationship join
4266
- */
4347
+
4348
+ * Joins a related table
4349
+
4350
+ * @param relationName - Name of the relationship to join
4351
+
4352
+ * @param joinKind - Type of join (defaults to INNER)
4353
+
4354
+ * @param extraCondition - Optional additional join condition
4355
+
4356
+ * @returns New query builder instance with the relationship join
4357
+
4358
+ */
4267
4359
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
4268
4360
  const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
4269
4361
  return this.clone(nextContext);
4270
4362
  }
4271
4363
  /**
4272
- * Includes related data in the query results
4273
- * @param relationName - Name of the relationship to include
4274
- * @param options - Optional include options
4275
- * @returns New query builder instance with the relationship inclusion
4276
- */
4364
+
4365
+ * Includes related data in the query results
4366
+
4367
+ * @param relationName - Name of the relationship to include
4368
+
4369
+ * @param options - Optional include options
4370
+
4371
+ * @returns New query builder instance with the relationship inclusion
4372
+
4373
+ */
4277
4374
  include(relationName, options) {
4278
4375
  const nextContext = this.relationManager.include(this.context, relationName, options);
4279
4376
  return this.clone(nextContext);
@@ -4292,125 +4389,186 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4292
4389
  async execute(ctx) {
4293
4390
  return executeHydrated(ctx, this);
4294
4391
  }
4392
+ async executeWithContexts(execCtx, hydCtx) {
4393
+ return executeHydratedWithContexts(execCtx, hydCtx, this);
4394
+ }
4295
4395
  /**
4296
- * Adds a WHERE condition to the query
4297
- * @param expr - Expression for the WHERE clause
4298
- * @returns New query builder instance with the WHERE condition
4299
- */
4396
+
4397
+ * Adds a WHERE condition to the query
4398
+
4399
+ * @param expr - Expression for the WHERE clause
4400
+
4401
+ * @returns New query builder instance with the WHERE condition
4402
+
4403
+ */
4300
4404
  where(expr) {
4301
4405
  const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
4302
4406
  return this.clone(nextContext);
4303
4407
  }
4304
4408
  /**
4305
- * Adds a GROUP BY clause to the query
4306
- * @param col - Column definition or column node to group by
4307
- * @returns New query builder instance with the GROUP BY clause
4308
- */
4409
+
4410
+ * Adds a GROUP BY clause to the query
4411
+
4412
+ * @param col - Column definition or column node to group by
4413
+
4414
+ * @returns New query builder instance with the GROUP BY clause
4415
+
4416
+ */
4309
4417
  groupBy(col2) {
4310
4418
  const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col2));
4311
4419
  return this.clone(nextContext);
4312
4420
  }
4313
4421
  /**
4314
- * Adds a HAVING condition to the query
4315
- * @param expr - Expression for the HAVING clause
4316
- * @returns New query builder instance with the HAVING condition
4317
- */
4422
+
4423
+ * Adds a HAVING condition to the query
4424
+
4425
+ * @param expr - Expression for the HAVING clause
4426
+
4427
+ * @returns New query builder instance with the HAVING condition
4428
+
4429
+ */
4318
4430
  having(expr) {
4319
4431
  const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
4320
4432
  return this.clone(nextContext);
4321
4433
  }
4322
4434
  /**
4323
- * Adds an ORDER BY clause to the query
4324
- * @param col - Column definition or column node to order by
4325
- * @param direction - Order direction (defaults to ASC)
4326
- * @returns New query builder instance with the ORDER BY clause
4327
- */
4435
+
4436
+ * Adds an ORDER BY clause to the query
4437
+
4438
+ * @param col - Column definition or column node to order by
4439
+
4440
+ * @param direction - Order direction (defaults to ASC)
4441
+
4442
+ * @returns New query builder instance with the ORDER BY clause
4443
+
4444
+ */
4328
4445
  orderBy(col2, direction = ORDER_DIRECTIONS.ASC) {
4329
4446
  const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col2, direction));
4330
4447
  return this.clone(nextContext);
4331
4448
  }
4332
4449
  /**
4333
- * Adds a DISTINCT clause to the query
4334
- * @param cols - Columns to make distinct
4335
- * @returns New query builder instance with the DISTINCT clause
4336
- */
4450
+
4451
+ * Adds a DISTINCT clause to the query
4452
+
4453
+ * @param cols - Columns to make distinct
4454
+
4455
+ * @returns New query builder instance with the DISTINCT clause
4456
+
4457
+ */
4337
4458
  distinct(...cols) {
4338
4459
  return this.clone(this.columnSelector.distinct(this.context, cols));
4339
4460
  }
4340
4461
  /**
4341
- * Adds a LIMIT clause to the query
4342
- * @param n - Maximum number of rows to return
4343
- * @returns New query builder instance with the LIMIT clause
4344
- */
4462
+
4463
+ * Adds a LIMIT clause to the query
4464
+
4465
+ * @param n - Maximum number of rows to return
4466
+
4467
+ * @returns New query builder instance with the LIMIT clause
4468
+
4469
+ */
4345
4470
  limit(n) {
4346
4471
  const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
4347
4472
  return this.clone(nextContext);
4348
4473
  }
4349
4474
  /**
4350
- * Adds an OFFSET clause to the query
4351
- * @param n - Number of rows to skip
4352
- * @returns New query builder instance with the OFFSET clause
4353
- */
4475
+
4476
+ * Adds an OFFSET clause to the query
4477
+
4478
+ * @param n - Number of rows to skip
4479
+
4480
+ * @returns New query builder instance with the OFFSET clause
4481
+
4482
+ */
4354
4483
  offset(n) {
4355
4484
  const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
4356
4485
  return this.clone(nextContext);
4357
4486
  }
4358
4487
  /**
4359
- * Combines this query with another using UNION
4360
- * @param query - Query to union with
4361
- * @returns New query builder instance with the set operation
4362
- */
4488
+
4489
+ * Combines this query with another using UNION
4490
+
4491
+ * @param query - Query to union with
4492
+
4493
+ * @returns New query builder instance with the set operation
4494
+
4495
+ */
4363
4496
  union(query) {
4364
4497
  return this.clone(this.applySetOperation("UNION", query));
4365
4498
  }
4366
4499
  /**
4367
- * Combines this query with another using UNION ALL
4368
- * @param query - Query to union with
4369
- * @returns New query builder instance with the set operation
4370
- */
4500
+
4501
+ * Combines this query with another using UNION ALL
4502
+
4503
+ * @param query - Query to union with
4504
+
4505
+ * @returns New query builder instance with the set operation
4506
+
4507
+ */
4371
4508
  unionAll(query) {
4372
4509
  return this.clone(this.applySetOperation("UNION ALL", query));
4373
4510
  }
4374
4511
  /**
4375
- * Combines this query with another using INTERSECT
4376
- * @param query - Query to intersect with
4377
- * @returns New query builder instance with the set operation
4378
- */
4512
+
4513
+ * Combines this query with another using INTERSECT
4514
+
4515
+ * @param query - Query to intersect with
4516
+
4517
+ * @returns New query builder instance with the set operation
4518
+
4519
+ */
4379
4520
  intersect(query) {
4380
4521
  return this.clone(this.applySetOperation("INTERSECT", query));
4381
4522
  }
4382
4523
  /**
4383
- * Combines this query with another using EXCEPT
4384
- * @param query - Query to subtract
4385
- * @returns New query builder instance with the set operation
4386
- */
4524
+
4525
+ * Combines this query with another using EXCEPT
4526
+
4527
+ * @param query - Query to subtract
4528
+
4529
+ * @returns New query builder instance with the set operation
4530
+
4531
+ */
4387
4532
  except(query) {
4388
4533
  return this.clone(this.applySetOperation("EXCEPT", query));
4389
4534
  }
4390
4535
  /**
4391
- * Adds a WHERE EXISTS condition to the query
4392
- * @param subquery - Subquery to check for existence
4393
- * @returns New query builder instance with the WHERE EXISTS condition
4394
- */
4536
+
4537
+ * Adds a WHERE EXISTS condition to the query
4538
+
4539
+ * @param subquery - Subquery to check for existence
4540
+
4541
+ * @returns New query builder instance with the WHERE EXISTS condition
4542
+
4543
+ */
4395
4544
  whereExists(subquery) {
4396
4545
  const subAst = this.resolveQueryNode(subquery);
4397
4546
  return this.where(exists(subAst));
4398
4547
  }
4399
4548
  /**
4400
- * Adds a WHERE NOT EXISTS condition to the query
4401
- * @param subquery - Subquery to check for non-existence
4402
- * @returns New query builder instance with the WHERE NOT EXISTS condition
4403
- */
4549
+
4550
+ * Adds a WHERE NOT EXISTS condition to the query
4551
+
4552
+ * @param subquery - Subquery to check for non-existence
4553
+
4554
+ * @returns New query builder instance with the WHERE NOT EXISTS condition
4555
+
4556
+ */
4404
4557
  whereNotExists(subquery) {
4405
4558
  const subAst = this.resolveQueryNode(subquery);
4406
4559
  return this.where(notExists(subAst));
4407
4560
  }
4408
4561
  /**
4409
- * Adds a WHERE EXISTS condition based on a relationship
4410
- * @param relationName - Name of the relationship to check
4411
- * @param callback - Optional callback to modify the relationship query
4412
- * @returns New query builder instance with the relationship existence check
4413
- */
4562
+
4563
+ * Adds a WHERE EXISTS condition based on a relationship
4564
+
4565
+ * @param relationName - Name of the relationship to check
4566
+
4567
+ * @param callback - Optional callback to modify the relationship query
4568
+
4569
+ * @returns New query builder instance with the relationship existence check
4570
+
4571
+ */
4414
4572
  whereHas(relationName, callback) {
4415
4573
  const relation = this.env.table.relations[relationName];
4416
4574
  if (!relation) {
@@ -4425,11 +4583,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4425
4583
  return this.where(exists(finalSubAst));
4426
4584
  }
4427
4585
  /**
4428
- * Adds a WHERE NOT EXISTS condition based on a relationship
4429
- * @param relationName - Name of the relationship to check
4430
- * @param callback - Optional callback to modify the relationship query
4431
- * @returns New query builder instance with the relationship non-existence check
4432
- */
4586
+
4587
+ * Adds a WHERE NOT EXISTS condition based on a relationship
4588
+
4589
+ * @param relationName - Name of the relationship to check
4590
+
4591
+ * @param callback - Optional callback to modify the relationship query
4592
+
4593
+ * @returns New query builder instance with the relationship non-existence check
4594
+
4595
+ */
4433
4596
  whereHasNot(relationName, callback) {
4434
4597
  const relation = this.env.table.relations[relationName];
4435
4598
  if (!relation) {
@@ -4444,33 +4607,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4444
4607
  return this.where(notExists(finalSubAst));
4445
4608
  }
4446
4609
  /**
4447
- * Compiles the query to SQL for a specific dialect
4448
- * @param dialect - Database dialect to compile for
4449
- * @returns Compiled query with SQL and parameters
4450
- */
4610
+
4611
+ * Compiles the query to SQL for a specific dialect
4612
+
4613
+ * @param dialect - Database dialect to compile for
4614
+
4615
+ * @returns Compiled query with SQL and parameters
4616
+
4617
+ */
4451
4618
  compile(dialect) {
4452
4619
  const resolved = resolveDialectInput(dialect);
4453
4620
  return resolved.compileSelect(this.context.state.ast);
4454
4621
  }
4455
4622
  /**
4456
- * Converts the query to SQL string for a specific dialect
4457
- * @param dialect - Database dialect to generate SQL for
4458
- * @returns SQL string representation of the query
4459
- */
4623
+
4624
+ * Converts the query to SQL string for a specific dialect
4625
+
4626
+ * @param dialect - Database dialect to generate SQL for
4627
+
4628
+ * @returns SQL string representation of the query
4629
+
4630
+ */
4460
4631
  toSql(dialect) {
4461
4632
  return this.compile(dialect).sql;
4462
4633
  }
4463
4634
  /**
4464
- * Gets the hydration plan for the query
4465
- * @returns Hydration plan or undefined if none exists
4466
- */
4635
+
4636
+ * Gets the hydration plan for the query
4637
+
4638
+ * @returns Hydration plan or undefined if none exists
4639
+
4640
+ */
4467
4641
  getHydrationPlan() {
4468
4642
  return this.context.hydration.getPlan();
4469
4643
  }
4470
4644
  /**
4471
- * Gets the Abstract Syntax Tree (AST) representation of the query
4472
- * @returns Query AST with hydration applied
4473
- */
4645
+
4646
+ * Gets the Abstract Syntax Tree (AST) representation of the query
4647
+
4648
+ * @returns Query AST with hydration applied
4649
+
4650
+ */
4474
4651
  getAST() {
4475
4652
  return this.context.hydration.applyToAst(this.context.state.ast);
4476
4653
  }
@@ -5768,12 +5945,47 @@ var SQL_OPERATOR_REGISTRY = {
5768
5945
  [SQL_OPERATORS.NOT_EXISTS]: { sql: SQL_OPERATORS.NOT_EXISTS, tsName: "notExists" }
5769
5946
  };
5770
5947
 
5948
+ // src/codegen/naming-strategy.ts
5949
+ var DefaultNamingStrategy = class {
5950
+ /**
5951
+ * Converts table names to TypeScript symbols
5952
+ * @param table - Table node, function table node, or string name
5953
+ * @returns Capitalized table name (handles schema-qualified names)
5954
+ */
5955
+ tableToSymbol(table) {
5956
+ const tableName = typeof table === "string" ? table : table.name;
5957
+ if (tableName.includes(".")) {
5958
+ return tableName.split(".").map((part) => this.capitalize(part)).join("");
5959
+ }
5960
+ return this.capitalize(tableName);
5961
+ }
5962
+ /**
5963
+ * Converts column references to property names
5964
+ * @param column - Column node
5965
+ * @returns Column name as-is (for backward compatibility)
5966
+ */
5967
+ columnToProperty(column) {
5968
+ return column.name;
5969
+ }
5970
+ /**
5971
+ * Capitalizes the first letter of a string
5972
+ * @param s - String to capitalize
5973
+ * @returns Capitalized string
5974
+ */
5975
+ capitalize(s) {
5976
+ if (!s) return s;
5977
+ return s.charAt(0).toUpperCase() + s.slice(1);
5978
+ }
5979
+ };
5980
+
5771
5981
  // src/codegen/typescript.ts
5772
- var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
5773
5982
  var assertNever2 = (value) => {
5774
5983
  throw new Error(`Unhandled SQL operator: ${value}`);
5775
5984
  };
5776
5985
  var TypeScriptGenerator = class {
5986
+ constructor(namingStrategy = new DefaultNamingStrategy()) {
5987
+ this.namingStrategy = namingStrategy;
5988
+ }
5777
5989
  /**
5778
5990
  * Generates TypeScript code from a query AST
5779
5991
  * @param ast - Query AST to generate code from
@@ -5804,9 +6016,9 @@ var TypeScriptGenerator = class {
5804
6016
  lines.push(` ${sel}${index < selections.length - 1 ? "," : ""}`);
5805
6017
  });
5806
6018
  lines.push(`})`);
5807
- lines.push(`.from(${capitalize(ast.from.name)})`);
6019
+ lines.push(`.from(${this.namingStrategy.tableToSymbol(ast.from)})`);
5808
6020
  if (ast.distinct && ast.distinct.length) {
5809
- const cols = ast.distinct.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
6021
+ const cols = ast.distinct.map((c) => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(", ");
5810
6022
  lines.push(`.distinct(${cols})`);
5811
6023
  }
5812
6024
  ast.joins.forEach((join) => {
@@ -5821,7 +6033,7 @@ var TypeScriptGenerator = class {
5821
6033
  lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
5822
6034
  }
5823
6035
  } else {
5824
- const table = capitalize(join.table.name);
6036
+ const table = this.namingStrategy.tableToSymbol(join.table);
5825
6037
  const cond = this.printExpression(join.condition);
5826
6038
  let method = "innerJoin";
5827
6039
  if (join.kind === "LEFT") method = "leftJoin";
@@ -5842,7 +6054,7 @@ var TypeScriptGenerator = class {
5842
6054
  lines.push(`.where(${this.printExpression(ast.where)})`);
5843
6055
  }
5844
6056
  if (ast.groupBy && ast.groupBy.length) {
5845
- const cols = ast.groupBy.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
6057
+ const cols = ast.groupBy.map((c) => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(", ");
5846
6058
  lines.push(`.groupBy(${cols})`);
5847
6059
  }
5848
6060
  if (ast.having) {
@@ -5850,7 +6062,7 @@ var TypeScriptGenerator = class {
5850
6062
  }
5851
6063
  if (ast.orderBy && ast.orderBy.length) {
5852
6064
  ast.orderBy.forEach((o) => {
5853
- lines.push(`.orderBy(${capitalize(o.column.table)}.${o.column.name}, '${o.direction}')`);
6065
+ lines.push(`.orderBy(${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name}, '${o.direction}')`);
5854
6066
  });
5855
6067
  }
5856
6068
  if (ast.limit) lines.push(`.limit(${ast.limit})`);
@@ -5989,7 +6201,7 @@ var TypeScriptGenerator = class {
5989
6201
  * @returns TypeScript code representation
5990
6202
  */
5991
6203
  printColumnOperand(column) {
5992
- return `${capitalize(column.table)}.${column.name}`;
6204
+ return `${this.namingStrategy.tableToSymbol(column.table)}.${column.name}`;
5993
6205
  }
5994
6206
  /**
5995
6207
  * Prints a literal operand to TypeScript code
@@ -6015,7 +6227,7 @@ var TypeScriptGenerator = class {
6015
6227
  * @returns TypeScript code representation
6016
6228
  */
6017
6229
  printJsonPathOperand(json) {
6018
- return `jsonPath(${capitalize(json.column.table)}.${json.column.name}, '${json.path}')`;
6230
+ return `jsonPath(${this.namingStrategy.tableToSymbol(json.column.table)}.${json.column.name}, '${json.path}')`;
6019
6231
  }
6020
6232
  /**
6021
6233
  * Prints a scalar subquery operand to TypeScript code
@@ -6051,11 +6263,11 @@ var TypeScriptGenerator = class {
6051
6263
  result += ") OVER (";
6052
6264
  const parts = [];
6053
6265
  if (node.partitionBy && node.partitionBy.length > 0) {
6054
- const partitionClause = "PARTITION BY " + node.partitionBy.map((col2) => `${capitalize(col2.table)}.${col2.name}`).join(", ");
6266
+ const partitionClause = "PARTITION BY " + node.partitionBy.map((col2) => `${this.namingStrategy.tableToSymbol(col2.table)}.${col2.name}`).join(", ");
6055
6267
  parts.push(partitionClause);
6056
6268
  }
6057
6269
  if (node.orderBy && node.orderBy.length > 0) {
6058
- const orderClause = "ORDER BY " + node.orderBy.map((o) => `${capitalize(o.column.table)}.${o.column.name} ${o.direction}`).join(", ");
6270
+ const orderClause = "ORDER BY " + node.orderBy.map((o) => `${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name} ${o.direction}`).join(", ");
6059
6271
  parts.push(orderClause);
6060
6272
  }
6061
6273
  result += parts.join(" ");
@@ -6084,46 +6296,24 @@ var TypeScriptGenerator = class {
6084
6296
  }
6085
6297
  };
6086
6298
 
6087
- // src/orm/domain-event-bus.ts
6088
- var DomainEventBus = class {
6089
- constructor(initialHandlers) {
6090
- this.handlers = /* @__PURE__ */ new Map();
6091
- const handlers = initialHandlers ?? {};
6092
- Object.entries(handlers).forEach(([name, list]) => {
6093
- this.handlers.set(name, [...list]);
6094
- });
6095
- }
6096
- register(name, handler) {
6097
- const existing = this.handlers.get(name) ?? [];
6098
- existing.push(handler);
6099
- this.handlers.set(name, existing);
6100
- }
6101
- async dispatch(trackedEntities, ctx) {
6102
- for (const tracked of trackedEntities) {
6103
- const entity = tracked.entity;
6104
- if (!entity.domainEvents || !entity.domainEvents.length) continue;
6105
- for (const event of entity.domainEvents) {
6106
- const eventName = this.getEventName(event);
6107
- const handlers = this.handlers.get(eventName);
6108
- if (!handlers) continue;
6109
- for (const handler of handlers) {
6110
- await handler(event, ctx);
6111
- }
6112
- }
6113
- entity.domainEvents = [];
6114
- }
6115
- }
6116
- getEventName(event) {
6117
- if (!event) return "Unknown";
6118
- if (typeof event === "string") return event;
6119
- return event.constructor?.name ?? "Unknown";
6120
- }
6299
+ // src/orm/entity-metadata.ts
6300
+ var metadataMap = /* @__PURE__ */ new Map();
6301
+ var getEntityMetadata = (target) => {
6302
+ return metadataMap.get(target);
6121
6303
  };
6122
- var addDomainEvent = (entity, event) => {
6123
- if (!entity.domainEvents) {
6124
- entity.domainEvents = [];
6304
+
6305
+ // src/decorators/bootstrap.ts
6306
+ var getTableDefFromEntity = (ctor) => {
6307
+ const meta = getEntityMetadata(ctor);
6308
+ if (!meta) return void 0;
6309
+ return meta.table;
6310
+ };
6311
+ var selectFromEntity = (ctor) => {
6312
+ const table = getTableDefFromEntity(ctor);
6313
+ if (!table) {
6314
+ throw new Error("Entity metadata has not been bootstrapped");
6125
6315
  }
6126
- entity.domainEvents.push(event);
6316
+ return new SelectQueryBuilder(table);
6127
6317
  };
6128
6318
 
6129
6319
  // src/orm/identity-map.ts
@@ -6153,272 +6343,121 @@ var IdentityMap = class {
6153
6343
  const bucket = this.buckets.get(table.name);
6154
6344
  return bucket ? Array.from(bucket.values()) : [];
6155
6345
  }
6346
+ clear() {
6347
+ this.buckets.clear();
6348
+ }
6156
6349
  toIdentityKey(pk) {
6157
6350
  return String(pk);
6158
6351
  }
6159
6352
  };
6160
6353
 
6161
- // src/orm/relation-change-processor.ts
6162
- var RelationChangeProcessor = class {
6163
- constructor(unitOfWork, dialect, executor) {
6164
- this.unitOfWork = unitOfWork;
6354
+ // src/orm/runtime-types.ts
6355
+ var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
6356
+ EntityStatus2["New"] = "new";
6357
+ EntityStatus2["Managed"] = "managed";
6358
+ EntityStatus2["Dirty"] = "dirty";
6359
+ EntityStatus2["Removed"] = "removed";
6360
+ EntityStatus2["Detached"] = "detached";
6361
+ return EntityStatus2;
6362
+ })(EntityStatus || {});
6363
+
6364
+ // src/orm/unit-of-work.ts
6365
+ var UnitOfWork = class {
6366
+ constructor(dialect, executor, identityMap, hookContext) {
6165
6367
  this.dialect = dialect;
6166
6368
  this.executor = executor;
6167
- this.relationChanges = [];
6369
+ this.identityMap = identityMap;
6370
+ this.hookContext = hookContext;
6371
+ this.trackedEntities = /* @__PURE__ */ new Map();
6168
6372
  }
6169
- registerChange(entry) {
6170
- this.relationChanges.push(entry);
6373
+ get identityBuckets() {
6374
+ return this.identityMap.bucketsMap;
6171
6375
  }
6172
- async process() {
6173
- if (!this.relationChanges.length) return;
6174
- const entries = [...this.relationChanges];
6175
- this.relationChanges.length = 0;
6176
- for (const entry of entries) {
6177
- switch (entry.relation.type) {
6178
- case RelationKinds.HasMany:
6179
- await this.handleHasManyChange(entry);
6376
+ getTracked() {
6377
+ return Array.from(this.trackedEntities.values());
6378
+ }
6379
+ getEntity(table, pk) {
6380
+ return this.identityMap.getEntity(table, pk);
6381
+ }
6382
+ getEntitiesForTable(table) {
6383
+ return this.identityMap.getEntitiesForTable(table);
6384
+ }
6385
+ findTracked(entity) {
6386
+ return this.trackedEntities.get(entity);
6387
+ }
6388
+ setEntity(table, pk, entity) {
6389
+ if (pk === null || pk === void 0) return;
6390
+ let tracked = this.trackedEntities.get(entity);
6391
+ if (!tracked) {
6392
+ tracked = {
6393
+ table,
6394
+ entity,
6395
+ pk,
6396
+ status: "managed" /* Managed */,
6397
+ original: this.createSnapshot(table, entity)
6398
+ };
6399
+ this.trackedEntities.set(entity, tracked);
6400
+ } else {
6401
+ tracked.pk = pk;
6402
+ }
6403
+ this.registerIdentity(tracked);
6404
+ }
6405
+ trackNew(table, entity, pk) {
6406
+ const tracked = {
6407
+ table,
6408
+ entity,
6409
+ pk: pk ?? null,
6410
+ status: "new" /* New */,
6411
+ original: null
6412
+ };
6413
+ this.trackedEntities.set(entity, tracked);
6414
+ if (pk != null) {
6415
+ this.registerIdentity(tracked);
6416
+ }
6417
+ }
6418
+ trackManaged(table, pk, entity) {
6419
+ const tracked = {
6420
+ table,
6421
+ entity,
6422
+ pk,
6423
+ status: "managed" /* Managed */,
6424
+ original: this.createSnapshot(table, entity)
6425
+ };
6426
+ this.trackedEntities.set(entity, tracked);
6427
+ this.registerIdentity(tracked);
6428
+ }
6429
+ markDirty(entity) {
6430
+ const tracked = this.trackedEntities.get(entity);
6431
+ if (!tracked) return;
6432
+ if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
6433
+ tracked.status = "dirty" /* Dirty */;
6434
+ }
6435
+ markRemoved(entity) {
6436
+ const tracked = this.trackedEntities.get(entity);
6437
+ if (!tracked) return;
6438
+ tracked.status = "removed" /* Removed */;
6439
+ }
6440
+ async flush() {
6441
+ const toFlush = Array.from(this.trackedEntities.values());
6442
+ for (const tracked of toFlush) {
6443
+ switch (tracked.status) {
6444
+ case "new" /* New */:
6445
+ await this.flushInsert(tracked);
6180
6446
  break;
6181
- case RelationKinds.HasOne:
6182
- await this.handleHasOneChange(entry);
6447
+ case "dirty" /* Dirty */:
6448
+ await this.flushUpdate(tracked);
6183
6449
  break;
6184
- case RelationKinds.BelongsToMany:
6185
- await this.handleBelongsToManyChange(entry);
6450
+ case "removed" /* Removed */:
6451
+ await this.flushDelete(tracked);
6186
6452
  break;
6187
- case RelationKinds.BelongsTo:
6188
- await this.handleBelongsToChange(entry);
6453
+ default:
6189
6454
  break;
6190
6455
  }
6191
6456
  }
6192
6457
  }
6193
- async handleHasManyChange(entry) {
6194
- const relation = entry.relation;
6195
- const target = entry.change.entity;
6196
- if (!target) return;
6197
- const tracked = this.unitOfWork.findTracked(target);
6198
- if (!tracked) return;
6199
- const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6200
- const rootValue = entry.root[localKey];
6201
- if (rootValue === void 0 || rootValue === null) return;
6202
- if (entry.change.kind === "add" || entry.change.kind === "attach") {
6203
- this.assignHasManyForeignKey(tracked.entity, relation, rootValue);
6204
- this.unitOfWork.markDirty(tracked.entity);
6205
- return;
6206
- }
6207
- if (entry.change.kind === "remove") {
6208
- this.detachHasManyChild(tracked.entity, relation);
6209
- }
6210
- }
6211
- async handleHasOneChange(entry) {
6212
- const relation = entry.relation;
6213
- const target = entry.change.entity;
6214
- if (!target) return;
6215
- const tracked = this.unitOfWork.findTracked(target);
6216
- if (!tracked) return;
6217
- const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6218
- const rootValue = entry.root[localKey];
6219
- if (rootValue === void 0 || rootValue === null) return;
6220
- if (entry.change.kind === "attach" || entry.change.kind === "add") {
6221
- this.assignHasOneForeignKey(tracked.entity, relation, rootValue);
6222
- this.unitOfWork.markDirty(tracked.entity);
6223
- return;
6224
- }
6225
- if (entry.change.kind === "remove") {
6226
- this.detachHasOneChild(tracked.entity, relation);
6227
- }
6228
- }
6229
- async handleBelongsToChange(_entry) {
6230
- }
6231
- async handleBelongsToManyChange(entry) {
6232
- const relation = entry.relation;
6233
- const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
6234
- const rootId = entry.root[rootKey];
6235
- if (rootId === void 0 || rootId === null) return;
6236
- const targetId = this.resolvePrimaryKeyValue(entry.change.entity, relation.target);
6237
- if (targetId === null) return;
6238
- if (entry.change.kind === "attach" || entry.change.kind === "add") {
6239
- await this.insertPivotRow(relation, rootId, targetId);
6240
- return;
6241
- }
6242
- if (entry.change.kind === "detach" || entry.change.kind === "remove") {
6243
- await this.deletePivotRow(relation, rootId, targetId);
6244
- if (relation.cascade === "all" || relation.cascade === "remove") {
6245
- this.unitOfWork.markRemoved(entry.change.entity);
6246
- }
6247
- }
6248
- }
6249
- assignHasManyForeignKey(child, relation, rootValue) {
6250
- const current = child[relation.foreignKey];
6251
- if (current === rootValue) return;
6252
- child[relation.foreignKey] = rootValue;
6253
- }
6254
- detachHasManyChild(child, relation) {
6255
- if (relation.cascade === "all" || relation.cascade === "remove") {
6256
- this.unitOfWork.markRemoved(child);
6257
- return;
6258
- }
6259
- child[relation.foreignKey] = null;
6260
- this.unitOfWork.markDirty(child);
6261
- }
6262
- assignHasOneForeignKey(child, relation, rootValue) {
6263
- const current = child[relation.foreignKey];
6264
- if (current === rootValue) return;
6265
- child[relation.foreignKey] = rootValue;
6266
- }
6267
- detachHasOneChild(child, relation) {
6268
- if (relation.cascade === "all" || relation.cascade === "remove") {
6269
- this.unitOfWork.markRemoved(child);
6270
- return;
6271
- }
6272
- child[relation.foreignKey] = null;
6273
- this.unitOfWork.markDirty(child);
6274
- }
6275
- async insertPivotRow(relation, rootId, targetId) {
6276
- const payload = {
6277
- [relation.pivotForeignKeyToRoot]: rootId,
6278
- [relation.pivotForeignKeyToTarget]: targetId
6279
- };
6280
- const builder = new InsertQueryBuilder(relation.pivotTable).values(payload);
6281
- const compiled = builder.compile(this.dialect);
6282
- await this.executor.executeSql(compiled.sql, compiled.params);
6283
- }
6284
- async deletePivotRow(relation, rootId, targetId) {
6285
- const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
6286
- const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
6287
- if (!rootCol || !targetCol) return;
6288
- const builder = new DeleteQueryBuilder(relation.pivotTable).where(
6289
- and(eq(rootCol, rootId), eq(targetCol, targetId))
6290
- );
6291
- const compiled = builder.compile(this.dialect);
6292
- await this.executor.executeSql(compiled.sql, compiled.params);
6293
- }
6294
- resolvePrimaryKeyValue(entity, table) {
6295
- if (!entity) return null;
6296
- const key = findPrimaryKey(table);
6297
- const value = entity[key];
6298
- if (value === void 0 || value === null) return null;
6299
- return value;
6300
- }
6301
- };
6302
-
6303
- // src/orm/transaction-runner.ts
6304
- var runInTransaction = async (executor, action) => {
6305
- if (!executor.beginTransaction) {
6306
- await action();
6307
- return;
6308
- }
6309
- await executor.beginTransaction();
6310
- try {
6311
- await action();
6312
- await executor.commitTransaction?.();
6313
- } catch (error) {
6314
- await executor.rollbackTransaction?.();
6315
- throw error;
6316
- }
6317
- };
6318
-
6319
- // src/orm/runtime-types.ts
6320
- var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
6321
- EntityStatus2["New"] = "new";
6322
- EntityStatus2["Managed"] = "managed";
6323
- EntityStatus2["Dirty"] = "dirty";
6324
- EntityStatus2["Removed"] = "removed";
6325
- EntityStatus2["Detached"] = "detached";
6326
- return EntityStatus2;
6327
- })(EntityStatus || {});
6328
-
6329
- // src/orm/unit-of-work.ts
6330
- var UnitOfWork = class {
6331
- constructor(dialect, executor, identityMap, hookContext) {
6332
- this.dialect = dialect;
6333
- this.executor = executor;
6334
- this.identityMap = identityMap;
6335
- this.hookContext = hookContext;
6336
- this.trackedEntities = /* @__PURE__ */ new Map();
6337
- }
6338
- get identityBuckets() {
6339
- return this.identityMap.bucketsMap;
6340
- }
6341
- getTracked() {
6342
- return Array.from(this.trackedEntities.values());
6343
- }
6344
- getEntity(table, pk) {
6345
- return this.identityMap.getEntity(table, pk);
6346
- }
6347
- getEntitiesForTable(table) {
6348
- return this.identityMap.getEntitiesForTable(table);
6349
- }
6350
- findTracked(entity) {
6351
- return this.trackedEntities.get(entity);
6352
- }
6353
- setEntity(table, pk, entity) {
6354
- if (pk === null || pk === void 0) return;
6355
- let tracked = this.trackedEntities.get(entity);
6356
- if (!tracked) {
6357
- tracked = {
6358
- table,
6359
- entity,
6360
- pk,
6361
- status: "managed" /* Managed */,
6362
- original: this.createSnapshot(table, entity)
6363
- };
6364
- this.trackedEntities.set(entity, tracked);
6365
- } else {
6366
- tracked.pk = pk;
6367
- }
6368
- this.registerIdentity(tracked);
6369
- }
6370
- trackNew(table, entity, pk) {
6371
- const tracked = {
6372
- table,
6373
- entity,
6374
- pk: pk ?? null,
6375
- status: "new" /* New */,
6376
- original: null
6377
- };
6378
- this.trackedEntities.set(entity, tracked);
6379
- if (pk != null) {
6380
- this.registerIdentity(tracked);
6381
- }
6382
- }
6383
- trackManaged(table, pk, entity) {
6384
- const tracked = {
6385
- table,
6386
- entity,
6387
- pk,
6388
- status: "managed" /* Managed */,
6389
- original: this.createSnapshot(table, entity)
6390
- };
6391
- this.trackedEntities.set(entity, tracked);
6392
- this.registerIdentity(tracked);
6393
- }
6394
- markDirty(entity) {
6395
- const tracked = this.trackedEntities.get(entity);
6396
- if (!tracked) return;
6397
- if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
6398
- tracked.status = "dirty" /* Dirty */;
6399
- }
6400
- markRemoved(entity) {
6401
- const tracked = this.trackedEntities.get(entity);
6402
- if (!tracked) return;
6403
- tracked.status = "removed" /* Removed */;
6404
- }
6405
- async flush() {
6406
- const toFlush = Array.from(this.trackedEntities.values());
6407
- for (const tracked of toFlush) {
6408
- switch (tracked.status) {
6409
- case "new" /* New */:
6410
- await this.flushInsert(tracked);
6411
- break;
6412
- case "dirty" /* Dirty */:
6413
- await this.flushUpdate(tracked);
6414
- break;
6415
- case "removed" /* Removed */:
6416
- await this.flushDelete(tracked);
6417
- break;
6418
- default:
6419
- break;
6420
- }
6421
- }
6458
+ reset() {
6459
+ this.trackedEntities.clear();
6460
+ this.identityMap.clear();
6422
6461
  }
6423
6462
  async flushInsert(tracked) {
6424
6463
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
@@ -6539,6 +6578,193 @@ var UnitOfWork = class {
6539
6578
  }
6540
6579
  };
6541
6580
 
6581
+ // src/orm/domain-event-bus.ts
6582
+ var DomainEventBus = class {
6583
+ constructor(initialHandlers) {
6584
+ this.handlers = /* @__PURE__ */ new Map();
6585
+ const handlers = initialHandlers ?? {};
6586
+ Object.entries(handlers).forEach(([name, list]) => {
6587
+ this.handlers.set(name, [...list]);
6588
+ });
6589
+ }
6590
+ register(name, handler) {
6591
+ const existing = this.handlers.get(name) ?? [];
6592
+ existing.push(handler);
6593
+ this.handlers.set(name, existing);
6594
+ }
6595
+ async dispatch(trackedEntities, ctx) {
6596
+ for (const tracked of trackedEntities) {
6597
+ const entity = tracked.entity;
6598
+ if (!entity.domainEvents || !entity.domainEvents.length) continue;
6599
+ for (const event of entity.domainEvents) {
6600
+ const eventName = this.getEventName(event);
6601
+ const handlers = this.handlers.get(eventName);
6602
+ if (!handlers) continue;
6603
+ for (const handler of handlers) {
6604
+ await handler(event, ctx);
6605
+ }
6606
+ }
6607
+ entity.domainEvents = [];
6608
+ }
6609
+ }
6610
+ getEventName(event) {
6611
+ if (!event) return "Unknown";
6612
+ if (typeof event === "string") return event;
6613
+ return event.constructor?.name ?? "Unknown";
6614
+ }
6615
+ };
6616
+ var addDomainEvent = (entity, event) => {
6617
+ if (!entity.domainEvents) {
6618
+ entity.domainEvents = [];
6619
+ }
6620
+ entity.domainEvents.push(event);
6621
+ };
6622
+
6623
+ // src/orm/relation-change-processor.ts
6624
+ var RelationChangeProcessor = class {
6625
+ constructor(unitOfWork, dialect, executor) {
6626
+ this.unitOfWork = unitOfWork;
6627
+ this.dialect = dialect;
6628
+ this.executor = executor;
6629
+ this.relationChanges = [];
6630
+ }
6631
+ registerChange(entry) {
6632
+ this.relationChanges.push(entry);
6633
+ }
6634
+ reset() {
6635
+ this.relationChanges.length = 0;
6636
+ }
6637
+ async process() {
6638
+ if (!this.relationChanges.length) return;
6639
+ const entries = [...this.relationChanges];
6640
+ this.relationChanges.length = 0;
6641
+ for (const entry of entries) {
6642
+ switch (entry.relation.type) {
6643
+ case RelationKinds.HasMany:
6644
+ await this.handleHasManyChange(entry);
6645
+ break;
6646
+ case RelationKinds.HasOne:
6647
+ await this.handleHasOneChange(entry);
6648
+ break;
6649
+ case RelationKinds.BelongsToMany:
6650
+ await this.handleBelongsToManyChange(entry);
6651
+ break;
6652
+ case RelationKinds.BelongsTo:
6653
+ await this.handleBelongsToChange(entry);
6654
+ break;
6655
+ }
6656
+ }
6657
+ }
6658
+ async handleHasManyChange(entry) {
6659
+ const relation = entry.relation;
6660
+ const target = entry.change.entity;
6661
+ if (!target) return;
6662
+ const tracked = this.unitOfWork.findTracked(target);
6663
+ if (!tracked) return;
6664
+ const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6665
+ const rootValue = entry.root[localKey];
6666
+ if (rootValue === void 0 || rootValue === null) return;
6667
+ if (entry.change.kind === "add" || entry.change.kind === "attach") {
6668
+ this.assignHasManyForeignKey(tracked.entity, relation, rootValue);
6669
+ this.unitOfWork.markDirty(tracked.entity);
6670
+ return;
6671
+ }
6672
+ if (entry.change.kind === "remove") {
6673
+ this.detachHasManyChild(tracked.entity, relation);
6674
+ }
6675
+ }
6676
+ async handleHasOneChange(entry) {
6677
+ const relation = entry.relation;
6678
+ const target = entry.change.entity;
6679
+ if (!target) return;
6680
+ const tracked = this.unitOfWork.findTracked(target);
6681
+ if (!tracked) return;
6682
+ const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6683
+ const rootValue = entry.root[localKey];
6684
+ if (rootValue === void 0 || rootValue === null) return;
6685
+ if (entry.change.kind === "attach" || entry.change.kind === "add") {
6686
+ this.assignHasOneForeignKey(tracked.entity, relation, rootValue);
6687
+ this.unitOfWork.markDirty(tracked.entity);
6688
+ return;
6689
+ }
6690
+ if (entry.change.kind === "remove") {
6691
+ this.detachHasOneChild(tracked.entity, relation);
6692
+ }
6693
+ }
6694
+ async handleBelongsToChange(_entry) {
6695
+ }
6696
+ async handleBelongsToManyChange(entry) {
6697
+ const relation = entry.relation;
6698
+ const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
6699
+ const rootId = entry.root[rootKey];
6700
+ if (rootId === void 0 || rootId === null) return;
6701
+ const targetId = this.resolvePrimaryKeyValue(entry.change.entity, relation.target);
6702
+ if (targetId === null) return;
6703
+ if (entry.change.kind === "attach" || entry.change.kind === "add") {
6704
+ await this.insertPivotRow(relation, rootId, targetId);
6705
+ return;
6706
+ }
6707
+ if (entry.change.kind === "detach" || entry.change.kind === "remove") {
6708
+ await this.deletePivotRow(relation, rootId, targetId);
6709
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6710
+ this.unitOfWork.markRemoved(entry.change.entity);
6711
+ }
6712
+ }
6713
+ }
6714
+ assignHasManyForeignKey(child, relation, rootValue) {
6715
+ const current = child[relation.foreignKey];
6716
+ if (current === rootValue) return;
6717
+ child[relation.foreignKey] = rootValue;
6718
+ }
6719
+ detachHasManyChild(child, relation) {
6720
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6721
+ this.unitOfWork.markRemoved(child);
6722
+ return;
6723
+ }
6724
+ child[relation.foreignKey] = null;
6725
+ this.unitOfWork.markDirty(child);
6726
+ }
6727
+ assignHasOneForeignKey(child, relation, rootValue) {
6728
+ const current = child[relation.foreignKey];
6729
+ if (current === rootValue) return;
6730
+ child[relation.foreignKey] = rootValue;
6731
+ }
6732
+ detachHasOneChild(child, relation) {
6733
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6734
+ this.unitOfWork.markRemoved(child);
6735
+ return;
6736
+ }
6737
+ child[relation.foreignKey] = null;
6738
+ this.unitOfWork.markDirty(child);
6739
+ }
6740
+ async insertPivotRow(relation, rootId, targetId) {
6741
+ const payload = {
6742
+ [relation.pivotForeignKeyToRoot]: rootId,
6743
+ [relation.pivotForeignKeyToTarget]: targetId
6744
+ };
6745
+ const builder = new InsertQueryBuilder(relation.pivotTable).values(payload);
6746
+ const compiled = builder.compile(this.dialect);
6747
+ await this.executor.executeSql(compiled.sql, compiled.params);
6748
+ }
6749
+ async deletePivotRow(relation, rootId, targetId) {
6750
+ const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
6751
+ const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
6752
+ if (!rootCol || !targetCol) return;
6753
+ const builder = new DeleteQueryBuilder(relation.pivotTable).where(
6754
+ and(eq(rootCol, rootId), eq(targetCol, targetId))
6755
+ );
6756
+ const compiled = builder.compile(this.dialect);
6757
+ await this.executor.executeSql(compiled.sql, compiled.params);
6758
+ }
6759
+ resolvePrimaryKeyValue(entity, table) {
6760
+ if (!entity) return null;
6761
+ const key = findPrimaryKey(table);
6762
+ const value = entity[key];
6763
+ if (value === void 0 || value === null) return null;
6764
+ return value;
6765
+ }
6766
+ };
6767
+
6542
6768
  // src/orm/query-logger.ts
6543
6769
  var createQueryLoggingExecutor = (executor, logger) => {
6544
6770
  if (!logger) {
@@ -6562,31 +6788,40 @@ var createQueryLoggingExecutor = (executor, logger) => {
6562
6788
  return wrapped;
6563
6789
  };
6564
6790
 
6565
- // src/orm/orm-context.ts
6566
- var OrmContext = class {
6567
- constructor(options) {
6568
- this.options = options;
6791
+ // src/orm/transaction-runner.ts
6792
+ var runInTransaction = async (executor, action) => {
6793
+ if (!executor.beginTransaction) {
6794
+ await action();
6795
+ return;
6796
+ }
6797
+ await executor.beginTransaction();
6798
+ try {
6799
+ await action();
6800
+ await executor.commitTransaction?.();
6801
+ } catch (error) {
6802
+ await executor.rollbackTransaction?.();
6803
+ throw error;
6804
+ }
6805
+ };
6806
+
6807
+ // src/orm/orm-session.ts
6808
+ var OrmSession = class {
6809
+ constructor(opts) {
6810
+ this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
6811
+ this.relationChanges.registerChange(
6812
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
6813
+ );
6814
+ };
6815
+ this.orm = opts.orm;
6816
+ this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
6817
+ this.interceptors = [...opts.interceptors ?? []];
6569
6818
  this.identityMap = new IdentityMap();
6570
- this.interceptors = [...options.interceptors ?? []];
6571
- this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
6572
- this.unitOfWork = new UnitOfWork(
6573
- options.dialect,
6574
- this.executorWithLogging,
6575
- this.identityMap,
6576
- () => this
6577
- );
6578
- this.relationChanges = new RelationChangeProcessor(
6579
- this.unitOfWork,
6580
- options.dialect,
6581
- this.executorWithLogging
6582
- );
6583
- this.domainEvents = new DomainEventBus(options.domainEventHandlers);
6819
+ this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
6820
+ this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
6821
+ this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
6584
6822
  }
6585
6823
  get dialect() {
6586
- return this.options.dialect;
6587
- }
6588
- get executor() {
6589
- return this.executorWithLogging;
6824
+ return this.orm.dialect;
6590
6825
  }
6591
6826
  get identityBuckets() {
6592
6827
  return this.unitOfWork.identityBuckets;
@@ -6612,16 +6847,8 @@ var OrmContext = class {
6612
6847
  markRemoved(entity) {
6613
6848
  this.unitOfWork.markRemoved(entity);
6614
6849
  }
6615
- registerRelationChange(root, relationKey, rootTable, relationName, relation, change) {
6616
- const entry = {
6617
- root,
6618
- relationKey,
6619
- rootTable,
6620
- relationName,
6621
- relation,
6622
- change
6623
- };
6624
- this.relationChanges.registerChange(entry);
6850
+ getEntitiesForTable(table) {
6851
+ return this.unitOfWork.getEntitiesForTable(table);
6625
6852
  }
6626
6853
  registerInterceptor(interceptor) {
6627
6854
  this.interceptors.push(interceptor);
@@ -6629,7 +6856,51 @@ var OrmContext = class {
6629
6856
  registerDomainEventHandler(name, handler) {
6630
6857
  this.domainEvents.register(name, handler);
6631
6858
  }
6632
- async saveChanges() {
6859
+ async find(entityClass, id) {
6860
+ const table = getTableDefFromEntity(entityClass);
6861
+ if (!table) {
6862
+ throw new Error("Entity metadata has not been bootstrapped");
6863
+ }
6864
+ const primaryKey = findPrimaryKey(table);
6865
+ const column = table.columns[primaryKey];
6866
+ if (!column) {
6867
+ throw new Error("Entity table does not expose a primary key");
6868
+ }
6869
+ const qb = selectFromEntity(entityClass).where(eq(column, id)).limit(1);
6870
+ const rows = await executeHydrated(this, qb);
6871
+ return rows[0] ?? null;
6872
+ }
6873
+ async findOne(qb) {
6874
+ const limited = qb.limit(1);
6875
+ const rows = await executeHydrated(this, limited);
6876
+ return rows[0] ?? null;
6877
+ }
6878
+ async findMany(qb) {
6879
+ return executeHydrated(this, qb);
6880
+ }
6881
+ async persist(entity) {
6882
+ if (this.unitOfWork.findTracked(entity)) {
6883
+ return;
6884
+ }
6885
+ const table = getTableDefFromEntity(entity.constructor);
6886
+ if (!table) {
6887
+ throw new Error("Entity metadata has not been bootstrapped");
6888
+ }
6889
+ const primaryKey = findPrimaryKey(table);
6890
+ const pkValue = entity[primaryKey];
6891
+ if (pkValue !== void 0 && pkValue !== null) {
6892
+ this.trackManaged(table, pkValue, entity);
6893
+ } else {
6894
+ this.trackNew(table, entity);
6895
+ }
6896
+ }
6897
+ async remove(entity) {
6898
+ this.markRemoved(entity);
6899
+ }
6900
+ async flush() {
6901
+ await this.unitOfWork.flush();
6902
+ }
6903
+ async commit() {
6633
6904
  await runInTransaction(this.executor, async () => {
6634
6905
  for (const interceptor of this.interceptors) {
6635
6906
  await interceptor.beforeFlush?.(this);
@@ -6643,8 +6914,83 @@ var OrmContext = class {
6643
6914
  });
6644
6915
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
6645
6916
  }
6646
- getEntitiesForTable(table) {
6647
- return this.unitOfWork.getEntitiesForTable(table);
6917
+ async rollback() {
6918
+ await this.executor.rollbackTransaction?.();
6919
+ this.unitOfWork.reset();
6920
+ this.relationChanges.reset();
6921
+ }
6922
+ getExecutionContext() {
6923
+ return {
6924
+ dialect: this.orm.dialect,
6925
+ executor: this.executor,
6926
+ interceptors: this.orm.interceptors
6927
+ };
6928
+ }
6929
+ getHydrationContext() {
6930
+ return {
6931
+ identityMap: this.identityMap,
6932
+ unitOfWork: this.unitOfWork,
6933
+ domainEvents: this.domainEvents,
6934
+ relationChanges: this.relationChanges,
6935
+ entityContext: this
6936
+ };
6937
+ }
6938
+ };
6939
+ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, relation, change) => ({
6940
+ root,
6941
+ relationKey,
6942
+ rootTable,
6943
+ relationName,
6944
+ relation,
6945
+ change
6946
+ });
6947
+
6948
+ // src/orm/interceptor-pipeline.ts
6949
+ var InterceptorPipeline = class {
6950
+ constructor() {
6951
+ this.interceptors = [];
6952
+ }
6953
+ use(interceptor) {
6954
+ this.interceptors.push(interceptor);
6955
+ }
6956
+ async run(ctx, executor) {
6957
+ let i = 0;
6958
+ const dispatch = async () => {
6959
+ const interceptor = this.interceptors[i++];
6960
+ if (!interceptor) {
6961
+ return executor.executeSql(ctx.sql, ctx.params);
6962
+ }
6963
+ return interceptor(ctx, dispatch);
6964
+ };
6965
+ return dispatch();
6966
+ }
6967
+ };
6968
+
6969
+ // src/orm/orm.ts
6970
+ var Orm = class {
6971
+ constructor(opts) {
6972
+ this.dialect = opts.dialect;
6973
+ this.interceptors = opts.interceptors ?? new InterceptorPipeline();
6974
+ this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
6975
+ this.executorFactory = opts.executorFactory;
6976
+ }
6977
+ createSession(options) {
6978
+ const executor = this.executorFactory.createExecutor(options?.tx);
6979
+ return new OrmSession({ orm: this, executor });
6980
+ }
6981
+ // Nice convenience:
6982
+ async transaction(fn4) {
6983
+ const executor = this.executorFactory.createTransactionalExecutor();
6984
+ const session = new OrmSession({ orm: this, executor });
6985
+ try {
6986
+ const result = await fn4(session);
6987
+ await session.commit();
6988
+ return result;
6989
+ } catch (err) {
6990
+ await session.rollback();
6991
+ throw err;
6992
+ } finally {
6993
+ }
6648
6994
  }
6649
6995
  };
6650
6996
 
@@ -6759,10 +7105,12 @@ export {
6759
7105
  DefaultHasManyCollection,
6760
7106
  DefaultManyToManyCollection,
6761
7107
  DeleteQueryBuilder,
7108
+ DomainEventBus,
6762
7109
  EntityStatus,
6763
7110
  InsertQueryBuilder,
6764
7111
  MySqlDialect,
6765
- OrmContext,
7112
+ Orm,
7113
+ OrmSession,
6766
7114
  PostgresDialect,
6767
7115
  RelationKinds,
6768
7116
  SelectQueryBuilder,
@@ -6804,6 +7152,7 @@ export {
6804
7152
  createMssqlExecutor,
6805
7153
  createMysqlExecutor,
6806
7154
  createPostgresExecutor,
7155
+ createQueryLoggingExecutor,
6807
7156
  createSqliteExecutor,
6808
7157
  currentDate,
6809
7158
  currentTime,
@@ -6821,6 +7170,7 @@ export {
6821
7170
  endOfMonth,
6822
7171
  eq,
6823
7172
  executeHydrated,
7173
+ executeHydratedWithContexts,
6824
7174
  exists,
6825
7175
  exp,
6826
7176
  extract,