metal-orm 1.0.56 → 1.0.58
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 +41 -33
- package/dist/index.cjs +1461 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +541 -114
- package/dist/index.d.ts +541 -114
- package/dist/index.js +1424 -195
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/codegen/naming-strategy.ts +3 -1
- package/src/codegen/typescript.ts +20 -10
- package/src/core/ast/aggregate-functions.ts +14 -0
- package/src/core/ast/builders.ts +38 -20
- package/src/core/ast/expression-builders.ts +70 -2
- package/src/core/ast/expression-nodes.ts +305 -274
- package/src/core/ast/expression-visitor.ts +11 -1
- package/src/core/ast/expression.ts +4 -0
- package/src/core/ast/query.ts +3 -0
- package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
- package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
- package/src/core/ddl/introspect/functions/mssql.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +4 -0
- package/src/core/ddl/introspect/mysql.ts +4 -0
- package/src/core/ddl/introspect/sqlite.ts +4 -0
- package/src/core/dialect/abstract.ts +552 -531
- package/src/core/dialect/base/function-table-formatter.ts +9 -30
- package/src/core/dialect/base/sql-dialect.ts +24 -0
- package/src/core/dialect/mssql/functions.ts +40 -2
- package/src/core/dialect/mysql/functions.ts +16 -2
- package/src/core/dialect/postgres/functions.ts +66 -2
- package/src/core/dialect/postgres/index.ts +17 -4
- package/src/core/dialect/postgres/table-functions.ts +27 -0
- package/src/core/dialect/sqlite/functions.ts +34 -0
- package/src/core/dialect/sqlite/index.ts +17 -1
- package/src/core/driver/database-driver.ts +9 -1
- package/src/core/driver/mssql-driver.ts +3 -0
- package/src/core/driver/mysql-driver.ts +3 -0
- package/src/core/driver/postgres-driver.ts +3 -0
- package/src/core/driver/sqlite-driver.ts +3 -0
- package/src/core/execution/executors/mssql-executor.ts +5 -0
- package/src/core/execution/executors/mysql-executor.ts +5 -0
- package/src/core/execution/executors/postgres-executor.ts +5 -0
- package/src/core/execution/executors/sqlite-executor.ts +5 -0
- package/src/core/functions/array.ts +26 -0
- package/src/core/functions/control-flow.ts +69 -0
- package/src/core/functions/datetime.ts +50 -0
- package/src/core/functions/definitions/aggregate.ts +16 -0
- package/src/core/functions/definitions/control-flow.ts +24 -0
- package/src/core/functions/definitions/datetime.ts +36 -0
- package/src/core/functions/definitions/helpers.ts +29 -0
- package/src/core/functions/definitions/json.ts +49 -0
- package/src/core/functions/definitions/numeric.ts +55 -0
- package/src/core/functions/definitions/string.ts +43 -0
- package/src/core/functions/function-registry.ts +48 -0
- package/src/core/functions/group-concat-helpers.ts +57 -0
- package/src/core/functions/json.ts +38 -0
- package/src/core/functions/numeric.ts +14 -0
- package/src/core/functions/standard-strategy.ts +86 -115
- package/src/core/functions/standard-table-strategy.ts +13 -0
- package/src/core/functions/table-types.ts +15 -0
- package/src/core/functions/text.ts +57 -0
- package/src/core/sql/sql.ts +59 -38
- package/src/decorators/bootstrap.ts +41 -4
- package/src/index.ts +18 -11
- package/src/orm/entity-meta.ts +6 -3
- package/src/orm/entity.ts +81 -14
- package/src/orm/execute.ts +87 -20
- package/src/orm/hydration-context.ts +10 -0
- package/src/orm/identity-map.ts +19 -0
- package/src/orm/interceptor-pipeline.ts +4 -0
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +19 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +19 -2
- package/src/orm/relations/many-to-many.ts +59 -4
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-service.ts +399 -95
- package/src/query-builder/relation-types.ts +2 -2
- package/src/query-builder/select.ts +124 -106
- package/src/schema/table-guards.ts +6 -0
- package/src/schema/types.ts +109 -85
|
@@ -32,7 +32,7 @@ import { QueryAstService } from './query-ast-service.js';
|
|
|
32
32
|
import { ColumnSelector } from './column-selector.js';
|
|
33
33
|
import { RelationManager } from './relation-manager.js';
|
|
34
34
|
import { RelationIncludeOptions } from './relation-types.js';
|
|
35
|
-
import type
|
|
35
|
+
import { RelationKinds, type RelationDef } from '../schema/relation.js';
|
|
36
36
|
import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
|
|
37
37
|
import { EntityInstance, RelationMap, RelationTargetTable } from '../schema/types.js';
|
|
38
38
|
import { OrmSession } from '../orm/orm-session.ts';
|
|
@@ -76,6 +76,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
76
76
|
private readonly columnSelector: ColumnSelector;
|
|
77
77
|
private readonly relationManager: RelationManager;
|
|
78
78
|
private readonly lazyRelations: Set<string>;
|
|
79
|
+
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* Creates a new SelectQueryBuilder instance
|
|
@@ -89,7 +90,8 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
89
90
|
state?: SelectQueryState,
|
|
90
91
|
hydration?: HydrationManager,
|
|
91
92
|
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
92
|
-
lazyRelations?: Set<string
|
|
93
|
+
lazyRelations?: Set<string>,
|
|
94
|
+
lazyRelationOptions?: Map<string, RelationIncludeOptions>
|
|
93
95
|
) {
|
|
94
96
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
95
97
|
this.env = { table, deps };
|
|
@@ -100,6 +102,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
100
102
|
hydration: initialHydration
|
|
101
103
|
};
|
|
102
104
|
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
105
|
+
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
103
106
|
this.columnSelector = deps.createColumnSelector(this.env);
|
|
104
107
|
this.relationManager = deps.createRelationManager(this.env);
|
|
105
108
|
}
|
|
@@ -112,9 +115,17 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
112
115
|
*/
|
|
113
116
|
private clone(
|
|
114
117
|
context: SelectQueryBuilderContext = this.context,
|
|
115
|
-
lazyRelations = new Set(this.lazyRelations)
|
|
118
|
+
lazyRelations = new Set(this.lazyRelations),
|
|
119
|
+
lazyRelationOptions = new Map(this.lazyRelationOptions)
|
|
116
120
|
): SelectQueryBuilder<T, TTable> {
|
|
117
|
-
return new SelectQueryBuilder(
|
|
121
|
+
return new SelectQueryBuilder(
|
|
122
|
+
this.env.table as TTable,
|
|
123
|
+
context.state,
|
|
124
|
+
context.hydration,
|
|
125
|
+
this.env.deps,
|
|
126
|
+
lazyRelations,
|
|
127
|
+
lazyRelationOptions
|
|
128
|
+
);
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
|
@@ -445,42 +456,41 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
445
456
|
/**
|
|
446
457
|
* Includes a relation lazily in the query results
|
|
447
458
|
* @param relationName - Name of the relation to include lazily
|
|
459
|
+
* @param options - Optional include options for lazy loading
|
|
448
460
|
* @returns New query builder instance with lazy relation inclusion
|
|
449
461
|
*/
|
|
450
|
-
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return this.include(relationName as string, { columns: cols as string[] });
|
|
480
|
-
}
|
|
462
|
+
includeLazy<K extends keyof RelationMap<TTable>>(
|
|
463
|
+
relationName: K,
|
|
464
|
+
options?: RelationIncludeOptions
|
|
465
|
+
): SelectQueryBuilder<T, TTable> {
|
|
466
|
+
let nextContext = this.context;
|
|
467
|
+
const relation = this.env.table.relations[relationName as string];
|
|
468
|
+
if (relation?.type === RelationKinds.BelongsTo) {
|
|
469
|
+
const foreignKey = relation.foreignKey;
|
|
470
|
+
const fkColumn = this.env.table.columns[foreignKey];
|
|
471
|
+
if (fkColumn) {
|
|
472
|
+
const hasAlias = nextContext.state.ast.columns.some(col => {
|
|
473
|
+
const node = col as { alias?: string; name?: string };
|
|
474
|
+
return (node.alias ?? node.name) === foreignKey;
|
|
475
|
+
});
|
|
476
|
+
if (!hasAlias) {
|
|
477
|
+
nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
482
|
+
nextLazy.add(relationName as string);
|
|
483
|
+
const nextOptions = new Map(this.lazyRelationOptions);
|
|
484
|
+
if (options) {
|
|
485
|
+
nextOptions.set(relationName as string, options);
|
|
486
|
+
} else {
|
|
487
|
+
nextOptions.delete(relationName as string);
|
|
488
|
+
}
|
|
489
|
+
return this.clone(nextContext, nextLazy, nextOptions);
|
|
490
|
+
}
|
|
481
491
|
|
|
482
492
|
/**
|
|
483
|
-
* Convenience alias for
|
|
493
|
+
* Convenience alias for including only specific columns from a relation.
|
|
484
494
|
*/
|
|
485
495
|
includePick<
|
|
486
496
|
K extends keyof TTable['relations'] & string,
|
|
@@ -488,7 +498,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
488
498
|
TTarget extends TableDef = RelationTargetTable<TRel>,
|
|
489
499
|
C extends keyof TTarget['columns'] & string = keyof TTarget['columns'] & string
|
|
490
500
|
>(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
|
|
491
|
-
return this.
|
|
501
|
+
return this.include(relationName, { columns: cols as readonly string[] });
|
|
492
502
|
}
|
|
493
503
|
|
|
494
504
|
|
|
@@ -505,7 +515,7 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
505
515
|
if (entry.type === 'root') {
|
|
506
516
|
currBuilder = currBuilder.select(...entry.columns);
|
|
507
517
|
} else {
|
|
508
|
-
currBuilder = currBuilder.
|
|
518
|
+
currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns as string[] });
|
|
509
519
|
}
|
|
510
520
|
}
|
|
511
521
|
|
|
@@ -520,6 +530,14 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
520
530
|
return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
|
|
521
531
|
}
|
|
522
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Gets lazy relation include options
|
|
535
|
+
* @returns Map of relation names to include options
|
|
536
|
+
*/
|
|
537
|
+
getLazyRelationOptions(): Map<string, RelationIncludeOptions> {
|
|
538
|
+
return new Map(this.lazyRelationOptions);
|
|
539
|
+
}
|
|
540
|
+
|
|
523
541
|
/**
|
|
524
542
|
* Gets the table definition for this query builder
|
|
525
543
|
* @returns Table definition
|
|
@@ -533,74 +551,74 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
|
|
|
533
551
|
* @param ctx - ORM session context
|
|
534
552
|
* @returns Promise of entity instances
|
|
535
553
|
*/
|
|
536
|
-
async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
|
|
537
|
-
return executeHydrated(ctx, this);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
private withAst(ast: SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
541
|
-
const nextState = new SelectQueryState(this.env.table as TTable, ast);
|
|
542
|
-
const nextContext: SelectQueryBuilderContext = {
|
|
543
|
-
...this.context,
|
|
544
|
-
state: nextState
|
|
545
|
-
};
|
|
546
|
-
return this.clone(nextContext);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
async count(session: OrmSession): Promise<number> {
|
|
550
|
-
const unpagedAst: SelectQueryNode = {
|
|
551
|
-
...this.context.state.ast,
|
|
552
|
-
orderBy: undefined,
|
|
553
|
-
limit: undefined,
|
|
554
|
-
offset: undefined
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
const subAst = this.withAst(unpagedAst).getAST();
|
|
558
|
-
|
|
559
|
-
const countQuery: SelectQueryNode = {
|
|
560
|
-
type: 'SelectQuery',
|
|
561
|
-
from: derivedTable(subAst, '__metal_count'),
|
|
562
|
-
columns: [{ type: 'Function', name: 'COUNT', args: [], alias: 'total' } as FunctionNode],
|
|
563
|
-
joins: []
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
const execCtx = session.getExecutionContext();
|
|
567
|
-
const compiled = execCtx.dialect.compileSelect(countQuery);
|
|
568
|
-
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
569
|
-
const value = results[0]?.values?.[0]?.[0];
|
|
570
|
-
|
|
571
|
-
if (typeof value === 'number') return value;
|
|
572
|
-
if (typeof value === 'bigint') return Number(value);
|
|
573
|
-
if (typeof value === 'string') return Number(value);
|
|
574
|
-
return value === null || value === undefined ? 0 : Number(value);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
async executePaged(
|
|
578
|
-
session: OrmSession,
|
|
579
|
-
options: { page: number; pageSize: number }
|
|
580
|
-
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
581
|
-
const { page, pageSize } = options;
|
|
582
|
-
if (!Number.isInteger(page) || page < 1) {
|
|
583
|
-
throw new Error('executePaged: page must be an integer >= 1');
|
|
584
|
-
}
|
|
585
|
-
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
586
|
-
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const offset = (page - 1) * pageSize;
|
|
590
|
-
const [items, totalItems] = await Promise.all([
|
|
591
|
-
this.limit(pageSize).offset(offset).execute(session),
|
|
592
|
-
this.count(session)
|
|
593
|
-
]);
|
|
594
|
-
|
|
595
|
-
return { items, totalItems };
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Executes the query with provided execution and hydration contexts
|
|
600
|
-
* @param execCtx - Execution context
|
|
601
|
-
* @param hydCtx - Hydration context
|
|
602
|
-
* @returns Promise of entity instances
|
|
603
|
-
*/
|
|
554
|
+
async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
|
|
555
|
+
return executeHydrated(ctx, this);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private withAst(ast: SelectQueryNode): SelectQueryBuilder<T, TTable> {
|
|
559
|
+
const nextState = new SelectQueryState(this.env.table as TTable, ast);
|
|
560
|
+
const nextContext: SelectQueryBuilderContext = {
|
|
561
|
+
...this.context,
|
|
562
|
+
state: nextState
|
|
563
|
+
};
|
|
564
|
+
return this.clone(nextContext);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async count(session: OrmSession): Promise<number> {
|
|
568
|
+
const unpagedAst: SelectQueryNode = {
|
|
569
|
+
...this.context.state.ast,
|
|
570
|
+
orderBy: undefined,
|
|
571
|
+
limit: undefined,
|
|
572
|
+
offset: undefined
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const subAst = this.withAst(unpagedAst).getAST();
|
|
576
|
+
|
|
577
|
+
const countQuery: SelectQueryNode = {
|
|
578
|
+
type: 'SelectQuery',
|
|
579
|
+
from: derivedTable(subAst, '__metal_count'),
|
|
580
|
+
columns: [{ type: 'Function', name: 'COUNT', args: [], alias: 'total' } as FunctionNode],
|
|
581
|
+
joins: []
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const execCtx = session.getExecutionContext();
|
|
585
|
+
const compiled = execCtx.dialect.compileSelect(countQuery);
|
|
586
|
+
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
587
|
+
const value = results[0]?.values?.[0]?.[0];
|
|
588
|
+
|
|
589
|
+
if (typeof value === 'number') return value;
|
|
590
|
+
if (typeof value === 'bigint') return Number(value);
|
|
591
|
+
if (typeof value === 'string') return Number(value);
|
|
592
|
+
return value === null || value === undefined ? 0 : Number(value);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async executePaged(
|
|
596
|
+
session: OrmSession,
|
|
597
|
+
options: { page: number; pageSize: number }
|
|
598
|
+
): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
|
|
599
|
+
const { page, pageSize } = options;
|
|
600
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
601
|
+
throw new Error('executePaged: page must be an integer >= 1');
|
|
602
|
+
}
|
|
603
|
+
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
604
|
+
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const offset = (page - 1) * pageSize;
|
|
608
|
+
const [items, totalItems] = await Promise.all([
|
|
609
|
+
this.limit(pageSize).offset(offset).execute(session),
|
|
610
|
+
this.count(session)
|
|
611
|
+
]);
|
|
612
|
+
|
|
613
|
+
return { items, totalItems };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Executes the query with provided execution and hydration contexts
|
|
618
|
+
* @param execCtx - Execution context
|
|
619
|
+
* @param hydCtx - Hydration context
|
|
620
|
+
* @returns Promise of entity instances
|
|
621
|
+
*/
|
|
604
622
|
async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
|
|
605
623
|
return executeHydratedWithContexts(execCtx, hydCtx, this);
|
|
606
624
|
}
|
|
@@ -9,6 +9,12 @@ const isRelationsRecord = (relations: unknown): relations is Record<string, unkn
|
|
|
9
9
|
return typeof relations === 'object' && relations !== null;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Type guard that checks if a value is a TableDef.
|
|
14
|
+
*
|
|
15
|
+
* @param value The value to check.
|
|
16
|
+
* @returns True if the value follows the TableDef structure.
|
|
17
|
+
*/
|
|
12
18
|
export const isTableDef = (value: unknown): value is TableDef => {
|
|
13
19
|
if (typeof value !== 'object' || value === null) {
|
|
14
20
|
return false;
|
package/src/schema/types.ts
CHANGED
|
@@ -1,104 +1,128 @@
|
|
|
1
|
-
import { ColumnDef } from './column-types.js';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
TRel extends
|
|
17
|
-
TRel extends
|
|
18
|
-
TRel extends
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
NormalizedColumnType<T> extends '
|
|
30
|
-
NormalizedColumnType<T> extends '
|
|
31
|
-
NormalizedColumnType<T> extends '
|
|
32
|
-
NormalizedColumnType<T> extends '
|
|
33
|
-
NormalizedColumnType<T> extends '
|
|
34
|
-
NormalizedColumnType<T> extends '
|
|
35
|
-
string
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
import type { ColumnDef } from './column-types.js';
|
|
2
|
+
export type { ColumnDef };
|
|
3
|
+
import { TableDef } from './table.js';
|
|
4
|
+
import {
|
|
5
|
+
RelationDef,
|
|
6
|
+
HasManyRelation,
|
|
7
|
+
HasOneRelation,
|
|
8
|
+
BelongsToRelation,
|
|
9
|
+
BelongsToManyRelation
|
|
10
|
+
} from './relation.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a relation definition to its target table type.
|
|
14
|
+
*/
|
|
15
|
+
export type RelationTargetTable<TRel extends RelationDef> =
|
|
16
|
+
TRel extends HasManyRelation<infer TTarget> ? TTarget :
|
|
17
|
+
TRel extends HasOneRelation<infer TTarget> ? TTarget :
|
|
18
|
+
TRel extends BelongsToRelation<infer TTarget> ? TTarget :
|
|
19
|
+
TRel extends BelongsToManyRelation<infer TTarget> ? TTarget :
|
|
20
|
+
never;
|
|
21
|
+
|
|
22
|
+
type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a ColumnDef to its TypeScript type representation
|
|
26
|
+
*/
|
|
27
|
+
export type ColumnToTs<T extends ColumnDef> =
|
|
28
|
+
[unknown] extends [T['tsType']]
|
|
29
|
+
? NormalizedColumnType<T> extends 'int' | 'integer' ? number :
|
|
30
|
+
NormalizedColumnType<T> extends 'bigint' ? number | bigint :
|
|
31
|
+
NormalizedColumnType<T> extends 'decimal' | 'float' | 'double' ? number :
|
|
32
|
+
NormalizedColumnType<T> extends 'boolean' ? boolean :
|
|
33
|
+
NormalizedColumnType<T> extends 'json' ? unknown :
|
|
34
|
+
NormalizedColumnType<T> extends 'blob' | 'binary' | 'varbinary' | 'bytea' ? Buffer :
|
|
35
|
+
NormalizedColumnType<T> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz' ? string :
|
|
36
|
+
string
|
|
37
|
+
: Exclude<T['tsType'], undefined>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Infers a row shape from a table definition
|
|
41
|
+
*/
|
|
42
|
+
export type InferRow<TTable extends TableDef> = {
|
|
43
|
+
[K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
|
|
44
|
+
};
|
|
45
|
+
|
|
45
46
|
type RelationResult<T extends RelationDef> =
|
|
46
47
|
T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
|
|
47
48
|
T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
48
49
|
T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
|
|
49
|
-
T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: unknown })[] :
|
|
50
|
+
T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: Record<string, unknown> })[] :
|
|
50
51
|
never;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Maps relation names to the expected row results
|
|
54
|
-
*/
|
|
55
|
-
export type RelationMap<TTable extends TableDef> = {
|
|
56
|
-
[K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
|
|
57
|
-
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maps relation names to the expected row results
|
|
55
|
+
*/
|
|
56
|
+
export type RelationMap<TTable extends TableDef> = {
|
|
57
|
+
[K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type RelationWrapper<TRel extends RelationDef> =
|
|
61
|
+
TRel extends HasManyRelation<infer TTarget>
|
|
62
|
+
? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
|
|
63
|
+
: TRel extends HasOneRelation<infer TTarget>
|
|
64
|
+
? HasOneReference<EntityInstance<TTarget>>
|
|
65
|
+
: TRel extends BelongsToManyRelation<infer TTarget>
|
|
66
|
+
? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
|
|
67
|
+
& ReadonlyArray<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
|
|
68
|
+
: TRel extends BelongsToRelation<infer TTarget>
|
|
69
|
+
? BelongsToReference<EntityInstance<TTarget>>
|
|
70
|
+
: never;
|
|
58
71
|
|
|
59
72
|
export interface HasManyCollection<TChild> {
|
|
73
|
+
length: number;
|
|
74
|
+
[Symbol.iterator](): Iterator<TChild>;
|
|
60
75
|
load(): Promise<TChild[]>;
|
|
61
76
|
getItems(): TChild[];
|
|
62
77
|
add(data: Partial<TChild>): TChild;
|
|
63
78
|
attach(entity: TChild): void;
|
|
64
79
|
remove(entity: TChild): void;
|
|
65
|
-
clear(): void;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface
|
|
69
|
-
load(): Promise<TParent | null>;
|
|
70
|
-
get(): TParent | null;
|
|
71
|
-
set(data: Partial<TParent> | TParent | null): TParent | null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
clear(): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BelongsToReferenceApi<TParent extends object = object> {
|
|
84
|
+
load(): Promise<TParent | null>;
|
|
85
|
+
get(): TParent | null;
|
|
86
|
+
set(data: Partial<TParent> | TParent | null): TParent | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type BelongsToReference<TParent extends object = object> = BelongsToReferenceApi<TParent> & Partial<TParent>;
|
|
90
|
+
|
|
91
|
+
export interface HasOneReferenceApi<TChild extends object = object> {
|
|
92
|
+
load(): Promise<TChild | null>;
|
|
93
|
+
get(): TChild | null;
|
|
94
|
+
set(data: Partial<TChild> | TChild | null): TChild | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
|
|
98
|
+
|
|
80
99
|
export interface ManyToManyCollection<TTarget> {
|
|
100
|
+
length: number;
|
|
101
|
+
[Symbol.iterator](): Iterator<TTarget>;
|
|
81
102
|
load(): Promise<TTarget[]>;
|
|
82
103
|
getItems(): TTarget[];
|
|
83
104
|
attach(target: TTarget | number | string): void;
|
|
84
105
|
detach(target: TTarget | number | string): void;
|
|
85
106
|
syncByIds(ids: (number | string)[]): Promise<void>;
|
|
86
|
-
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type EntityInstance<
|
|
110
|
+
TTable extends TableDef,
|
|
111
|
+
TRow = InferRow<TTable>
|
|
112
|
+
> = TRow & {
|
|
113
|
+
[K in keyof RelationMap<TTable>]: RelationWrapper<TTable['relations'][K]>;
|
|
114
|
+
} & {
|
|
115
|
+
$load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
|
|
119
|
+
|
|
120
|
+
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
87
121
|
|
|
88
|
-
export type
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
: TTable['relations'][K] extends HasOneRelation<infer TTarget>
|
|
96
|
-
? HasOneReference<EntityInstance<TTarget>>
|
|
97
|
-
: TTable['relations'][K] extends BelongsToManyRelation<infer TTarget>
|
|
98
|
-
? ManyToManyCollection<EntityInstance<TTarget>>
|
|
99
|
-
: TTable['relations'][K] extends BelongsToRelation<infer TTarget>
|
|
100
|
-
? BelongsToReference<EntityInstance<TTarget>>
|
|
101
|
-
: never;
|
|
102
|
-
} & {
|
|
103
|
-
$load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
|
|
104
|
-
};
|
|
122
|
+
export type SelectableKeys<T> = {
|
|
123
|
+
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
124
|
+
? never
|
|
125
|
+
: NonNullable<T[K]> extends Primitive
|
|
126
|
+
? K
|
|
127
|
+
: never
|
|
128
|
+
}[keyof T];
|