metal-orm 1.0.16 → 1.0.17

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 (45) hide show
  1. package/README.md +33 -37
  2. package/dist/decorators/index.cjs +152 -23
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -1
  5. package/dist/decorators/index.d.ts +1 -1
  6. package/dist/decorators/index.js +152 -23
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +322 -115
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +53 -4
  11. package/dist/index.d.ts +53 -4
  12. package/dist/index.js +316 -115
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-BKZrMRCQ.d.cts → select-BPCn6MOH.d.cts} +183 -64
  15. package/dist/{select-BKZrMRCQ.d.ts → select-BPCn6MOH.d.ts} +183 -64
  16. package/package.json +2 -1
  17. package/src/core/ast/aggregate-functions.ts +50 -4
  18. package/src/core/ast/expression-builders.ts +22 -15
  19. package/src/core/ast/expression-nodes.ts +6 -0
  20. package/src/core/ddl/introspect/functions/postgres.ts +2 -6
  21. package/src/core/dialect/abstract.ts +12 -8
  22. package/src/core/dialect/mssql/functions.ts +24 -15
  23. package/src/core/dialect/postgres/functions.ts +33 -24
  24. package/src/core/dialect/sqlite/functions.ts +19 -12
  25. package/src/core/functions/datetime.ts +2 -1
  26. package/src/core/functions/numeric.ts +2 -1
  27. package/src/core/functions/standard-strategy.ts +52 -12
  28. package/src/core/functions/text.ts +2 -1
  29. package/src/core/functions/types.ts +8 -8
  30. package/src/index.ts +5 -4
  31. package/src/orm/domain-event-bus.ts +43 -25
  32. package/src/orm/entity-meta.ts +40 -0
  33. package/src/orm/execution-context.ts +6 -0
  34. package/src/orm/hydration-context.ts +6 -4
  35. package/src/orm/orm-session.ts +35 -24
  36. package/src/orm/orm.ts +10 -10
  37. package/src/orm/query-logger.ts +15 -0
  38. package/src/orm/runtime-types.ts +60 -2
  39. package/src/orm/transaction-runner.ts +7 -0
  40. package/src/orm/unit-of-work.ts +1 -0
  41. package/src/query-builder/insert-query-state.ts +13 -3
  42. package/src/query-builder/select-helpers.ts +50 -0
  43. package/src/query-builder/select.ts +122 -30
  44. package/src/query-builder/update-query-state.ts +31 -9
  45. package/src/schema/types.ts +16 -6
@@ -348,6 +348,10 @@ interface TableDef<T extends Record<string, ColumnDef> = Record<string, ColumnDe
348
348
  */
349
349
  declare const defineTable: <T extends Record<string, ColumnDef>>(name: string, columns: T, relations?: Record<string, RelationDef>, hooks?: TableHooks, options?: TableOptions) => TableDef<T>;
350
350
 
351
+ /**
352
+ * Resolves a relation definition to its target table type.
353
+ */
354
+ type RelationTargetTable<TRel extends RelationDef> = TRel extends HasManyRelation<infer TTarget> ? TTarget : TRel extends HasOneRelation<infer TTarget> ? TTarget : TRel extends BelongsToRelation<infer TTarget> ? TTarget : TRel extends BelongsToManyRelation<infer TTarget> ? TTarget : never;
351
355
  /**
352
356
  * Maps a ColumnDef to its TypeScript type representation
353
357
  */
@@ -544,6 +548,12 @@ interface FunctionNode {
544
548
  args: OperandNode[];
545
549
  /** Optional alias for the function result */
546
550
  alias?: string;
551
+ /** Optional ORDER BY clause used by aggregations like GROUP_CONCAT */
552
+ orderBy?: OrderByNode[];
553
+ /** Optional separator argument used by GROUP_CONCAT-like functions */
554
+ separator?: OperandNode;
555
+ /** Optional DISTINCT modifier */
556
+ distinct?: boolean;
547
557
  }
548
558
  /**
549
559
  * AST node representing a JSON path expression
@@ -893,6 +903,8 @@ interface HydrationMetadata {
893
903
  interface FunctionRenderContext {
894
904
  node: FunctionNode;
895
905
  compiledArgs: string[];
906
+ /** Helper to compile additional operands (e.g., separators or ORDER BY columns) */
907
+ compileOperand: (operand: OperandNode) => string;
896
908
  }
897
909
  type FunctionRenderer = (ctx: FunctionRenderContext) => string;
