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.
- package/README.md +33 -37
- package/dist/decorators/index.cjs +152 -23
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +152 -23
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +322 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -4
- package/dist/index.d.ts +53 -4
- package/dist/index.js +316 -115
- package/dist/index.js.map +1 -1
- package/dist/{select-BKZrMRCQ.d.cts → select-BPCn6MOH.d.cts} +183 -64
- package/dist/{select-BKZrMRCQ.d.ts → select-BPCn6MOH.d.ts} +183 -64
- package/package.json +2 -1
- package/src/core/ast/aggregate-functions.ts +50 -4
- package/src/core/ast/expression-builders.ts +22 -15
- package/src/core/ast/expression-nodes.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +2 -6
- package/src/core/dialect/abstract.ts +12 -8
- package/src/core/dialect/mssql/functions.ts +24 -15
- package/src/core/dialect/postgres/functions.ts +33 -24
- package/src/core/dialect/sqlite/functions.ts +19 -12
- package/src/core/functions/datetime.ts +2 -1
- package/src/core/functions/numeric.ts +2 -1
- package/src/core/functions/standard-strategy.ts +52 -12
- package/src/core/functions/text.ts +2 -1
- package/src/core/functions/types.ts +8 -8
- package/src/index.ts +5 -4
- package/src/orm/domain-event-bus.ts +43 -25
- package/src/orm/entity-meta.ts +40 -0
- package/src/orm/execution-context.ts +6 -0
- package/src/orm/hydration-context.ts +6 -4
- package/src/orm/orm-session.ts +35 -24
- package/src/orm/orm.ts +10 -10
- package/src/orm/query-logger.ts +15 -0
- package/src/orm/runtime-types.ts +60 -2
- package/src/orm/transaction-runner.ts +7 -0
- package/src/orm/unit-of-work.ts +1 -0
- package/src/query-builder/insert-query-state.ts +13 -3
- package/src/query-builder/select-helpers.ts +50 -0
- package/src/query-builder/select.ts +122 -30
- package/src/query-builder/update-query-state.ts +31 -9
- 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
|
|
1731
|
-
|
|
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?:
|
|
1734
|
-
|
|
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
|
|
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<
|
|
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?:
|
|
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(
|
|
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,
|
|
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
|
|
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.
|
|
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:
|
|
28
|
-
if (
|
|
29
|
-
value
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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 {
|
|
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({
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
}
|