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.cjs CHANGED
@@ -48,10 +48,12 @@ __export(index_exports, {
48
48
  DefaultHasManyCollection: () => DefaultHasManyCollection,
49
49
  DefaultManyToManyCollection: () => DefaultManyToManyCollection,
50
50
  DeleteQueryBuilder: () => DeleteQueryBuilder,
51
+ DomainEventBus: () => DomainEventBus,
51
52
  EntityStatus: () => EntityStatus,
52
53
  InsertQueryBuilder: () => InsertQueryBuilder,
53
54
  MySqlDialect: () => MySqlDialect,
54
- OrmContext: () => OrmContext,
55
+ Orm: () => Orm,
56
+ OrmSession: () => OrmSession,
55
57
  PostgresDialect: () => PostgresDialect,
56
58
  RelationKinds: () => RelationKinds,
57
59
  SelectQueryBuilder: () => SelectQueryBuilder,
@@ -93,6 +95,7 @@ __export(index_exports, {
93
95
  createMssqlExecutor: () => createMssqlExecutor,
94
96
  createMysqlExecutor: () => createMysqlExecutor,
95
97
  createPostgresExecutor: () => createPostgresExecutor,
98
+ createQueryLoggingExecutor: () => createQueryLoggingExecutor,
96
99
  createSqliteExecutor: () => createSqliteExecutor,
97
100
  currentDate: () => currentDate,
98
101
  currentTime: () => currentTime,
@@ -110,6 +113,7 @@ __export(index_exports, {
110
113
  endOfMonth: () => endOfMonth,
111
114
  eq: () => eq,
112
115
  executeHydrated: () => executeHydrated,
116
+ executeHydratedWithContexts: () => executeHydratedWithContexts,
113
117
  exists: () => exists,
114
118
  exp: () => exp,
115
119
  extract: () => extract,
@@ -723,7 +727,7 @@ var StandardFunctionStrategy = class {
723
727
  };
724
728
 
725
729
  // src/core/dialect/abstract.ts
726
- var Dialect = class {
730
+ var Dialect = class _Dialect {
727
731
  /**
728
732
  * Compiles a SELECT query AST to SQL
729
733
  * @param ast - Query AST to compile
@@ -896,6 +900,35 @@ var Dialect = class {
896
900
  this.registerDefaultOperandCompilers();
897
901
  this.registerDefaultExpressionCompilers();
898
902
  }
903
+ /**
904
+ * Creates a new Dialect instance (for testing purposes)
905
+ * @param functionStrategy - Optional function strategy
906
+ * @returns New Dialect instance
907
+ */
908
+ static create(functionStrategy) {
909
+ class TestDialect extends _Dialect {
910
+ constructor() {
911
+ super(...arguments);
912
+ this.dialect = "sqlite";
913
+ }
914
+ quoteIdentifier(id) {
915
+ return `"${id}"`;
916
+ }
917
+ compileSelectAst() {
918
+ throw new Error("Not implemented");
919
+ }
920
+ compileInsertAst() {
921
+ throw new Error("Not implemented");
922
+ }
923
+ compileUpdateAst() {
924
+ throw new Error("Not implemented");
925
+ }
926
+ compileDeleteAst() {
927
+ throw new Error("Not implemented");
928
+ }
929
+ }
930
+ return new TestDialect(functionStrategy);
931
+ }
899
932
  /**
900
933
  * Registers an expression compiler for a specific node type
901
934
  * @param type - Expression node type
@@ -4288,31 +4321,43 @@ var flattenResults = (results) => {
4288
4321
  }
4289
4322
  return rows;
4290
4323
  };
4291
- async function executeHydrated(ctx, qb) {
4324
+ var executeWithEntityContext = async (entityCtx, qb) => {
4292
4325
  const ast = qb.getAST();
4293
- const compiled = ctx.dialect.compileSelect(ast);
4294
- const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
4326
+ const compiled = entityCtx.dialect.compileSelect(ast);
4327
+ const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
4295
4328
  const rows = flattenResults(executed);
4296
4329
  if (ast.setOps && ast.setOps.length > 0) {
4297
- return rows.map(
4298
- (row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
4299
- );
4330
+ return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4300
4331
  }
4301
4332
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
4302
- return hydrated.map(
4303
- (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
4304
- );
4333
+ return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4334
+ };
4335
+ async function executeHydrated(session, qb) {
4336
+ return executeWithEntityContext(session, qb);
4337
+ }
4338
+ async function executeHydratedWithContexts(_execCtx, hydCtx, qb) {
4339
+ const entityCtx = hydCtx.entityContext;
4340
+ if (!entityCtx) {
4341
+ throw new Error("Hydration context is missing an EntityContext");
4342
+ }
4343
+ return executeWithEntityContext(entityCtx, qb);
4305
4344
  }
4306
4345
 
4307
4346
  // src/query-builder/select.ts
4308
4347
  var SelectQueryBuilder = class _SelectQueryBuilder {
4309
4348
  /**
4310
- * Creates a new SelectQueryBuilder instance
4311
- * @param table - Table definition to query
4312
- * @param state - Optional initial query state
4313
- * @param hydration - Optional hydration manager
4314
- * @param dependencies - Optional query builder dependencies
4315
- */
4349
+
4350
+ * Creates a new SelectQueryBuilder instance
4351
+
4352
+ * @param table - Table definition to query
4353
+
4354
+ * @param state - Optional initial query state
4355
+
4356
+ * @param hydration - Optional hydration manager
4357
+
4358
+ * @param dependencies - Optional query builder dependencies
4359
+
4360
+ */
4316
4361
  constructor(table, state, hydration, dependencies, lazyRelations) {
4317
4362
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
4318
4363
  this.env = { table, deps };
@@ -4349,112 +4394,168 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4349
4394
  return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
4350
4395
  }
4351
4396
  /**
4352
- * Selects specific columns for the query
4353
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
4354
- * @returns New query builder instance with selected columns
4355
- */
4397
+
4398
+ * Selects specific columns for the query
4399
+
4400
+ * @param columns - Record of column definitions, function nodes, case expressions, or window functions
4401
+
4402
+ * @returns New query builder instance with selected columns
4403
+
4404
+ */
4356
4405
  select(columns) {
4357
4406
  return this.clone(this.columnSelector.select(this.context, columns));
4358
4407
  }
4359
4408
  /**
4360
- * Selects raw column expressions
4361
- * @param cols - Column expressions as strings
4362
- * @returns New query builder instance with raw column selections
4363
- */
4409
+
4410
+ * Selects raw column expressions
4411
+
4412
+ * @param cols - Column expressions as strings
4413
+
4414
+ * @returns New query builder instance with raw column selections
4415
+
4416
+ */
4364
4417
  selectRaw(...cols) {
4365
4418
  return this.clone(this.columnSelector.selectRaw(this.context, cols));
4366
4419
  }
4367
4420
  /**
4368
- * Adds a Common Table Expression (CTE) to the query
4369
- * @param name - Name of the CTE
4370
- * @param query - Query builder or query node for the CTE
4371
- * @param columns - Optional column names for the CTE
4372
- * @returns New query builder instance with the CTE
4373
- */
4421
+
4422
+ * Adds a Common Table Expression (CTE) to the query
4423
+
4424
+ * @param name - Name of the CTE
4425
+
4426
+ * @param query - Query builder or query node for the CTE
4427
+
4428
+ * @param columns - Optional column names for the CTE
4429
+
4430
+ * @returns New query builder instance with the CTE
4431
+
4432
+ */
4374
4433
  with(name, query, columns) {
4375
4434
  const subAst = this.resolveQueryNode(query);
4376
4435
  const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
4377
4436
  return this.clone(nextContext);
4378
4437
  }
4379
4438
  /**
4380
- * Adds a recursive Common Table Expression (CTE) to the query
4381
- * @param name - Name of the CTE
4382
- * @param query - Query builder or query node for the CTE
4383
- * @param columns - Optional column names for the CTE
4384
- * @returns New query builder instance with the recursive CTE
4385
- */
4439
+
4440
+ * Adds a recursive Common Table Expression (CTE) to the query
4441
+
4442
+ * @param name - Name of the CTE
4443
+
4444
+ * @param query - Query builder or query node for the CTE
4445
+
4446
+ * @param columns - Optional column names for the CTE
4447
+
4448
+ * @returns New query builder instance with the recursive CTE
4449
+
4450
+ */
4386
4451
  withRecursive(name, query, columns) {
4387
4452
  const subAst = this.resolveQueryNode(query);
4388
4453
  const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
4389
4454
  return this.clone(nextContext);
4390
4455
  }
4391
4456
  /**
4392
- * Selects a subquery as a column
4393
- * @param alias - Alias for the subquery column
4394
- * @param sub - Query builder or query node for the subquery
4395
- * @returns New query builder instance with the subquery selection
4396
- */
4457
+
4458
+ * Selects a subquery as a column
4459
+
4460
+ * @param alias - Alias for the subquery column
4461
+
4462
+ * @param sub - Query builder or query node for the subquery
4463
+
4464
+ * @returns New query builder instance with the subquery selection
4465
+
4466
+ */
4397
4467
  selectSubquery(alias, sub) {
4398
4468
  const query = this.resolveQueryNode(sub);
4399
4469
  return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
4400
4470
  }
4401
4471
  /**
4402
- * Adds an INNER JOIN to the query
4403
- * @param table - Table to join
4404
- * @param condition - Join condition expression
4405
- * @returns New query builder instance with the INNER JOIN
4406
- */
4472
+
4473
+ * Adds an INNER JOIN to the query
4474
+
4475
+ * @param table - Table to join
4476
+
4477
+ * @param condition - Join condition expression
4478
+
4479
+ * @returns New query builder instance with the INNER JOIN
4480
+
4481
+ */
4407
4482
  innerJoin(table, condition) {
4408
4483
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
4409
4484
  return this.clone(nextContext);
4410
4485
  }
4411
4486
  /**
4412
- * Adds a LEFT JOIN to the query
4413
- * @param table - Table to join
4414
- * @param condition - Join condition expression
4415
- * @returns New query builder instance with the LEFT JOIN
4416
- */
4487
+
4488
+ * Adds a LEFT JOIN to the query
4489
+
4490
+ * @param table - Table to join
4491
+
4492
+ * @param condition - Join condition expression
4493
+
4494
+ * @returns New query builder instance with the LEFT JOIN
4495
+
4496
+ */
4417
4497
  leftJoin(table, condition) {
4418
4498
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
4419
4499
  return this.clone(nextContext);
4420
4500
  }
4421
4501
  /**
4422
- * Adds a RIGHT JOIN to the query
4423
- * @param table - Table to join
4424
- * @param condition - Join condition expression
4425
- * @returns New query builder instance with the RIGHT JOIN
4426
- */
4502
+
4503
+ * Adds a RIGHT JOIN to the query
4504
+
4505
+ * @param table - Table to join
4506
+
4507
+ * @param condition - Join condition expression
4508
+
4509
+ * @returns New query builder instance with the RIGHT JOIN
4510
+
4511
+ */
4427
4512
  rightJoin(table, condition) {
4428
4513
  const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
4429
4514
  return this.clone(nextContext);
4430
4515
  }
4431
4516
  /**
4432
- * Matches records based on a relationship
4433
- * @param relationName - Name of the relationship to match
4434
- * @param predicate - Optional predicate expression
4435
- * @returns New query builder instance with the relationship match
4436
- */
4517
+
4518
+ * Matches records based on a relationship
4519
+
4520
+ * @param relationName - Name of the relationship to match
4521
+
4522
+ * @param predicate - Optional predicate expression
4523
+
4524
+ * @returns New query builder instance with the relationship match
4525
+
4526
+ */
4437
4527
  match(relationName, predicate) {
4438
4528
  const nextContext = this.relationManager.match(this.context, relationName, predicate);
4439
4529
  return this.clone(nextContext);
4440
4530
  }
4441
4531
  /**
4442
- * Joins a related table
4443
- * @param relationName - Name of the relationship to join
4444
- * @param joinKind - Type of join (defaults to INNER)
4445
- * @param extraCondition - Optional additional join condition
4446
- * @returns New query builder instance with the relationship join
4447
- */
4532
+
4533
+ * Joins a related table
4534
+
4535
+ * @param relationName - Name of the relationship to join
4536
+
4537
+ * @param joinKind - Type of join (defaults to INNER)
4538
+
4539
+ * @param extraCondition - Optional additional join condition
4540
+
4541
+ * @returns New query builder instance with the relationship join
4542
+
4543
+ */
4448
4544
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
4449
4545
  const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
4450
4546
  return this.clone(nextContext);
4451
4547
  }
4452
4548
  /**
4453
- * Includes related data in the query results
4454
- * @param relationName - Name of the relationship to include
4455
- * @param options - Optional include options
4456
- * @returns New query builder instance with the relationship inclusion
4457
- */
4549
+
4550
+ * Includes related data in the query results
4551
+
4552
+ * @param relationName - Name of the relationship to include
4553
+
4554
+ * @param options - Optional include options
4555
+
4556
+ * @returns New query builder instance with the relationship inclusion
4557
+
4558
+ */
4458
4559
  include(relationName, options) {
4459
4560
  const nextContext = this.relationManager.include(this.context, relationName, options);
4460
4561
  return this.clone(nextContext);
@@ -4473,125 +4574,186 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4473
4574
  async execute(ctx) {
4474
4575
  return executeHydrated(ctx, this);
4475
4576
  }
4577
+ async executeWithContexts(execCtx, hydCtx) {
4578
+ return executeHydratedWithContexts(execCtx, hydCtx, this);
4579
+ }
4476
4580
  /**
4477
- * Adds a WHERE condition to the query
4478
- * @param expr - Expression for the WHERE clause
4479
- * @returns New query builder instance with the WHERE condition
4480
- */
4581
+
4582
+ * Adds a WHERE condition to the query
4583
+
4584
+ * @param expr - Expression for the WHERE clause
4585
+
4586
+ * @returns New query builder instance with the WHERE condition
4587
+
4588
+ */
4481
4589
  where(expr) {
4482
4590
  const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
4483
4591
  return this.clone(nextContext);
4484
4592
  }
4485
4593
  /**
4486
- * Adds a GROUP BY clause to the query
4487
- * @param col - Column definition or column node to group by
4488
- * @returns New query builder instance with the GROUP BY clause
4489
- */
4594
+
4595
+ * Adds a GROUP BY clause to the query
4596
+
4597
+ * @param col - Column definition or column node to group by
4598
+
4599
+ * @returns New query builder instance with the GROUP BY clause
4600
+
4601
+ */
4490
4602
  groupBy(col2) {
4491
4603
  const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col2));
4492
4604
  return this.clone(nextContext);
4493
4605
  }
4494
4606
  /**
4495
- * Adds a HAVING condition to the query
4496
- * @param expr - Expression for the HAVING clause
4497
- * @returns New query builder instance with the HAVING condition
4498
- */
4607
+
4608
+ * Adds a HAVING condition to the query
4609
+
4610
+ * @param expr - Expression for the HAVING clause
4611
+
4612
+ * @returns New query builder instance with the HAVING condition
4613
+
4614
+ */
4499
4615
  having(expr) {
4500
4616
  const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
4501
4617
  return this.clone(nextContext);
4502
4618
  }
4503
4619
  /**
4504
- * Adds an ORDER BY clause to the query
4505
- * @param col - Column definition or column node to order by
4506
- * @param direction - Order direction (defaults to ASC)
4507
- * @returns New query builder instance with the ORDER BY clause
4508
- */
4620
+
4621
+ * Adds an ORDER BY clause to the query
4622
+
4623
+ * @param col - Column definition or column node to order by
4624
+
4625
+ * @param direction - Order direction (defaults to ASC)
4626
+
4627
+ * @returns New query builder instance with the ORDER BY clause
4628
+
4629
+ */
4509
4630
  orderBy(col2, direction = ORDER_DIRECTIONS.ASC) {
4510
4631
  const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col2, direction));
4511
4632
  return this.clone(nextContext);
4512
4633
  }
4513
4634
  /**
4514
- * Adds a DISTINCT clause to the query
4515
- * @param cols - Columns to make distinct
4516
- * @returns New query builder instance with the DISTINCT clause
4517
- */
4635
+
4636
+ * Adds a DISTINCT clause to the query
4637
+
4638
+ * @param cols - Columns to make distinct
4639
+
4640
+ * @returns New query builder instance with the DISTINCT clause
4641
+
4642
+ */
4518
4643
  distinct(...cols) {
4519
4644
  return this.clone(this.columnSelector.distinct(this.context, cols));
4520
4645
  }
4521
4646
  /**
4522
- * Adds a LIMIT clause to the query
4523
- * @param n - Maximum number of rows to return
4524
- * @returns New query builder instance with the LIMIT clause
4525
- */
4647
+
4648
+ * Adds a LIMIT clause to the query
4649
+
4650
+ * @param n - Maximum number of rows to return
4651
+
4652
+ * @returns New query builder instance with the LIMIT clause
4653
+
4654
+ */
4526
4655
  limit(n) {
4527
4656
  const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
4528
4657
  return this.clone(nextContext);
4529
4658
  }
4530
4659
  /**
4531
- * Adds an OFFSET clause to the query
4532
- * @param n - Number of rows to skip
4533
- * @returns New query builder instance with the OFFSET clause
4534
- */
4660
+
4661
+ * Adds an OFFSET clause to the query
4662
+
4663
+ * @param n - Number of rows to skip
4664
+
4665
+ * @returns New query builder instance with the OFFSET clause
4666
+
4667
+ */
4535
4668
  offset(n) {
4536
4669
  const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
4537
4670
  return this.clone(nextContext);
4538
4671
  }
4539
4672
  /**
4540
- * Combines this query with another using UNION
4541
- * @param query - Query to union with
4542
- * @returns New query builder instance with the set operation
4543
- */
4673
+
4674
+ * Combines this query with another using UNION
4675
+
4676
+ * @param query - Query to union with
4677
+
4678
+ * @returns New query builder instance with the set operation
4679
+
4680
+ */
4544
4681
  union(query) {
4545
4682
  return this.clone(this.applySetOperation("UNION", query));
4546
4683
  }
4547
4684
  /**
4548
- * Combines this query with another using UNION ALL
4549
- * @param query - Query to union with
4550
- * @returns New query builder instance with the set operation
4551
- */
4685
+
4686
+ * Combines this query with another using UNION ALL
4687
+
4688
+ * @param query - Query to union with
4689
+
4690
+ * @returns New query builder instance with the set operation
4691
+
4692
+ */
4552
4693
  unionAll(query) {
4553
4694
  return this.clone(this.applySetOperation("UNION ALL", query));
4554
4695
  }
4555
4696
  /**
4556
- * Combines this query with another using INTERSECT
4557
- * @param query - Query to intersect with
4558
- * @returns New query builder instance with the set operation
4559
- */
4697
+
4698
+ * Combines this query with another using INTERSECT
4699
+
4700
+ * @param query - Query to intersect with
4701
+
4702
+ * @returns New query builder instance with the set operation
4703
+
4704
+ */
4560
4705
  intersect(query) {
4561
4706
  return this.clone(this.applySetOperation("INTERSECT", query));
4562
4707
  }
4563
4708
  /**
4564
- * Combines this query with another using EXCEPT
4565
- * @param query - Query to subtract
4566
- * @returns New query builder instance with the set operation
4567
- */
4709
+
4710
+ * Combines this query with another using EXCEPT
4711
+
4712
+ * @param query - Query to subtract
4713
+
4714
+ * @returns New query builder instance with the set operation
4715
+
4716
+ */
4568
4717
  except(query) {
4569
4718
  return this.clone(this.applySetOperation("EXCEPT", query));
4570
4719
  }
4571
4720
  /**
4572
- * Adds a WHERE EXISTS condition to the query
4573
- * @param subquery - Subquery to check for existence
4574
- * @returns New query builder instance with the WHERE EXISTS condition
4575
- */
4721
+
4722
+ * Adds a WHERE EXISTS condition to the query
4723
+
4724
+ * @param subquery - Subquery to check for existence
4725
+
4726
+ * @returns New query builder instance with the WHERE EXISTS condition
4727
+
4728
+ */
4576
4729
  whereExists(subquery) {
4577
4730
  const subAst = this.resolveQueryNode(subquery);
4578
4731
  return this.where(exists(subAst));
4579
4732
  }
4580
4733
  /**
4581
- * Adds a WHERE NOT EXISTS condition to the query
4582
- * @param subquery - Subquery to check for non-existence
4583
- * @returns New query builder instance with the WHERE NOT EXISTS condition
4584
- */
4734
+
4735
+ * Adds a WHERE NOT EXISTS condition to the query
4736
+
4737
+ * @param subquery - Subquery to check for non-existence
4738
+
4739
+ * @returns New query builder instance with the WHERE NOT EXISTS condition
4740
+
4741
+ */
4585
4742
  whereNotExists(subquery) {
4586
4743
  const subAst = this.resolveQueryNode(subquery);
4587
4744
  return this.where(notExists(subAst));
4588
4745
  }
4589
4746
  /**
4590
- * Adds a WHERE EXISTS condition based on a relationship
4591
- * @param relationName - Name of the relationship to check
4592
- * @param callback - Optional callback to modify the relationship query
4593
- * @returns New query builder instance with the relationship existence check
4594
- */
4747
+
4748
+ * Adds a WHERE EXISTS condition based on a relationship
4749
+
4750
+ * @param relationName - Name of the relationship to check
4751
+
4752
+ * @param callback - Optional callback to modify the relationship query
4753
+
4754
+ * @returns New query builder instance with the relationship existence check
4755
+
4756
+ */
4595
4757
  whereHas(relationName, callback) {
4596
4758
  const relation = this.env.table.relations[relationName];
4597
4759
  if (!relation) {
@@ -4606,11 +4768,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4606
4768
  return this.where(exists(finalSubAst));
4607
4769
  }
4608
4770
  /**
4609
- * Adds a WHERE NOT EXISTS condition based on a relationship
4610
- * @param relationName - Name of the relationship to check
4611
- * @param callback - Optional callback to modify the relationship query
4612
- * @returns New query builder instance with the relationship non-existence check
4613
- */
4771
+
4772
+ * Adds a WHERE NOT EXISTS condition based on a relationship
4773
+
4774
+ * @param relationName - Name of the relationship to check
4775
+
4776
+ * @param callback - Optional callback to modify the relationship query
4777
+
4778
+ * @returns New query builder instance with the relationship non-existence check
4779
+
4780
+ */
4614
4781
  whereHasNot(relationName, callback) {
4615
4782
  const relation = this.env.table.relations[relationName];
4616
4783
  if (!relation) {
@@ -4625,33 +4792,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
4625
4792
  return this.where(notExists(finalSubAst));
4626
4793
  }
4627
4794
  /**
4628
- * Compiles the query to SQL for a specific dialect
4629
- * @param dialect - Database dialect to compile for
4630
- * @returns Compiled query with SQL and parameters
4631
- */
4795
+
4796
+ * Compiles the query to SQL for a specific dialect
4797
+
4798
+ * @param dialect - Database dialect to compile for
4799
+
4800
+ * @returns Compiled query with SQL and parameters
4801
+
4802
+ */
4632
4803
  compile(dialect) {
4633
4804
  const resolved = resolveDialectInput(dialect);
4634
4805
  return resolved.compileSelect(this.context.state.ast);
4635
4806
  }
4636
4807
  /**
4637
- * Converts the query to SQL string for a specific dialect
4638
- * @param dialect - Database dialect to generate SQL for
4639
- * @returns SQL string representation of the query
4640
- */
4808
+
4809
+ * Converts the query to SQL string for a specific dialect
4810
+
4811
+ * @param dialect - Database dialect to generate SQL for
4812
+
4813
+ * @returns SQL string representation of the query
4814
+
4815
+ */
4641
4816
  toSql(dialect) {
4642
4817
  return this.compile(dialect).sql;
4643
4818
  }
4644
4819
  /**
4645
- * Gets the hydration plan for the query
4646
- * @returns Hydration plan or undefined if none exists
4647
- */
4820
+
4821
+ * Gets the hydration plan for the query
4822
+
4823
+ * @returns Hydration plan or undefined if none exists
4824
+
4825
+ */
4648
4826
  getHydrationPlan() {
4649
4827
  return this.context.hydration.getPlan();
4650
4828
  }
4651
4829
  /**
4652
- * Gets the Abstract Syntax Tree (AST) representation of the query
4653
- * @returns Query AST with hydration applied
4654
- */
4830
+
4831
+ * Gets the Abstract Syntax Tree (AST) representation of the query
4832
+
4833
+ * @returns Query AST with hydration applied
4834
+
4835
+ */
4655
4836
  getAST() {
4656
4837
  return this.context.hydration.applyToAst(this.context.state.ast);
4657
4838
  }
@@ -5949,12 +6130,47 @@ var SQL_OPERATOR_REGISTRY = {
5949
6130
  [SQL_OPERATORS.NOT_EXISTS]: { sql: SQL_OPERATORS.NOT_EXISTS, tsName: "notExists" }
5950
6131
  };
5951
6132
 
6133
+ // src/codegen/naming-strategy.ts
6134
+ var DefaultNamingStrategy = class {
6135
+ /**
6136
+ * Converts table names to TypeScript symbols
6137
+ * @param table - Table node, function table node, or string name
6138
+ * @returns Capitalized table name (handles schema-qualified names)
6139
+ */
6140
+ tableToSymbol(table) {
6141
+ const tableName = typeof table === "string" ? table : table.name;
6142
+ if (tableName.includes(".")) {
6143
+ return tableName.split(".").map((part) => this.capitalize(part)).join("");
6144
+ }
6145
+ return this.capitalize(tableName);
6146
+ }
6147
+ /**
6148
+ * Converts column references to property names
6149
+ * @param column - Column node
6150
+ * @returns Column name as-is (for backward compatibility)
6151
+ */
6152
+ columnToProperty(column) {
6153
+ return column.name;
6154
+ }
6155
+ /**
6156
+ * Capitalizes the first letter of a string
6157
+ * @param s - String to capitalize
6158
+ * @returns Capitalized string
6159
+ */
6160
+ capitalize(s) {
6161
+ if (!s) return s;
6162
+ return s.charAt(0).toUpperCase() + s.slice(1);
6163
+ }
6164
+ };
6165
+
5952
6166
  // src/codegen/typescript.ts
5953
- var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
5954
6167
  var assertNever2 = (value) => {
5955
6168
  throw new Error(`Unhandled SQL operator: ${value}`);
5956
6169
  };
5957
6170
  var TypeScriptGenerator = class {
6171
+ constructor(namingStrategy = new DefaultNamingStrategy()) {
6172
+ this.namingStrategy = namingStrategy;
6173
+ }
5958
6174
  /**
5959
6175
  * Generates TypeScript code from a query AST
5960
6176
  * @param ast - Query AST to generate code from
@@ -5985,9 +6201,9 @@ var TypeScriptGenerator = class {
5985
6201
  lines.push(` ${sel}${index < selections.length - 1 ? "," : ""}`);
5986
6202
  });
5987
6203
  lines.push(`})`);
5988
- lines.push(`.from(${capitalize(ast.from.name)})`);
6204
+ lines.push(`.from(${this.namingStrategy.tableToSymbol(ast.from)})`);
5989
6205
  if (ast.distinct && ast.distinct.length) {
5990
- const cols = ast.distinct.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
6206
+ const cols = ast.distinct.map((c) => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(", ");
5991
6207
  lines.push(`.distinct(${cols})`);
5992
6208
  }
5993
6209
  ast.joins.forEach((join) => {
@@ -6002,7 +6218,7 @@ var TypeScriptGenerator = class {
6002
6218
  lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
6003
6219
  }
6004
6220
  } else {
6005
- const table = capitalize(join.table.name);
6221
+ const table = this.namingStrategy.tableToSymbol(join.table);
6006
6222
  const cond = this.printExpression(join.condition);
6007
6223
  let method = "innerJoin";
6008
6224
  if (join.kind === "LEFT") method = "leftJoin";
@@ -6023,7 +6239,7 @@ var TypeScriptGenerator = class {
6023
6239
  lines.push(`.where(${this.printExpression(ast.where)})`);
6024
6240
  }
6025
6241
  if (ast.groupBy && ast.groupBy.length) {
6026
- const cols = ast.groupBy.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
6242
+ const cols = ast.groupBy.map((c) => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(", ");
6027
6243
  lines.push(`.groupBy(${cols})`);
6028
6244
  }
6029
6245
  if (ast.having) {
@@ -6031,7 +6247,7 @@ var TypeScriptGenerator = class {
6031
6247
  }
6032
6248
  if (ast.orderBy && ast.orderBy.length) {
6033
6249
  ast.orderBy.forEach((o) => {
6034
- lines.push(`.orderBy(${capitalize(o.column.table)}.${o.column.name}, '${o.direction}')`);
6250
+ lines.push(`.orderBy(${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name}, '${o.direction}')`);
6035
6251
  });
6036
6252
  }
6037
6253
  if (ast.limit) lines.push(`.limit(${ast.limit})`);
@@ -6170,7 +6386,7 @@ var TypeScriptGenerator = class {
6170
6386
  * @returns TypeScript code representation
6171
6387
  */
6172
6388
  printColumnOperand(column) {
6173
- return `${capitalize(column.table)}.${column.name}`;
6389
+ return `${this.namingStrategy.tableToSymbol(column.table)}.${column.name}`;
6174
6390
  }
6175
6391
  /**
6176
6392
  * Prints a literal operand to TypeScript code
@@ -6196,7 +6412,7 @@ var TypeScriptGenerator = class {
6196
6412
  * @returns TypeScript code representation
6197
6413
  */
6198
6414
  printJsonPathOperand(json) {
6199
- return `jsonPath(${capitalize(json.column.table)}.${json.column.name}, '${json.path}')`;
6415
+ return `jsonPath(${this.namingStrategy.tableToSymbol(json.column.table)}.${json.column.name}, '${json.path}')`;
6200
6416
  }
6201
6417
  /**
6202
6418
  * Prints a scalar subquery operand to TypeScript code
@@ -6232,11 +6448,11 @@ var TypeScriptGenerator = class {
6232
6448
  result += ") OVER (";
6233
6449
  const parts = [];
6234
6450
  if (node.partitionBy && node.partitionBy.length > 0) {
6235
- const partitionClause = "PARTITION BY " + node.partitionBy.map((col2) => `${capitalize(col2.table)}.${col2.name}`).join(", ");
6451
+ const partitionClause = "PARTITION BY " + node.partitionBy.map((col2) => `${this.namingStrategy.tableToSymbol(col2.table)}.${col2.name}`).join(", ");
6236
6452
  parts.push(partitionClause);
6237
6453
  }
6238
6454
  if (node.orderBy && node.orderBy.length > 0) {
6239
- const orderClause = "ORDER BY " + node.orderBy.map((o) => `${capitalize(o.column.table)}.${o.column.name} ${o.direction}`).join(", ");
6455
+ const orderClause = "ORDER BY " + node.orderBy.map((o) => `${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name} ${o.direction}`).join(", ");
6240
6456
  parts.push(orderClause);
6241
6457
  }
6242
6458
  result += parts.join(" ");
@@ -6265,46 +6481,24 @@ var TypeScriptGenerator = class {
6265
6481
  }
6266
6482
  };
6267
6483
 
6268
- // src/orm/domain-event-bus.ts
6269
- var DomainEventBus = class {
6270
- constructor(initialHandlers) {
6271
- this.handlers = /* @__PURE__ */ new Map();
6272
- const handlers = initialHandlers ?? {};
6273
- Object.entries(handlers).forEach(([name, list]) => {
6274
- this.handlers.set(name, [...list]);
6275
- });
6276
- }
6277
- register(name, handler) {
6278
- const existing = this.handlers.get(name) ?? [];
6279
- existing.push(handler);
6280
- this.handlers.set(name, existing);
6281
- }
6282
- async dispatch(trackedEntities, ctx) {
6283
- for (const tracked of trackedEntities) {
6284
- const entity = tracked.entity;
6285
- if (!entity.domainEvents || !entity.domainEvents.length) continue;
6286
- for (const event of entity.domainEvents) {
6287
- const eventName = this.getEventName(event);
6288
- const handlers = this.handlers.get(eventName);
6289
- if (!handlers) continue;
6290
- for (const handler of handlers) {
6291
- await handler(event, ctx);
6292
- }
6293
- }
6294
- entity.domainEvents = [];
6295
- }
6296
- }
6297
- getEventName(event) {
6298
- if (!event) return "Unknown";
6299
- if (typeof event === "string") return event;
6300
- return event.constructor?.name ?? "Unknown";
6301
- }
6484
+ // src/orm/entity-metadata.ts
6485
+ var metadataMap = /* @__PURE__ */ new Map();
6486
+ var getEntityMetadata = (target) => {
6487
+ return metadataMap.get(target);
6302
6488
  };
6303
- var addDomainEvent = (entity, event) => {
6304
- if (!entity.domainEvents) {
6305
- entity.domainEvents = [];
6489
+
6490
+ // src/decorators/bootstrap.ts
6491
+ var getTableDefFromEntity = (ctor) => {
6492
+ const meta = getEntityMetadata(ctor);
6493
+ if (!meta) return void 0;
6494
+ return meta.table;
6495
+ };
6496
+ var selectFromEntity = (ctor) => {
6497
+ const table = getTableDefFromEntity(ctor);
6498
+ if (!table) {
6499
+ throw new Error("Entity metadata has not been bootstrapped");
6306
6500
  }
6307
- entity.domainEvents.push(event);
6501
+ return new SelectQueryBuilder(table);
6308
6502
  };
6309
6503
 
6310
6504
  // src/orm/identity-map.ts
@@ -6334,272 +6528,121 @@ var IdentityMap = class {
6334
6528
  const bucket = this.buckets.get(table.name);
6335
6529
  return bucket ? Array.from(bucket.values()) : [];
6336
6530
  }
6531
+ clear() {
6532
+ this.buckets.clear();
6533
+ }
6337
6534
  toIdentityKey(pk) {
6338
6535
  return String(pk);
6339
6536
  }
6340
6537
  };
6341
6538
 
6342
- // src/orm/relation-change-processor.ts
6343
- var RelationChangeProcessor = class {
6344
- constructor(unitOfWork, dialect, executor) {
6345
- this.unitOfWork = unitOfWork;
6539
+ // src/orm/runtime-types.ts
6540
+ var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
6541
+ EntityStatus2["New"] = "new";
6542
+ EntityStatus2["Managed"] = "managed";
6543
+ EntityStatus2["Dirty"] = "dirty";
6544
+ EntityStatus2["Removed"] = "removed";
6545
+ EntityStatus2["Detached"] = "detached";
6546
+ return EntityStatus2;
6547
+ })(EntityStatus || {});
6548
+
6549
+ // src/orm/unit-of-work.ts
6550
+ var UnitOfWork = class {
6551
+ constructor(dialect, executor, identityMap, hookContext) {
6346
6552
  this.dialect = dialect;
6347
6553
  this.executor = executor;
6348
- this.relationChanges = [];
6554
+ this.identityMap = identityMap;
6555
+ this.hookContext = hookContext;
6556
+ this.trackedEntities = /* @__PURE__ */ new Map();
6349
6557
  }
6350
- registerChange(entry) {
6351
- this.relationChanges.push(entry);
6558
+ get identityBuckets() {
6559
+ return this.identityMap.bucketsMap;
6352
6560
  }
6353
- async process() {
6354
- if (!this.relationChanges.length) return;
6355
- const entries = [...this.relationChanges];
6356
- this.relationChanges.length = 0;
6357
- for (const entry of entries) {
6358
- switch (entry.relation.type) {
6359
- case RelationKinds.HasMany:
6360
- await this.handleHasManyChange(entry);
6561
+ getTracked() {
6562
+ return Array.from(this.trackedEntities.values());
6563
+ }
6564
+ getEntity(table, pk) {
6565
+ return this.identityMap.getEntity(table, pk);
6566
+ }
6567
+ getEntitiesForTable(table) {
6568
+ return this.identityMap.getEntitiesForTable(table);
6569
+ }
6570
+ findTracked(entity) {
6571
+ return this.trackedEntities.get(entity);
6572
+ }
6573
+ setEntity(table, pk, entity) {
6574
+ if (pk === null || pk === void 0) return;
6575
+ let tracked = this.trackedEntities.get(entity);
6576
+ if (!tracked) {
6577
+ tracked = {
6578
+ table,
6579
+ entity,
6580
+ pk,
6581
+ status: "managed" /* Managed */,
6582
+ original: this.createSnapshot(table, entity)
6583
+ };
6584
+ this.trackedEntities.set(entity, tracked);
6585
+ } else {
6586
+ tracked.pk = pk;
6587
+ }
6588
+ this.registerIdentity(tracked);
6589
+ }
6590
+ trackNew(table, entity, pk) {
6591
+ const tracked = {
6592
+ table,
6593
+ entity,
6594
+ pk: pk ?? null,
6595
+ status: "new" /* New */,
6596
+ original: null
6597
+ };
6598
+ this.trackedEntities.set(entity, tracked);
6599
+ if (pk != null) {
6600
+ this.registerIdentity(tracked);
6601
+ }
6602
+ }
6603
+ trackManaged(table, pk, entity) {
6604
+ const tracked = {
6605
+ table,
6606
+ entity,
6607
+ pk,
6608
+ status: "managed" /* Managed */,
6609
+ original: this.createSnapshot(table, entity)
6610
+ };
6611
+ this.trackedEntities.set(entity, tracked);
6612
+ this.registerIdentity(tracked);
6613
+ }
6614
+ markDirty(entity) {
6615
+ const tracked = this.trackedEntities.get(entity);
6616
+ if (!tracked) return;
6617
+ if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
6618
+ tracked.status = "dirty" /* Dirty */;
6619
+ }
6620
+ markRemoved(entity) {
6621
+ const tracked = this.trackedEntities.get(entity);
6622
+ if (!tracked) return;
6623
+ tracked.status = "removed" /* Removed */;
6624
+ }
6625
+ async flush() {
6626
+ const toFlush = Array.from(this.trackedEntities.values());
6627
+ for (const tracked of toFlush) {
6628
+ switch (tracked.status) {
6629
+ case "new" /* New */:
6630
+ await this.flushInsert(tracked);
6361
6631
  break;
6362
- case RelationKinds.HasOne:
6363
- await this.handleHasOneChange(entry);
6632
+ case "dirty" /* Dirty */:
6633
+ await this.flushUpdate(tracked);
6364
6634
  break;
6365
- case RelationKinds.BelongsToMany:
6366
- await this.handleBelongsToManyChange(entry);
6635
+ case "removed" /* Removed */:
6636
+ await this.flushDelete(tracked);
6367
6637
  break;
6368
- case RelationKinds.BelongsTo:
6369
- await this.handleBelongsToChange(entry);
6638
+ default:
6370
6639
  break;
6371
6640
  }
6372
6641
  }
6373
6642
  }
6374
- async handleHasManyChange(entry) {
6375
- const relation = entry.relation;
6376
- const target = entry.change.entity;
6377
- if (!target) return;
6378
- const tracked = this.unitOfWork.findTracked(target);
6379
- if (!tracked) return;
6380
- const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6381
- const rootValue = entry.root[localKey];
6382
- if (rootValue === void 0 || rootValue === null) return;
6383
- if (entry.change.kind === "add" || entry.change.kind === "attach") {
6384
- this.assignHasManyForeignKey(tracked.entity, relation, rootValue);
6385
- this.unitOfWork.markDirty(tracked.entity);
6386
- return;
6387
- }
6388
- if (entry.change.kind === "remove") {
6389
- this.detachHasManyChild(tracked.entity, relation);
6390
- }
6391
- }
6392
- async handleHasOneChange(entry) {
6393
- const relation = entry.relation;
6394
- const target = entry.change.entity;
6395
- if (!target) return;
6396
- const tracked = this.unitOfWork.findTracked(target);
6397
- if (!tracked) return;
6398
- const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6399
- const rootValue = entry.root[localKey];
6400
- if (rootValue === void 0 || rootValue === null) return;
6401
- if (entry.change.kind === "attach" || entry.change.kind === "add") {
6402
- this.assignHasOneForeignKey(tracked.entity, relation, rootValue);
6403
- this.unitOfWork.markDirty(tracked.entity);
6404
- return;
6405
- }
6406
- if (entry.change.kind === "remove") {
6407
- this.detachHasOneChild(tracked.entity, relation);
6408
- }
6409
- }
6410
- async handleBelongsToChange(_entry) {
6411
- }
6412
- async handleBelongsToManyChange(entry) {
6413
- const relation = entry.relation;
6414
- const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
6415
- const rootId = entry.root[rootKey];
6416
- if (rootId === void 0 || rootId === null) return;
6417
- const targetId = this.resolvePrimaryKeyValue(entry.change.entity, relation.target);
6418
- if (targetId === null) return;
6419
- if (entry.change.kind === "attach" || entry.change.kind === "add") {
6420
- await this.insertPivotRow(relation, rootId, targetId);
6421
- return;
6422
- }
6423
- if (entry.change.kind === "detach" || entry.change.kind === "remove") {
6424
- await this.deletePivotRow(relation, rootId, targetId);
6425
- if (relation.cascade === "all" || relation.cascade === "remove") {
6426
- this.unitOfWork.markRemoved(entry.change.entity);
6427
- }
6428
- }
6429
- }
6430
- assignHasManyForeignKey(child, relation, rootValue) {
6431
- const current = child[relation.foreignKey];
6432
- if (current === rootValue) return;
6433
- child[relation.foreignKey] = rootValue;
6434
- }
6435
- detachHasManyChild(child, relation) {
6436
- if (relation.cascade === "all" || relation.cascade === "remove") {
6437
- this.unitOfWork.markRemoved(child);
6438
- return;
6439
- }
6440
- child[relation.foreignKey] = null;
6441
- this.unitOfWork.markDirty(child);
6442
- }
6443
- assignHasOneForeignKey(child, relation, rootValue) {
6444
- const current = child[relation.foreignKey];
6445
- if (current === rootValue) return;
6446
- child[relation.foreignKey] = rootValue;
6447
- }
6448
- detachHasOneChild(child, relation) {
6449
- if (relation.cascade === "all" || relation.cascade === "remove") {
6450
- this.unitOfWork.markRemoved(child);
6451
- return;
6452
- }
6453
- child[relation.foreignKey] = null;
6454
- this.unitOfWork.markDirty(child);
6455
- }
6456
- async insertPivotRow(relation, rootId, targetId) {
6457
- const payload = {
6458
- [relation.pivotForeignKeyToRoot]: rootId,
6459
- [relation.pivotForeignKeyToTarget]: targetId
6460
- };
6461
- const builder = new InsertQueryBuilder(relation.pivotTable).values(payload);
6462
- const compiled = builder.compile(this.dialect);
6463
- await this.executor.executeSql(compiled.sql, compiled.params);
6464
- }
6465
- async deletePivotRow(relation, rootId, targetId) {
6466
- const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
6467
- const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
6468
- if (!rootCol || !targetCol) return;
6469
- const builder = new DeleteQueryBuilder(relation.pivotTable).where(
6470
- and(eq(rootCol, rootId), eq(targetCol, targetId))
6471
- );
6472
- const compiled = builder.compile(this.dialect);
6473
- await this.executor.executeSql(compiled.sql, compiled.params);
6474
- }
6475
- resolvePrimaryKeyValue(entity, table) {
6476
- if (!entity) return null;
6477
- const key = findPrimaryKey(table);
6478
- const value = entity[key];
6479
- if (value === void 0 || value === null) return null;
6480
- return value;
6481
- }
6482
- };
6483
-
6484
- // src/orm/transaction-runner.ts
6485
- var runInTransaction = async (executor, action) => {
6486
- if (!executor.beginTransaction) {
6487
- await action();
6488
- return;
6489
- }
6490
- await executor.beginTransaction();
6491
- try {
6492
- await action();
6493
- await executor.commitTransaction?.();
6494
- } catch (error) {
6495
- await executor.rollbackTransaction?.();
6496
- throw error;
6497
- }
6498
- };
6499
-
6500
- // src/orm/runtime-types.ts
6501
- var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
6502
- EntityStatus2["New"] = "new";
6503
- EntityStatus2["Managed"] = "managed";
6504
- EntityStatus2["Dirty"] = "dirty";
6505
- EntityStatus2["Removed"] = "removed";
6506
- EntityStatus2["Detached"] = "detached";
6507
- return EntityStatus2;
6508
- })(EntityStatus || {});
6509
-
6510
- // src/orm/unit-of-work.ts
6511
- var UnitOfWork = class {
6512
- constructor(dialect, executor, identityMap, hookContext) {
6513
- this.dialect = dialect;
6514
- this.executor = executor;
6515
- this.identityMap = identityMap;
6516
- this.hookContext = hookContext;
6517
- this.trackedEntities = /* @__PURE__ */ new Map();
6518
- }
6519
- get identityBuckets() {
6520
- return this.identityMap.bucketsMap;
6521
- }
6522
- getTracked() {
6523
- return Array.from(this.trackedEntities.values());
6524
- }
6525
- getEntity(table, pk) {
6526
- return this.identityMap.getEntity(table, pk);
6527
- }
6528
- getEntitiesForTable(table) {
6529
- return this.identityMap.getEntitiesForTable(table);
6530
- }
6531
- findTracked(entity) {
6532
- return this.trackedEntities.get(entity);
6533
- }
6534
- setEntity(table, pk, entity) {
6535
- if (pk === null || pk === void 0) return;
6536
- let tracked = this.trackedEntities.get(entity);
6537
- if (!tracked) {
6538
- tracked = {
6539
- table,
6540
- entity,
6541
- pk,
6542
- status: "managed" /* Managed */,
6543
- original: this.createSnapshot(table, entity)
6544
- };
6545
- this.trackedEntities.set(entity, tracked);
6546
- } else {
6547
- tracked.pk = pk;
6548
- }
6549
- this.registerIdentity(tracked);
6550
- }
6551
- trackNew(table, entity, pk) {
6552
- const tracked = {
6553
- table,
6554
- entity,
6555
- pk: pk ?? null,
6556
- status: "new" /* New */,
6557
- original: null
6558
- };
6559
- this.trackedEntities.set(entity, tracked);
6560
- if (pk != null) {
6561
- this.registerIdentity(tracked);
6562
- }
6563
- }
6564
- trackManaged(table, pk, entity) {
6565
- const tracked = {
6566
- table,
6567
- entity,
6568
- pk,
6569
- status: "managed" /* Managed */,
6570
- original: this.createSnapshot(table, entity)
6571
- };
6572
- this.trackedEntities.set(entity, tracked);
6573
- this.registerIdentity(tracked);
6574
- }
6575
- markDirty(entity) {
6576
- const tracked = this.trackedEntities.get(entity);
6577
- if (!tracked) return;
6578
- if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
6579
- tracked.status = "dirty" /* Dirty */;
6580
- }
6581
- markRemoved(entity) {
6582
- const tracked = this.trackedEntities.get(entity);
6583
- if (!tracked) return;
6584
- tracked.status = "removed" /* Removed */;
6585
- }
6586
- async flush() {
6587
- const toFlush = Array.from(this.trackedEntities.values());
6588
- for (const tracked of toFlush) {
6589
- switch (tracked.status) {
6590
- case "new" /* New */:
6591
- await this.flushInsert(tracked);
6592
- break;
6593
- case "dirty" /* Dirty */:
6594
- await this.flushUpdate(tracked);
6595
- break;
6596
- case "removed" /* Removed */:
6597
- await this.flushDelete(tracked);
6598
- break;
6599
- default:
6600
- break;
6601
- }
6602
- }
6643
+ reset() {
6644
+ this.trackedEntities.clear();
6645
+ this.identityMap.clear();
6603
6646
  }
6604
6647
  async flushInsert(tracked) {
6605
6648
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
@@ -6720,6 +6763,193 @@ var UnitOfWork = class {
6720
6763
  }
6721
6764
  };
6722
6765
 
6766
+ // src/orm/domain-event-bus.ts
6767
+ var DomainEventBus = class {
6768
+ constructor(initialHandlers) {
6769
+ this.handlers = /* @__PURE__ */ new Map();
6770
+ const handlers = initialHandlers ?? {};
6771
+ Object.entries(handlers).forEach(([name, list]) => {
6772
+ this.handlers.set(name, [...list]);
6773
+ });
6774
+ }
6775
+ register(name, handler) {
6776
+ const existing = this.handlers.get(name) ?? [];
6777
+ existing.push(handler);
6778
+ this.handlers.set(name, existing);
6779
+ }
6780
+ async dispatch(trackedEntities, ctx) {
6781
+ for (const tracked of trackedEntities) {
6782
+ const entity = tracked.entity;
6783
+ if (!entity.domainEvents || !entity.domainEvents.length) continue;
6784
+ for (const event of entity.domainEvents) {
6785
+ const eventName = this.getEventName(event);
6786
+ const handlers = this.handlers.get(eventName);
6787
+ if (!handlers) continue;
6788
+ for (const handler of handlers) {
6789
+ await handler(event, ctx);
6790
+ }
6791
+ }
6792
+ entity.domainEvents = [];
6793
+ }
6794
+ }
6795
+ getEventName(event) {
6796
+ if (!event) return "Unknown";
6797
+ if (typeof event === "string") return event;
6798
+ return event.constructor?.name ?? "Unknown";
6799
+ }
6800
+ };
6801
+ var addDomainEvent = (entity, event) => {
6802
+ if (!entity.domainEvents) {
6803
+ entity.domainEvents = [];
6804
+ }
6805
+ entity.domainEvents.push(event);
6806
+ };
6807
+
6808
+ // src/orm/relation-change-processor.ts
6809
+ var RelationChangeProcessor = class {
6810
+ constructor(unitOfWork, dialect, executor) {
6811
+ this.unitOfWork = unitOfWork;
6812
+ this.dialect = dialect;
6813
+ this.executor = executor;
6814
+ this.relationChanges = [];
6815
+ }
6816
+ registerChange(entry) {
6817
+ this.relationChanges.push(entry);
6818
+ }
6819
+ reset() {
6820
+ this.relationChanges.length = 0;
6821
+ }
6822
+ async process() {
6823
+ if (!this.relationChanges.length) return;
6824
+ const entries = [...this.relationChanges];
6825
+ this.relationChanges.length = 0;
6826
+ for (const entry of entries) {
6827
+ switch (entry.relation.type) {
6828
+ case RelationKinds.HasMany:
6829
+ await this.handleHasManyChange(entry);
6830
+ break;
6831
+ case RelationKinds.HasOne:
6832
+ await this.handleHasOneChange(entry);
6833
+ break;
6834
+ case RelationKinds.BelongsToMany:
6835
+ await this.handleBelongsToManyChange(entry);
6836
+ break;
6837
+ case RelationKinds.BelongsTo:
6838
+ await this.handleBelongsToChange(entry);
6839
+ break;
6840
+ }
6841
+ }
6842
+ }
6843
+ async handleHasManyChange(entry) {
6844
+ const relation = entry.relation;
6845
+ const target = entry.change.entity;
6846
+ if (!target) return;
6847
+ const tracked = this.unitOfWork.findTracked(target);
6848
+ if (!tracked) return;
6849
+ const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6850
+ const rootValue = entry.root[localKey];
6851
+ if (rootValue === void 0 || rootValue === null) return;
6852
+ if (entry.change.kind === "add" || entry.change.kind === "attach") {
6853
+ this.assignHasManyForeignKey(tracked.entity, relation, rootValue);
6854
+ this.unitOfWork.markDirty(tracked.entity);
6855
+ return;
6856
+ }
6857
+ if (entry.change.kind === "remove") {
6858
+ this.detachHasManyChild(tracked.entity, relation);
6859
+ }
6860
+ }
6861
+ async handleHasOneChange(entry) {
6862
+ const relation = entry.relation;
6863
+ const target = entry.change.entity;
6864
+ if (!target) return;
6865
+ const tracked = this.unitOfWork.findTracked(target);
6866
+ if (!tracked) return;
6867
+ const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
6868
+ const rootValue = entry.root[localKey];
6869
+ if (rootValue === void 0 || rootValue === null) return;
6870
+ if (entry.change.kind === "attach" || entry.change.kind === "add") {
6871
+ this.assignHasOneForeignKey(tracked.entity, relation, rootValue);
6872
+ this.unitOfWork.markDirty(tracked.entity);
6873
+ return;
6874
+ }
6875
+ if (entry.change.kind === "remove") {
6876
+ this.detachHasOneChild(tracked.entity, relation);
6877
+ }
6878
+ }
6879
+ async handleBelongsToChange(_entry) {
6880
+ }
6881
+ async handleBelongsToManyChange(entry) {
6882
+ const relation = entry.relation;
6883
+ const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
6884
+ const rootId = entry.root[rootKey];
6885
+ if (rootId === void 0 || rootId === null) return;
6886
+ const targetId = this.resolvePrimaryKeyValue(entry.change.entity, relation.target);
6887
+ if (targetId === null) return;
6888
+ if (entry.change.kind === "attach" || entry.change.kind === "add") {
6889
+ await this.insertPivotRow(relation, rootId, targetId);
6890
+ return;
6891
+ }
6892
+ if (entry.change.kind === "detach" || entry.change.kind === "remove") {
6893
+ await this.deletePivotRow(relation, rootId, targetId);
6894
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6895
+ this.unitOfWork.markRemoved(entry.change.entity);
6896
+ }
6897
+ }
6898
+ }
6899
+ assignHasManyForeignKey(child, relation, rootValue) {
6900
+ const current = child[relation.foreignKey];
6901
+ if (current === rootValue) return;
6902
+ child[relation.foreignKey] = rootValue;
6903
+ }
6904
+ detachHasManyChild(child, relation) {
6905
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6906
+ this.unitOfWork.markRemoved(child);
6907
+ return;
6908
+ }
6909
+ child[relation.foreignKey] = null;
6910
+ this.unitOfWork.markDirty(child);
6911
+ }
6912
+ assignHasOneForeignKey(child, relation, rootValue) {
6913
+ const current = child[relation.foreignKey];
6914
+ if (current === rootValue) return;
6915
+ child[relation.foreignKey] = rootValue;
6916
+ }
6917
+ detachHasOneChild(child, relation) {
6918
+ if (relation.cascade === "all" || relation.cascade === "remove") {
6919
+ this.unitOfWork.markRemoved(child);
6920
+ return;
6921
+ }
6922
+ child[relation.foreignKey] = null;
6923
+ this.unitOfWork.markDirty(child);
6924
+ }
6925
+ async insertPivotRow(relation, rootId, targetId) {
6926
+ const payload = {
6927
+ [relation.pivotForeignKeyToRoot]: rootId,
6928
+ [relation.pivotForeignKeyToTarget]: targetId
6929
+ };
6930
+ const builder = new InsertQueryBuilder(relation.pivotTable).values(payload);
6931
+ const compiled = builder.compile(this.dialect);
6932
+ await this.executor.executeSql(compiled.sql, compiled.params);
6933
+ }
6934
+ async deletePivotRow(relation, rootId, targetId) {
6935
+ const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
6936
+ const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
6937
+ if (!rootCol || !targetCol) return;
6938
+ const builder = new DeleteQueryBuilder(relation.pivotTable).where(
6939
+ and(eq(rootCol, rootId), eq(targetCol, targetId))
6940
+ );
6941
+ const compiled = builder.compile(this.dialect);
6942
+ await this.executor.executeSql(compiled.sql, compiled.params);
6943
+ }
6944
+ resolvePrimaryKeyValue(entity, table) {
6945
+ if (!entity) return null;
6946
+ const key = findPrimaryKey(table);
6947
+ const value = entity[key];
6948
+ if (value === void 0 || value === null) return null;
6949
+ return value;
6950
+ }
6951
+ };
6952
+
6723
6953
  // src/orm/query-logger.ts
6724
6954
  var createQueryLoggingExecutor = (executor, logger) => {
6725
6955
  if (!logger) {
@@ -6743,31 +6973,40 @@ var createQueryLoggingExecutor = (executor, logger) => {
6743
6973
  return wrapped;
6744
6974
  };
6745
6975
 
6746
- // src/orm/orm-context.ts
6747
- var OrmContext = class {
6748
- constructor(options) {
6749
- this.options = options;
6976
+ // src/orm/transaction-runner.ts
6977
+ var runInTransaction = async (executor, action) => {
6978
+ if (!executor.beginTransaction) {
6979
+ await action();
6980
+ return;
6981
+ }
6982
+ await executor.beginTransaction();
6983
+ try {
6984
+ await action();
6985
+ await executor.commitTransaction?.();
6986
+ } catch (error) {
6987
+ await executor.rollbackTransaction?.();
6988
+ throw error;
6989
+ }
6990
+ };
6991
+
6992
+ // src/orm/orm-session.ts
6993
+ var OrmSession = class {
6994
+ constructor(opts) {
6995
+ this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
6996
+ this.relationChanges.registerChange(
6997
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
6998
+ );
6999
+ };
7000
+ this.orm = opts.orm;
7001
+ this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
7002
+ this.interceptors = [...opts.interceptors ?? []];
6750
7003
  this.identityMap = new IdentityMap();
6751
- this.interceptors = [...options.interceptors ?? []];
6752
- this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
6753
- this.unitOfWork = new UnitOfWork(
6754
- options.dialect,
6755
- this.executorWithLogging,
6756
- this.identityMap,
6757
- () => this
6758
- );
6759
- this.relationChanges = new RelationChangeProcessor(
6760
- this.unitOfWork,
6761
- options.dialect,
6762
- this.executorWithLogging
6763
- );
6764
- this.domainEvents = new DomainEventBus(options.domainEventHandlers);
7004
+ this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
7005
+ this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
7006
+ this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
6765
7007
  }
6766
7008
  get dialect() {
6767
- return this.options.dialect;
6768
- }
6769
- get executor() {
6770
- return this.executorWithLogging;
7009
+ return this.orm.dialect;
6771
7010
  }
6772
7011
  get identityBuckets() {
6773
7012
  return this.unitOfWork.identityBuckets;
@@ -6793,16 +7032,8 @@ var OrmContext = class {
6793
7032
  markRemoved(entity) {
6794
7033
  this.unitOfWork.markRemoved(entity);
6795
7034
  }
6796
- registerRelationChange(root, relationKey, rootTable, relationName, relation, change) {
6797
- const entry = {
6798
- root,
6799
- relationKey,
6800
- rootTable,
6801
- relationName,
6802
- relation,
6803
- change
6804
- };
6805
- this.relationChanges.registerChange(entry);
7035
+ getEntitiesForTable(table) {
7036
+ return this.unitOfWork.getEntitiesForTable(table);
6806
7037
  }
6807
7038
  registerInterceptor(interceptor) {
6808
7039
  this.interceptors.push(interceptor);
@@ -6810,7 +7041,51 @@ var OrmContext = class {
6810
7041
  registerDomainEventHandler(name, handler) {
6811
7042
  this.domainEvents.register(name, handler);
6812
7043
  }
6813
- async saveChanges() {
7044
+ async find(entityClass, id) {
7045
+ const table = getTableDefFromEntity(entityClass);
7046
+ if (!table) {
7047
+ throw new Error("Entity metadata has not been bootstrapped");
7048
+ }
7049
+ const primaryKey = findPrimaryKey(table);
7050
+ const column = table.columns[primaryKey];
7051
+ if (!column) {
7052
+ throw new Error("Entity table does not expose a primary key");
7053
+ }
7054
+ const qb = selectFromEntity(entityClass).where(eq(column, id)).limit(1);
7055
+ const rows = await executeHydrated(this, qb);
7056
+ return rows[0] ?? null;
7057
+ }
7058
+ async findOne(qb) {
7059
+ const limited = qb.limit(1);
7060
+ const rows = await executeHydrated(this, limited);
7061
+ return rows[0] ?? null;
7062
+ }
7063
+ async findMany(qb) {
7064
+ return executeHydrated(this, qb);
7065
+ }
7066
+ async persist(entity) {
7067
+ if (this.unitOfWork.findTracked(entity)) {
7068
+ return;
7069
+ }
7070
+ const table = getTableDefFromEntity(entity.constructor);
7071
+ if (!table) {
7072
+ throw new Error("Entity metadata has not been bootstrapped");
7073
+ }
7074
+ const primaryKey = findPrimaryKey(table);
7075
+ const pkValue = entity[primaryKey];
7076
+ if (pkValue !== void 0 && pkValue !== null) {
7077
+ this.trackManaged(table, pkValue, entity);
7078
+ } else {
7079
+ this.trackNew(table, entity);
7080
+ }
7081
+ }
7082
+ async remove(entity) {
7083
+ this.markRemoved(entity);
7084
+ }
7085
+ async flush() {
7086
+ await this.unitOfWork.flush();
7087
+ }
7088
+ async commit() {
6814
7089
  await runInTransaction(this.executor, async () => {
6815
7090
  for (const interceptor of this.interceptors) {
6816
7091
  await interceptor.beforeFlush?.(this);
@@ -6824,8 +7099,83 @@ var OrmContext = class {
6824
7099
  });
6825
7100
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
6826
7101
  }
6827
- getEntitiesForTable(table) {
6828
- return this.unitOfWork.getEntitiesForTable(table);
7102
+ async rollback() {
7103
+ await this.executor.rollbackTransaction?.();
7104
+ this.unitOfWork.reset();
7105
+ this.relationChanges.reset();
7106
+ }
7107
+ getExecutionContext() {
7108
+ return {
7109
+ dialect: this.orm.dialect,
7110
+ executor: this.executor,
7111
+ interceptors: this.orm.interceptors
7112
+ };
7113
+ }
7114
+ getHydrationContext() {
7115
+ return {
7116
+ identityMap: this.identityMap,
7117
+ unitOfWork: this.unitOfWork,
7118
+ domainEvents: this.domainEvents,
7119
+ relationChanges: this.relationChanges,
7120
+ entityContext: this
7121
+ };
7122
+ }
7123
+ };
7124
+ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, relation, change) => ({
7125
+ root,
7126
+ relationKey,
7127
+ rootTable,
7128
+ relationName,
7129
+ relation,
7130
+ change
7131
+ });
7132
+
7133
+ // src/orm/interceptor-pipeline.ts
7134
+ var InterceptorPipeline = class {
7135
+ constructor() {
7136
+ this.interceptors = [];
7137
+ }
7138
+ use(interceptor) {
7139
+ this.interceptors.push(interceptor);
7140
+ }
7141
+ async run(ctx, executor) {
7142
+ let i = 0;
7143
+ const dispatch = async () => {
7144
+ const interceptor = this.interceptors[i++];
7145
+ if (!interceptor) {
7146
+ return executor.executeSql(ctx.sql, ctx.params);
7147
+ }
7148
+ return interceptor(ctx, dispatch);
7149
+ };
7150
+ return dispatch();
7151
+ }
7152
+ };
7153
+
7154
+ // src/orm/orm.ts
7155
+ var Orm = class {
7156
+ constructor(opts) {
7157
+ this.dialect = opts.dialect;
7158
+ this.interceptors = opts.interceptors ?? new InterceptorPipeline();
7159
+ this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
7160
+ this.executorFactory = opts.executorFactory;
7161
+ }
7162
+ createSession(options) {
7163
+ const executor = this.executorFactory.createExecutor(options?.tx);
7164
+ return new OrmSession({ orm: this, executor });
7165
+ }
7166
+ // Nice convenience:
7167
+ async transaction(fn4) {
7168
+ const executor = this.executorFactory.createTransactionalExecutor();
7169
+ const session = new OrmSession({ orm: this, executor });
7170
+ try {
7171
+ const result = await fn4(session);
7172
+ await session.commit();
7173
+ return result;
7174
+ } catch (err) {
7175
+ await session.rollback();
7176
+ throw err;
7177
+ } finally {
7178
+ }
6829
7179
  }
6830
7180
  };
6831
7181
 
@@ -6941,10 +7291,12 @@ function createMssqlExecutor(client) {
6941
7291
  DefaultHasManyCollection,
6942
7292
  DefaultManyToManyCollection,
6943
7293
  DeleteQueryBuilder,
7294
+ DomainEventBus,
6944
7295
  EntityStatus,
6945
7296
  InsertQueryBuilder,
6946
7297
  MySqlDialect,
6947
- OrmContext,
7298
+ Orm,
7299
+ OrmSession,
6948
7300
  PostgresDialect,
6949
7301
  RelationKinds,
6950
7302
  SelectQueryBuilder,
@@ -6986,6 +7338,7 @@ function createMssqlExecutor(client) {
6986
7338
  createMssqlExecutor,
6987
7339
  createMysqlExecutor,
6988
7340
  createPostgresExecutor,
7341
+ createQueryLoggingExecutor,
6989
7342
  createSqliteExecutor,
6990
7343
  currentDate,
6991
7344
  currentTime,
@@ -7003,6 +7356,7 @@ function createMssqlExecutor(client) {
7003
7356
  endOfMonth,
7004
7357
  eq,
7005
7358
  executeHydrated,
7359
+ executeHydratedWithContexts,
7006
7360
  exists,
7007
7361
  exp,
7008
7362
  extract,