898
910
  interface FunctionStrategy {
@@ -1587,6 +1599,101 @@ type EntityConstructor = new (...args: any[]) => any;
1587
1599
  type EntityOrTableTarget = EntityConstructor | TableDef;
1588
1600
  type EntityOrTableTargetResolver = EntityOrTableTarget | (() => EntityOrTableTarget);
1589
1601
 
1602
+ /**
1603
+ * Entity status enum representing the lifecycle state of an entity
1604
+ */
1605
+ declare enum EntityStatus {
1606
+ /** Entity is newly created and not yet persisted */
1607
+ New = "new",
1608
+ /** Entity is managed by the ORM and synchronized with the database */
1609
+ Managed = "managed",
1610
+ /** Entity has been modified but not yet persisted */
1611
+ Dirty = "dirty",
1612
+ /** Entity has been marked for removal */
1613
+ Removed = "removed",
1614
+ /** Entity is detached from the ORM context */
1615
+ Detached = "detached"
1616
+ }
1617
+ /**
1618
+ * Represents an entity being tracked by the ORM
1619
+ */
1620
+ interface TrackedEntity {
1621
+ /** The table definition this entity belongs to */
1622
+ table: TableDef;
1623
+ /** The actual entity instance */
1624
+ entity: any;
1625
+ /** Primary key value of the entity */
1626
+ pk: string | number | null;
1627
+ /** Current status of the entity */
1628
+ status: EntityStatus;
1629
+ /** Original values of the entity when it was loaded */
1630
+ original: Record<string, any> | null;
1631
+ }
1632
+ /**
1633
+ * Type representing a key for relation navigation
1634
+ */
1635
+ type RelationKey = string;
1636
+ /**
1637
+ * Represents a change operation on a relation
1638
+ * @typeParam T - Type of the related entity
1639
+ */
1640
+ type RelationChange<T> = {
1641
+ kind: 'add';
1642
+ entity: T;
1643
+ } | {
1644
+ kind: 'attach';
1645
+ entity: T;
1646
+ } | {
1647
+ kind: 'remove';
1648
+ entity: T;
1649
+ } | {
1650
+ kind: 'detach';
1651
+ entity: T;
1652
+ };
1653
+ /**
1654
+ * Represents a relation change entry in the unit of work
1655
+ */
1656
+ interface RelationChangeEntry {
1657
+ /** Root entity that owns the relation */
1658
+ root: any;
1659
+ /** Key of the relation being changed */
1660
+ relationKey: RelationKey;
1661
+ /** Table definition of the root entity */
1662
+ rootTable: TableDef;
1663
+ /** Name of the relation */
1664
+ relationName: string;
1665
+ /** Relation definition */
1666
+ relation: RelationDef;
1667
+ /** The change being applied */
1668
+ change: RelationChange<any>;
1669
+ }
1670
+ /**
1671
+ * Represents a domain event that can be emitted by entities
1672
+ * @typeParam TType - Type of the event (string literal)
1673
+ */
1674
+ interface DomainEvent<TType extends string = string> {
1675
+ /** Type identifier for the event */
1676
+ readonly type: TType;
1677
+ /** Timestamp when the event occurred */
1678
+ readonly occurredAt?: Date;
1679
+ }
1680
+ /**
1681
+ * Type representing any domain event
1682
+ */
1683
+ type AnyDomainEvent = DomainEvent<string>;
1684
+ /**
1685
+ * Type representing ORM-specific domain events
1686
+ */
1687
+ type OrmDomainEvent = AnyDomainEvent;
1688
+ /**
1689
+ * Interface for entities that can emit domain events
1690
+ * @typeParam E - Type of domain events this entity can emit
1691
+ */
1692
+ interface HasDomainEvents<E extends DomainEvent = AnyDomainEvent> {
1693
+ /** Array of domain events emitted by this entity */
1694
+ domainEvents?: E[];
1695
+ }
1696
+
1590
1697
  /**
1591
1698
  * Strategy interface for converting database names to TypeScript identifiers
1592
1699
  */
@@ -1616,7 +1723,7 @@ declare class InterceptorPipeline {
1616
1723
  run(ctx: QueryContext, executor: DbExecutor): Promise<QueryResult[]>;
1617
1724
  }
1618
1725
 
1619
- interface OrmOptions {
1726
+ interface OrmOptions<E extends DomainEvent = OrmDomainEvent> {
1620
1727
  dialect: Dialect;
1621
1728
  executorFactory: DbExecutorFactory;
1622
1729
  interceptors?: InterceptorPipeline;
@@ -1630,56 +1737,16 @@ interface DbExecutorFactory {
1630
1737
  }
1631
1738
  interface ExternalTransaction {
1632
1739
  }
1633
- declare class Orm {
1740
+ declare class Orm<E extends DomainEvent = OrmDomainEvent> {
1634
1741
  readonly dialect: Dialect;
1635
1742
  readonly interceptors: InterceptorPipeline;
1636
1743
  readonly namingStrategy: NamingStrategy;
1637
1744
  private readonly executorFactory;
1638
- constructor(opts: OrmOptions);
1745
+ constructor(opts: OrmOptions<E>);
1639
1746
  createSession(options?: {
1640
1747
  tx?: ExternalTransaction;
1641
- }): OrmSession;
1642
- transaction<T>(fn: (session: OrmSession) => Promise<T>): Promise<T>;
1643
- }
1644
-
1645
- declare enum EntityStatus {
1646
- New = "new",
1647
- Managed = "managed",
1648
- Dirty = "dirty",
1649
- Removed = "removed",
1650
- Detached = "detached"
1651
- }
1652
- interface TrackedEntity {
1653
- table: TableDef;
1654
- entity: any;
1655
- pk: string | number | null;
1656
- status: EntityStatus;
1657
- original: Record<string, any> | null;
1658
- }
1659
- type RelationKey = string;
1660
- type RelationChange<T> = {
1661
- kind: 'add';
1662
- entity: T;
1663
- } | {
1664
- kind: 'attach';
1665
- entity: T;
1666
- } | {
1667
- kind: 'remove';
1668
- entity: T;
1669
- } | {
1670
- kind: 'detach';
1671
- entity: T;
1672
- };
1673
- interface RelationChangeEntry {
1674
- root: any;
1675
- relationKey: RelationKey;
1676
- rootTable: TableDef;
1677
- relationName: string;
1678
- relation: RelationDef;
1679
- change: RelationChange<any>;
1680
- }
1681
- interface HasDomainEvents {
1682
- domainEvents?: any[];
1748
+ }): OrmSession<E>;
1749
+ transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T>;
1683
1750
  }
1684
1751
 
1685
1752
  declare class IdentityMap {
@@ -1727,15 +1794,21 @@ declare class UnitOfWork {
1727
1794
  private getPrimaryKeyValue;
1728
1795
  }
1729
1796
 
1730
- type DomainEventHandler<Context> = (event: any, ctx: Context) => Promise<void> | void;
1731
- declare class DomainEventBus<Context> {
1797
+ type EventOfType<E extends DomainEvent, TType extends E['type']> = Extract<E, {
1798
+ type: TType;
1799
+ }>;
1800
+ type DomainEventHandler<E extends DomainEvent, Context> = (event: E, ctx: Context) => Promise<void> | void;
1801
+ type InitialHandlers<E extends DomainEvent, Context> = {
1802
+ [K in E['type']]?: DomainEventHandler<EventOfType<E, K>, Context>[];
1803
+ };
1804
+ declare class DomainEventBus<E extends DomainEvent, Context> {
1732
1805
  private readonly handlers;
1733
- constructor(initialHandlers?: Record<string, DomainEventHandler<Context>[]>);
1734
- register(name: string, handler: DomainEventHandler<Context>): void;
1806
+ constructor(initialHandlers?: InitialHandlers<E, Context>);
1807
+ on<TType extends E['type']>(type: TType, handler: DomainEventHandler<EventOfType<E, TType>, Context>): void;
1808
+ register<TType extends E['type']>(type: TType, handler: DomainEventHandler<EventOfType<E, TType>, Context>): void;
1735
1809
  dispatch(trackedEntities: Iterable<TrackedEntity>, ctx: Context): Promise<void>;
1736
- private getEventName;
1737
1810
  }
1738
- declare const addDomainEvent: (entity: HasDomainEvents, event: any) => void;
1811
+ declare const addDomainEvent: <E extends DomainEvent>(entity: HasDomainEvents<E>, event: E) => void;
1739
1812
 
1740
1813
  declare class RelationChangeProcessor {
1741
1814
  private readonly unitOfWork;
@@ -1759,16 +1832,37 @@ declare class RelationChangeProcessor {
1759
1832
  private resolvePrimaryKeyValue;
1760
1833
  }
1761
1834
 
1835
+ /**
1836
+ * Represents a single SQL query log entry
1837
+ */
1762
1838
  interface QueryLogEntry {
1839
+ /** The SQL query that was executed */
1763
1840
  sql: string;
1841
+ /** Parameters used in the query */
1764
1842
  params?: unknown[];
1765
1843
  }
1844
+ /**
1845
+ * Function type for query logging callbacks
1846
+ * @param entry - The query log entry to process
1847
+ */
1766
1848
  type QueryLogger = (entry: QueryLogEntry) => void;
1849
+ /**
1850
+ * Creates a wrapped database executor that logs all SQL queries
1851
+ * @param executor - Original database executor to wrap
1852
+ * @param logger - Optional logger function to receive query log entries
1853
+ * @returns Wrapped executor that logs queries before execution
1854
+ */
1767
1855
  declare const createQueryLoggingExecutor: (executor: DbExecutor, logger?: QueryLogger) => DbExecutor;
1768
1856
 
1857
+ /**
1858
+ * Context for SQL query execution
1859
+ */
1769
1860
  interface ExecutionContext {
1861
+ /** Database dialect to use for SQL generation */
1770
1862
  dialect: Dialect;
1863
+ /** Database executor for running SQL queries */
1771
1864
  executor: DbExecutor;
1865
+ /** Interceptor pipeline for query processing */
1772
1866
  interceptors: InterceptorPipeline;
1773
1867
  }
1774
1868
 
@@ -1785,10 +1879,10 @@ interface EntityContext {
1785
1879
  registerRelationChange(root: any, relationKey: RelationKey, rootTable: TableDef, relationName: string, relation: RelationDef, change: RelationChange<any>): void;
1786
1880
  }
1787
1881
 
1788
- interface HydrationContext {
1882
+ interface HydrationContext<E extends DomainEvent = AnyDomainEvent> {
1789
1883
  identityMap: IdentityMap;
1790
1884
  unitOfWork: UnitOfWork;
1791
- domainEvents: DomainEventBus<any>;
1885
+ domainEvents: DomainEventBus<E, OrmSession<E>>;
1792
1886
  relationChanges: RelationChangeProcessor;
1793
1887
  entityContext: EntityContext;
1794
1888
  }
@@ -1797,22 +1891,22 @@ interface OrmInterceptor {
1797
1891
  beforeFlush?(ctx: EntityContext): Promise<void> | void;
1798
1892
  afterFlush?(ctx: EntityContext): Promise<void> | void;
1799
1893
  }
1800
- interface OrmSessionOptions {
1801
- orm: Orm;
1894
+ interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
1895
+ orm: Orm<E>;
1802
1896
  executor: DbExecutor;
1803
1897
  queryLogger?: QueryLogger;
1804
1898
  interceptors?: OrmInterceptor[];
1805
- domainEventHandlers?: Record<string, DomainEventHandler<OrmSession>[]>;
1899
+ domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
1806
1900
  }
1807
- declare class OrmSession implements EntityContext {
1808
- readonly orm: Orm;
1901
+ declare class OrmSession<E extends DomainEvent = OrmDomainEvent> implements EntityContext {
1902
+ readonly orm: Orm<E>;
1809
1903
  readonly executor: DbExecutor;
1810
1904
  readonly identityMap: IdentityMap;
1811
1905
  readonly unitOfWork: UnitOfWork;
1812
- readonly domainEvents: DomainEventBus<OrmSession>;
1906
+ readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
1813
1907
  readonly relationChanges: RelationChangeProcessor;
1814
1908
  private readonly interceptors;
1815
- constructor(opts: OrmSessionOptions);
1909
+ constructor(opts: OrmSessionOptions<E>);
1816
1910
  get dialect(): Dialect;
1817
1911
  get identityBuckets(): Map<string, Map<string, TrackedEntity>>;
1818
1912
  get tracked(): TrackedEntity[];
@@ -1825,7 +1919,9 @@ declare class OrmSession implements EntityContext {
1825
1919
  registerRelationChange: (root: any, relationKey: RelationKey, rootTable: TableDef, relationName: string, relation: RelationDef, change: RelationChange<any>) => void;
1826
1920
  getEntitiesForTable(table: TableDef): TrackedEntity[];
1827
1921
  registerInterceptor(interceptor: OrmInterceptor): void;
1828
- registerDomainEventHandler(name: string, handler: DomainEventHandler<OrmSession>): void;
1922
+ registerDomainEventHandler<TType extends E['type']>(type: TType, handler: DomainEventHandler<Extract<E, {
1923
+ type: TType;
1924
+ }>, OrmSession<E>>): void;
1829
1925
  find<TTable extends TableDef>(entityClass: EntityConstructor, id: any): Promise<Entity<TTable> | null>;
1830
1926
  findOne<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable> | null>;
1831
1927
  findMany<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable>[]>;
@@ -1835,11 +1931,17 @@ declare class OrmSession implements EntityContext {
1835
1931
  commit(): Promise<void>;
1836
1932
  rollback(): Promise<void>;
1837
1933
  getExecutionContext(): ExecutionContext;
1838
- getHydrationContext(): HydrationContext;
1934
+ getHydrationContext(): HydrationContext<E>;
1839
1935
  }
1840
1936
 
1841
1937
  type SelectDialectInput = Dialect | DialectKey;
1842
1938
 
1939
+ type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
1940
+ type DeepSelectConfig<TTable extends TableDef> = {
1941
+ root?: (keyof TTable['columns'] & string)[];
1942
+ } & {
1943
+ [K in keyof TTable['relations'] & string]?: (keyof RelationTargetTable<TTable['relations'][K]>['columns'] & string)[];
1944
+ };
1843
1945
  /**
1844
1946
 
1845
1947
  * Main query builder class for constructing SQL SELECT queries
@@ -1884,7 +1986,12 @@ declare class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
1884
1986
  * @returns New query builder instance with selected columns
1885
1987
 
1886
1988
  */
1887
- select(columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>): SelectQueryBuilder<T, TTable>;
1989
+ select(columns: Record<string, ColumnSelectionValue>): SelectQueryBuilder<T, TTable>;
1990
+ /**
1991
+ * Selects columns from the root table by name (typed).
1992
+ * @param cols - Column names on the root table
1993
+ */
1994
+ selectColumns<K extends keyof TTable['columns'] & string>(...cols: K[]): SelectQueryBuilder<T, TTable>;
1888
1995
  /**
1889
1996
 
1890
1997
  * Selects raw column expressions
@@ -2010,6 +2117,18 @@ declare class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
2010
2117
  */
2011
2118
  include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T, TTable>;
2012
2119
  includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable>;
2120
+ /**
2121
+ * Selects columns for a related table in a single hop.
2122
+ */
2123
+ selectRelationColumns<K extends keyof TTable['relations'] & string, TRel extends RelationDef = TTable['relations'][K], TTarget extends TableDef = RelationTargetTable<TRel>, C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string>(relationName: K, ...cols: C[]): SelectQueryBuilder<T, TTable>;
2124
+ /**
2125
+ * Convenience alias for selecting specific columns from a relation.
2126
+ */
2127
+ includePick<K extends keyof TTable['relations'] & string, TRel extends RelationDef = TTable['relations'][K], TTarget extends TableDef = RelationTargetTable<TRel>, C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable>;
2128
+ /**
2129
+ * Selects columns for the root table and relations from a single config object.
2130
+ */
2131
+ selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable>;
2013
2132
  getLazyRelations(): (keyof RelationMap<TTable>)[];
2014
2133
  getTable(): TTable;
2015
2134
  execute(ctx: OrmSession): Promise<Entity<TTable>[]>;
@@ -2230,4 +2349,4 @@ declare const createColumn: (table: string, name: string) => ColumnNode;
2230
2349
  */
2231
2350
  declare const createLiteral: (val: string | number) => LiteralNode;
2232
2351
 
2233
- export { type TableHooks as $, type BelongsToRelation as A, type BinaryExpressionNode as B, type ColumnRef as C, Dialect as D, type ExpressionNode as E, type FunctionNode as F, type BelongsToManyRelation as G, type HydrationPlan as H, type InExpressionNode as I, type JsonPathNode as J, type HasManyCollection as K, type LogicalExpressionNode as L, type BelongsToReference as M, type NullExpressionNode as N, type OperandNode as O, type ManyToManyCollection as P, OrmSession as Q, type RelationMap as R, type SelectQueryNode as S, type TableRef as T, type UpdateQueryNode as U, SelectQueryBuilder as V, type WindowFunctionNode as W, type ExecutionContext as X, type HydrationContext as Y, type CheckConstraint as Z, type TableOptions as _, type ColumnNode as a, defineTable as a0, type ColumnType as a1, type ReferentialAction as a2, type RawDefaultValue as a3, type DefaultValue as a4, col as a5, RelationKinds as a6, type RelationType as a7, type CascadeMode as a8, type RelationDef as a9, EntityStatus as aA, type TrackedEntity as aB, type RelationKey as aC, type RelationChange as aD, type RelationChangeEntry as aE, type HasDomainEvents as aF, type QueryLogEntry as aG, type QueryLogger as aH, createQueryLoggingExecutor as aI, type QueryResult as aJ, rowsToQueryResult as aK, type SimpleQueryRunner as aL, createExecutorFromQueryRunner as aM, type EntityOrTableTargetResolver as aN, type EntityConstructor as aO, hasMany as aa, hasOne as ab, belongsTo as ac, belongsToMany as ad, type ColumnToTs as ae, type InferRow as af, type HasOneReference as ag, createColumn as ah, createLiteral as ai, isOperandNode as aj, isFunctionNode as ak, isCaseExpressionNode as al, isWindowFunctionNode as am, isExpressionSelectionNode as an, type HydrationPivotPlan as ao, type HydrationRelationPlan as ap, type HydrationMetadata as aq, type OrmInterceptor as ar, type OrmSessionOptions as as, type OrmOptions as at, type DbExecutorFactory as au, type ExternalTransaction as av, Orm as aw, type DomainEventHandler as ax, DomainEventBus as ay, addDomainEvent as az, type LiteralNode as b, type BetweenExpressionNode as c, type CaseExpressionNode as d, type ExistsExpressionNode as e, type OrderDirection as f, type ScalarSubqueryNode as g, type ColumnDef as h, type TableDef as i, type InsertQueryNode as j, type InsertCompiler as k, type CompiledQuery as l, type DialectKey as m, type UpdateCompiler as n, type DeleteQueryNode as o, type DeleteCompiler as p, type CompilerContext as q, type ForeignKeyReference as r, type IndexColumn as s, type IndexDef as t, type DbExecutor as u, type NamingStrategy as v, type EntityContext as w, type Entity as x, type HasManyRelation as y, type HasOneRelation as z };
2352
+ export { type TableHooks as $, type BelongsToRelation as A, type BinaryExpressionNode as B, type ColumnRef as C, Dialect as D, type ExpressionNode as E, type FunctionNode as F, type BelongsToManyRelation as G, type HydrationPlan as H, type InExpressionNode as I, type JsonPathNode as J, type HasManyCollection as K, type LiteralNode as L, type BelongsToReference as M, type NullExpressionNode as N, type OperandNode as O, type ManyToManyCollection as P, OrmSession as Q, type RelationMap as R, type SelectQueryNode as S, type TableRef as T, type UpdateQueryNode as U, SelectQueryBuilder as V, type WindowFunctionNode as W, type ExecutionContext as X, type HydrationContext as Y, type CheckConstraint as Z, type TableOptions as _, type ColumnNode as a, defineTable as a0, type ColumnType as a1, type ReferentialAction as a2, type RawDefaultValue as a3, type DefaultValue as a4, col as a5, RelationKinds as a6, type RelationType as a7, type CascadeMode as a8, type RelationDef as a9, DomainEventBus as aA, addDomainEvent as aB, EntityStatus as aC, type TrackedEntity as aD, type RelationKey as aE, type RelationChange as aF, type RelationChangeEntry as aG, type DomainEvent as aH, type AnyDomainEvent as aI, type OrmDomainEvent as aJ, type HasDomainEvents as aK, type QueryLogEntry as aL, type QueryLogger as aM, createQueryLoggingExecutor as aN, type QueryResult as aO, rowsToQueryResult as aP, type SimpleQueryRunner as aQ, createExecutorFromQueryRunner as aR, type EntityOrTableTargetResolver as aS, type EntityConstructor as aT, hasMany as aa, hasOne as ab, belongsTo as ac, belongsToMany as ad, type RelationTargetTable as ae, type ColumnToTs as af, type InferRow as ag, type HasOneReference as ah, createColumn as ai, createLiteral as aj, isOperandNode as ak, isFunctionNode as al, isCaseExpressionNode as am, isWindowFunctionNode as an, isExpressionSelectionNode as ao, type HydrationPivotPlan as ap, type HydrationRelationPlan as aq, type HydrationMetadata as ar, type OrmInterceptor as as, type OrmSessionOptions as at, type OrmOptions as au, type DbExecutorFactory as av, type ExternalTransaction as aw, Orm as ax, type DomainEventHandler as ay, type InitialHandlers as az, type LogicalExpressionNode as b, type BetweenExpressionNode as c, type CaseExpressionNode as d, type ExistsExpressionNode as e, type OrderDirection as f, type ScalarSubqueryNode as g, type ColumnDef as h, type TableDef as i, type InsertQueryNode as j, type InsertCompiler as k, type CompiledQuery as l, type DialectKey as m, type UpdateCompiler as n, type DeleteQueryNode as o, type DeleteCompiler as p, type CompilerContext as q, type ForeignKeyReference as r, type IndexColumn as s, type IndexDef as t, type DbExecutor as u, type NamingStrategy as v, type EntityContext as w, type Entity as x, type HasManyRelation as y, type HasOneRelation as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@vitest/ui": "^4.0.14",
49
+ "sqlite3": "^5.1.7",
49
50
  "tsup": "^8.0.0",
50
51
  "typescript": "^5.5.0",
51
52
  "vitest": "^4.0.14"
@@ -1,6 +1,8 @@
1
1
  import { ColumnNode, FunctionNode } from './expression-nodes.js';
2
- import { columnOperand } from './expression-builders.js';
2
+ import { columnOperand, valueToOperand, ValueOperandInput } from './expression-builders.js';
3
3
  import { ColumnRef } from './types.js';
4
+ import { OrderByNode } from './query.js';
5
+ import { ORDER_DIRECTIONS, OrderDirection } from '../sql/sql.js';
4
6
 
5
7
  const buildAggregate = (name: string) => (col: ColumnRef | ColumnNode): FunctionNode => ({
6
8
  type: 'Function',
@@ -25,6 +27,50 @@ export const sum = buildAggregate('SUM');
25
27
  /**
26
28
  * Creates an AVG function expression
27
29
  * @param col - Column to average
28
- * @returns Function node with AVG
29
- */
30
- export const avg = buildAggregate('AVG');
30
+ * @returns Function node with AVG
31
+ */
32
+ export const avg = buildAggregate('AVG');
33
+
34
+ /**
35
+ * Creates a MIN function expression
36
+ * @param col - Column to take the minimum of
37
+ * @returns Function node with MIN
38
+ */
39
+ export const min = buildAggregate('MIN');
40
+
41
+ /**
42
+ * Creates a MAX function expression
43
+ * @param col - Column to take the maximum of
44
+ * @returns Function node with MAX
45
+ */
46
+ export const max = buildAggregate('MAX');
47
+
48
+ type GroupConcatOrderByInput = {
49
+ column: ColumnRef | ColumnNode;
50
+ direction?: OrderDirection;
51
+ };
52
+
53
+ export type GroupConcatOptions = {
54
+ separator?: ValueOperandInput;
55
+ orderBy?: GroupConcatOrderByInput[];
56
+ };
57
+
58
+ const toOrderByNode = (order: GroupConcatOrderByInput): OrderByNode => ({
59
+ type: 'OrderBy',
60
+ column: columnOperand(order.column),
61
+ direction: order.direction ?? ORDER_DIRECTIONS.ASC
62
+ });
63
+
64
+ /**
65
+ * Aggregates grouped strings into a single value.
66
+ */
67
+ export const groupConcat = (
68
+ col: ColumnRef | ColumnNode,
69
+ options?: GroupConcatOptions
70
+ ): FunctionNode => ({
71
+ type: 'Function',
72
+ name: 'GROUP_CONCAT',
73
+ args: [columnOperand(col)],
74
+ orderBy: options?.orderBy?.map(toOrderByNode),
75
+ separator: options?.separator !== undefined ? valueToOperand(options.separator) : undefined
76
+ });
@@ -19,22 +19,23 @@ import {
19
19
  isOperandNode
20
20
  } from './expression-nodes.js';
21
21
 
22
+ export type LiteralValue = LiteralNode['value'];
23
+ export type ValueOperandInput = OperandNode | LiteralValue;
24
+
22
25
  /**
23
26
  * Converts a primitive or existing operand into an operand node
24
27
  * @param value - Value or operand to normalize
25
28
  * @returns OperandNode representing the value
26
29
  */
27
- export const valueToOperand = (value: unknown): OperandNode => {
28
- if (
29
- value === null ||
30
- value === undefined ||
31
- typeof value === 'string' ||
32
- typeof value === 'number' ||
33
- typeof value === 'boolean'
34
- ) {
35
- return { type: 'Literal', value: value === undefined ? null : value } as LiteralNode;
30
+ export const valueToOperand = (value: ValueOperandInput): OperandNode => {
31
+ if (isOperandNode(value)) {
32
+ return value;
36
33
  }
37
- return value as OperandNode;
34
+
35
+ return {
36
+ type: 'Literal',
37
+ value
38
+ } as LiteralNode;
38
39
  };
39
40
 
40
41
  const toNode = (col: ColumnRef | OperandNode): OperandNode => {
@@ -48,12 +49,18 @@ const toLiteralNode = (value: string | number | boolean | null): LiteralNode =>
48
49
  value
49
50
  });
50
51
 
51
- const toOperand = (val: OperandNode | ColumnRef | string | number | boolean | null): OperandNode => {
52
- if (val === null) return { type: 'Literal', value: null };
53
- if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
54
- return { type: 'Literal', value: val };
52
+ const isLiteralValue = (value: unknown): value is LiteralValue =>
53
+ value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
54
+
55
+ export const isValueOperandInput = (value: unknown): value is ValueOperandInput =>
56
+ isOperandNode(value) || isLiteralValue(value);
57
+
58
+ const toOperand = (val: OperandNode | ColumnRef | LiteralValue): OperandNode => {
59
+ if (isLiteralValue(val)) {
60
+ return valueToOperand(val);
55
61
  }
56
- return toNode(val as OperandNode | ColumnRef);
62
+
63
+ return toNode(val);
57
64
  };
58
65
 
59
66
  export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
@@ -37,6 +37,12 @@ export interface FunctionNode {
37
37
  args: OperandNode[];
38
38
  /** Optional alias for the function result */
39
39
  alias?: string;
40
+ /** Optional ORDER BY clause used by aggregations like GROUP_CONCAT */
41
+ orderBy?: OrderByNode[];
42
+ /** Optional separator argument used by GROUP_CONCAT-like functions */
43
+ separator?: OperandNode;
44
+ /** Optional DISTINCT modifier */
45
+ distinct?: boolean;
40
46
  }
41
47
 
42
48
  /**
@@ -1,14 +1,10 @@
1
1
  // Small helpers to build Postgres-specific function calls as AST FunctionNodes
2
- import { columnOperand, valueToOperand } from '../../../ast/expression-builders.js';
2
+ import { valueToOperand } from '../../../ast/expression-builders.js';
3
3
  import type { OperandNode, FunctionNode } from '../../../ast/expression.js';
4
4
 
5
5
  type OperandInput = OperandNode | string | number | boolean | null;
6
6
 
7
- const toOperand = (v: OperandInput) => {
8
- if (v === null) return valueToOperand(null);
9
- if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return valueToOperand(v);
10
- return v as OperandNode;
11
- };
7
+ const toOperand = (v: OperandInput): OperandNode => valueToOperand(v);
12
8
 
13
9
  const fn = (name: string, args: OperandInput[]): FunctionNode => ({
14
10
  type: 'Function',
@@ -476,12 +476,16 @@ export abstract class Dialect
476
476
  /**
477
477
  * Compiles a function operand, using the dialect's function strategy.
478
478
  */
479
- protected compileFunctionOperand(fnNode: FunctionNode, ctx: CompilerContext): string {
480
- const compiledArgs = fnNode.args.map(arg => this.compileOperand(arg, ctx));
481
- const renderer = this.functionStrategy.getRenderer(fnNode.name);
482
- if (renderer) {
483
- return renderer({ node: fnNode, compiledArgs });
484
- }
485
- return `${fnNode.name}(${compiledArgs.join(', ')})`;
486
- }
479
+ protected compileFunctionOperand(fnNode: FunctionNode, ctx: CompilerContext): string {
480
+ const compiledArgs = fnNode.args.map(arg => this.compileOperand(arg, ctx));
481
+ const renderer = this.functionStrategy.getRenderer(fnNode.name);
482
+ if (renderer) {
483
+ return renderer({
484
+ node: fnNode,
485
+ compiledArgs,
486
+ compileOperand: operand => this.compileOperand(operand, ctx)
487
+ });
488
+ }
489
+ return `${fnNode.name}(${compiledArgs.join(', ')})`;
490
+ }
487
491
  }
@@ -84,18 +84,27 @@ export class MssqlFunctionStrategy extends StandardFunctionStrategy {
84
84
  return `DATEPART(dw, ${compiledArgs[0]})`;
85
85
  });
86
86
 
87
- this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
88
- if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
89
- return `DATEPART(wk, ${compiledArgs[0]})`;
90
- });
91
-
92
- this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
93
- if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
94
- const [, date] = compiledArgs;
95
- const partArg = node.args[0] as LiteralNode;
96
- const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
97
- // SQL Server 2022+ has DATETRUNC
98
- return `DATETRUNC(${partClean}, ${date})`;
99
- });
100
- }
101
- }
87
+ this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
88
+ if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
89
+ return `DATEPART(wk, ${compiledArgs[0]})`;
90
+ });
91
+
92
+ this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
93
+ if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
94
+ const [, date] = compiledArgs;
95
+ const partArg = node.args[0] as LiteralNode;
96
+ const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
97
+ // SQL Server 2022+ has DATETRUNC
98
+ return `DATETRUNC(${partClean}, ${date})`;
99
+ });
100
+
101
+ this.add('GROUP_CONCAT', ctx => {
102
+ const arg = ctx.compiledArgs[0];
103
+ const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
104
+ const separator = ctx.compileOperand(separatorOperand);
105
+ const orderClause = this.buildOrderByExpression(ctx);
106
+ const withinGroup = orderClause ? ` WITHIN GROUP (${orderClause})` : '';
107
+ return `STRING_AGG(${arg}, ${separator})${withinGroup}`;
108
+ });
109
+ }
110
+ }