metal-orm 1.0.118 → 1.1.0
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 +26 -14
- package/dist/index.cjs +728 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +419 -7
- package/dist/index.d.ts +419 -7
- package/dist/index.js +720 -17
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/cache/adapters/index.ts +2 -0
- package/src/cache/adapters/keyv-cache-adapter.ts +81 -0
- package/src/cache/adapters/memory-cache-adapter.ts +127 -0
- package/src/cache/cache-interfaces.ts +70 -0
- package/src/cache/duration-utils.ts +82 -0
- package/src/cache/index.ts +28 -0
- package/src/cache/query-cache-manager.ts +130 -0
- package/src/cache/strategies/cache-strategy.ts +29 -0
- package/src/cache/strategies/default-cache-strategy.ts +96 -0
- package/src/cache/strategies/index.ts +2 -0
- package/src/cache/tag-index.ts +128 -0
- package/src/core/dialect/abstract.ts +565 -565
- package/src/core/dialect/mssql/index.ts +68 -3
- package/src/core/dialect/postgres/index.ts +1 -1
- package/src/core/dialect/sqlite/index.ts +1 -1
- package/src/core/execution/db-executor.ts +107 -103
- package/src/core/execution/executors/mysql-executor.ts +9 -2
- package/src/index.ts +3 -0
- package/src/orm/orm-session.ts +616 -563
- package/src/orm/orm.ts +108 -71
- package/src/orm/unit-of-work.ts +22 -4
- package/src/query-builder/select/cache-facet.ts +67 -0
- package/src/query-builder/select.ts +125 -57
package/src/orm/orm.ts
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
import type { DomainEvent, OrmDomainEvent } from './runtime-types.js';
|
|
2
|
-
import type { Dialect } from '../core/dialect/abstract.js';
|
|
3
|
-
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
4
|
-
import type { NamingStrategy } from '../codegen/naming-strategy.js';
|
|
5
|
-
import { InterceptorPipeline } from './interceptor-pipeline.js';
|
|
6
|
-
import { DefaultNamingStrategy } from '../codegen/naming-strategy.js';
|
|
7
|
-
import { OrmSession } from './orm-session.js';
|
|
1
|
+
import type { DomainEvent, OrmDomainEvent } from './runtime-types.js';
|
|
2
|
+
import type { Dialect } from '../core/dialect/abstract.js';
|
|
3
|
+
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
4
|
+
import type { NamingStrategy } from '../codegen/naming-strategy.js';
|
|
5
|
+
import { InterceptorPipeline } from './interceptor-pipeline.js';
|
|
6
|
+
import { DefaultNamingStrategy } from '../codegen/naming-strategy.js';
|
|
7
|
+
import { OrmSession } from './orm-session.js';
|
|
8
|
+
import type { QueryCacheManager } from '../cache/query-cache-manager.js';
|
|
9
|
+
import type { Duration, CacheProvider, CacheStrategy } from '../cache/index.js';
|
|
10
|
+
import { QueryCacheManager as QueryCacheManagerImpl } from '../cache/query-cache-manager.js';
|
|
11
|
+
import { DefaultCacheStrategy } from '../cache/strategies/default-cache-strategy.js';
|
|
8
12
|
|
|
9
|
-
/**
|
|
10
|
-
* Options for creating an ORM instance.
|
|
11
|
-
*/
|
|
12
|
-
export interface OrmOptions {
|
|
13
|
-
/** The database dialect */
|
|
14
|
-
dialect: Dialect;
|
|
15
|
-
/** The database executor factory */
|
|
16
|
-
executorFactory: DbExecutorFactory;
|
|
17
|
-
/** Optional interceptors pipeline */
|
|
18
|
-
interceptors?: InterceptorPipeline;
|
|
19
|
-
/** Optional naming strategy */
|
|
20
|
-
namingStrategy?: NamingStrategy;
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Options for creating an ORM instance.
|
|
15
|
+
*/
|
|
16
|
+
export interface OrmOptions {
|
|
17
|
+
/** The database dialect */
|
|
18
|
+
dialect: Dialect;
|
|
19
|
+
/** The database executor factory */
|
|
20
|
+
executorFactory: DbExecutorFactory;
|
|
21
|
+
/** Optional interceptors pipeline */
|
|
22
|
+
interceptors?: InterceptorPipeline;
|
|
23
|
+
/** Optional naming strategy */
|
|
24
|
+
namingStrategy?: NamingStrategy;
|
|
25
|
+
/** Optional cache configuration */
|
|
26
|
+
cache?: OrmCacheOptions;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Cache configuration options for ORM
|
|
31
|
+
*/
|
|
32
|
+
export interface OrmCacheOptions {
|
|
33
|
+
/** Cache provider (e.g., MemoryCacheAdapter, KeyvCacheAdapter) */
|
|
34
|
+
provider: CacheProvider;
|
|
35
|
+
/** Optional cache strategy (defaults to DefaultCacheStrategy) */
|
|
36
|
+
strategy?: CacheStrategy;
|
|
37
|
+
/** Default TTL for cached queries (e.g., '1h', '30m') */
|
|
38
|
+
defaultTtl?: Duration;
|
|
39
|
+
}
|
|
23
40
|
|
|
24
41
|
/**
|
|
25
42
|
* Database executor factory interface.
|
|
@@ -43,58 +60,78 @@ export interface DbExecutorFactory {
|
|
|
43
60
|
dispose(): Promise<void>;
|
|
44
61
|
}
|
|
45
62
|
|
|
46
|
-
/**
|
|
47
|
-
* ORM (Object-Relational Mapping) main class.
|
|
48
|
-
* @template E - The domain event type
|
|
49
|
-
*/
|
|
50
|
-
export class Orm<E extends DomainEvent = OrmDomainEvent> {
|
|
51
|
-
/** The database dialect */
|
|
52
|
-
readonly dialect: Dialect;
|
|
53
|
-
/** The interceptors pipeline */
|
|
54
|
-
readonly interceptors: InterceptorPipeline;
|
|
55
|
-
/** The naming strategy */
|
|
56
|
-
readonly namingStrategy: NamingStrategy;
|
|
57
|
-
|
|
63
|
+
/**
|
|
64
|
+
* ORM (Object-Relational Mapping) main class.
|
|
65
|
+
* @template E - The domain event type
|
|
66
|
+
*/
|
|
67
|
+
export class Orm<E extends DomainEvent = OrmDomainEvent> {
|
|
68
|
+
/** The database dialect */
|
|
69
|
+
readonly dialect: Dialect;
|
|
70
|
+
/** The interceptors pipeline */
|
|
71
|
+
readonly interceptors: InterceptorPipeline;
|
|
72
|
+
/** The naming strategy */
|
|
73
|
+
readonly namingStrategy: NamingStrategy;
|
|
74
|
+
/** The cache manager (if configured) */
|
|
75
|
+
readonly cacheManager?: QueryCacheManager;
|
|
76
|
+
private readonly executorFactory: DbExecutorFactory;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a new ORM instance.
|
|
80
|
+
* @param opts - ORM options
|
|
81
|
+
*/
|
|
82
|
+
constructor(opts: OrmOptions) {
|
|
83
|
+
this.dialect = opts.dialect;
|
|
84
|
+
this.interceptors = opts.interceptors ?? new InterceptorPipeline();
|
|
85
|
+
this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
|
|
86
|
+
this.executorFactory = opts.executorFactory;
|
|
87
|
+
|
|
88
|
+
// Initialize cache manager if cache options provided
|
|
89
|
+
if (opts.cache) {
|
|
90
|
+
this.cacheManager = new QueryCacheManagerImpl(
|
|
91
|
+
opts.cache.provider,
|
|
92
|
+
opts.cache.strategy ?? new DefaultCacheStrategy(),
|
|
93
|
+
opts.cache.defaultTtl ?? '1h'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
58
97
|
|
|
59
|
-
/**
|
|
60
|
-
* Creates a new ORM
|
|
61
|
-
* @param
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Creates a new ORM session.
|
|
100
|
+
* @param options - Optional session options (e.g., tenantId for multi-tenancy)
|
|
101
|
+
* @returns The ORM session
|
|
102
|
+
*/
|
|
103
|
+
createSession(options?: { tenantId?: string | number }): OrmSession<E> {
|
|
104
|
+
// No implicit transaction binding; callers should use Orm.transaction() for transactional work.
|
|
105
|
+
const executor = this.executorFactory.createExecutor();
|
|
106
|
+
return new OrmSession<E>({
|
|
107
|
+
orm: this,
|
|
108
|
+
executor,
|
|
109
|
+
cacheManager: this.cacheManager,
|
|
110
|
+
tenantId: options?.tenantId
|
|
111
|
+
});
|
|
112
|
+
}
|
|
69
113
|
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
* @
|
|
73
|
-
* @
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// A real transaction scope: begin before running user code, commit/rollback after.
|
|
93
|
-
return await session.transaction(() => fn(session));
|
|
94
|
-
} finally {
|
|
95
|
-
await session.dispose();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
114
|
+
/**
|
|
115
|
+
* Executes a function within a transaction.
|
|
116
|
+
* @template T - The return type
|
|
117
|
+
* @param fn - The function to execute
|
|
118
|
+
* @returns The result of the function
|
|
119
|
+
* @throws If the transaction fails
|
|
120
|
+
*/
|
|
121
|
+
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
122
|
+
const executor = this.executorFactory.createTransactionalExecutor();
|
|
123
|
+
const session = new OrmSession<E>({
|
|
124
|
+
orm: this,
|
|
125
|
+
executor,
|
|
126
|
+
cacheManager: this.cacheManager
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
// A real transaction scope: begin before running user code, commit/rollback after.
|
|
130
|
+
return await session.transaction(() => fn(session));
|
|
131
|
+
} finally {
|
|
132
|
+
await session.dispose();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
98
135
|
|
|
99
136
|
/**
|
|
100
137
|
* Shuts down the ORM and releases underlying resources (pools, timers).
|
package/src/orm/unit-of-work.ts
CHANGED
|
@@ -198,12 +198,13 @@ export class UnitOfWork {
|
|
|
198
198
|
|
|
199
199
|
const payload = this.extractColumns(tracked.table, tracked.entity as Record<string, unknown>);
|
|
200
200
|
let builder = new InsertQueryBuilder(tracked.table).values(payload as Record<string, ValueOperandInput>);
|
|
201
|
-
if (this.dialect.
|
|
201
|
+
if (this.dialect.supportsDmlReturningClause()) {
|
|
202
202
|
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
203
203
|
}
|
|
204
204
|
const compiled = builder.compile(this.dialect);
|
|
205
205
|
const results = await this.executeCompiled(compiled);
|
|
206
206
|
this.applyReturningResults(tracked, results);
|
|
207
|
+
this.applyInsertedIdIfAbsent(tracked, results);
|
|
207
208
|
|
|
208
209
|
tracked.status = EntityStatus.Managed;
|
|
209
210
|
tracked.original = this.createSnapshot(tracked.table, tracked.entity as Record<string, unknown>);
|
|
@@ -234,7 +235,7 @@ export class UnitOfWork {
|
|
|
234
235
|
.set(changes)
|
|
235
236
|
.where(eq(pkColumn, tracked.pk));
|
|
236
237
|
|
|
237
|
-
if (this.dialect.
|
|
238
|
+
if (this.dialect.supportsDmlReturningClause()) {
|
|
238
239
|
builder = builder.returning(...this.getReturningColumns(tracked.table));
|
|
239
240
|
}
|
|
240
241
|
|
|
@@ -345,9 +346,8 @@ export class UnitOfWork {
|
|
|
345
346
|
* @param results - Query results
|
|
346
347
|
*/
|
|
347
348
|
private applyReturningResults(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
348
|
-
if (!this.dialect.supportsReturning()) return;
|
|
349
349
|
const first = results[0];
|
|
350
|
-
if (!first || first.values.length === 0) return;
|
|
350
|
+
if (!first || first.columns.length === 0 || first.values.length === 0) return;
|
|
351
351
|
|
|
352
352
|
const row = first.values[0];
|
|
353
353
|
for (let i = 0; i < first.columns.length; i++) {
|
|
@@ -357,6 +357,24 @@ export class UnitOfWork {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Applies the driver-provided insertId when no RETURNING clause was used.
|
|
362
|
+
* Only sets the PK if it is currently absent on the entity.
|
|
363
|
+
* @param tracked - The tracked entity
|
|
364
|
+
* @param results - Query results (may contain meta.insertId)
|
|
365
|
+
*/
|
|
366
|
+
private applyInsertedIdIfAbsent(tracked: TrackedEntity, results: QueryResult[]): void {
|
|
367
|
+
const pkName = findPrimaryKey(tracked.table);
|
|
368
|
+
const current = (tracked.entity as Record<string, unknown>)[pkName];
|
|
369
|
+
if (current != null) return;
|
|
370
|
+
|
|
371
|
+
const first = results[0];
|
|
372
|
+
const insertId = first?.meta?.insertId;
|
|
373
|
+
if (insertId == null) return;
|
|
374
|
+
|
|
375
|
+
(tracked.entity as Record<string, unknown>)[pkName] = insertId;
|
|
376
|
+
}
|
|
377
|
+
|
|
360
378
|
/**
|
|
361
379
|
* Normalizes a column name by removing quotes and table prefixes.
|
|
362
380
|
* @param column - The column name to normalize
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { CacheOptions, CacheState, Duration } from '../../cache/cache-interfaces.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Facet para gerenciar estado de cache no SelectQueryBuilder
|
|
5
|
+
* Segue o padrão de facets existente no projeto
|
|
6
|
+
*/
|
|
7
|
+
export interface CacheFacetContext {
|
|
8
|
+
state: CacheState;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class CacheFacet {
|
|
12
|
+
/**
|
|
13
|
+
* Configura opções de cache no contexto
|
|
14
|
+
*/
|
|
15
|
+
cache(
|
|
16
|
+
context: CacheFacetContext,
|
|
17
|
+
options: CacheOptions
|
|
18
|
+
): CacheFacetContext {
|
|
19
|
+
return {
|
|
20
|
+
state: {
|
|
21
|
+
...context.state,
|
|
22
|
+
options,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Obtém as opções de cache do contexto
|
|
29
|
+
*/
|
|
30
|
+
getOptions(context: CacheFacetContext): CacheOptions | undefined {
|
|
31
|
+
return context.state.options;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Verifica se há configuração de cache
|
|
36
|
+
*/
|
|
37
|
+
hasCache(context: CacheFacetContext): boolean {
|
|
38
|
+
return context.state.options !== undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cria opções de cache a partir de parâmetros variados
|
|
43
|
+
* API flexível para diferentes casos de uso
|
|
44
|
+
*/
|
|
45
|
+
static createOptions(
|
|
46
|
+
key: string,
|
|
47
|
+
ttl: Duration,
|
|
48
|
+
tagsOrConfig?: string[] | { tags?: string[]; autoInvalidate?: boolean }
|
|
49
|
+
): CacheOptions {
|
|
50
|
+
let tags: string[] | undefined;
|
|
51
|
+
let autoInvalidate: boolean | undefined;
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(tagsOrConfig)) {
|
|
54
|
+
tags = tagsOrConfig;
|
|
55
|
+
} else if (tagsOrConfig) {
|
|
56
|
+
tags = tagsOrConfig.tags;
|
|
57
|
+
autoInvalidate = tagsOrConfig.autoInvalidate;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
key,
|
|
62
|
+
ttl,
|
|
63
|
+
tags,
|
|
64
|
+
autoInvalidate,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -67,9 +67,11 @@ import { SelectProjectionFacet } from './select/projection-facet.js';
|
|
|
67
67
|
import { SelectPredicateFacet } from './select/predicate-facet.js';
|
|
68
68
|
import { SelectCTEFacet } from './select/cte-facet.js';
|
|
69
69
|
import { SelectSetOpFacet } from './select/setop-facet.js';
|
|
70
|
-
import { SelectRelationFacet } from './select/relation-facet.js';
|
|
71
|
-
|
|
72
|
-
type
|
|
70
|
+
import { SelectRelationFacet } from './select/relation-facet.js';
|
|
71
|
+
import { CacheFacet, CacheFacetContext } from './select/cache-facet.js';
|
|
72
|
+
import type { Duration } from '../cache/cache-interfaces.js';
|
|
73
|
+
|
|
74
|
+
type ColumnSelectionValue =
|
|
73
75
|
| ColumnDef
|
|
74
76
|
| FunctionNode
|
|
75
77
|
| CaseExpressionNode
|
|
@@ -119,26 +121,29 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
119
121
|
private readonly relationFacet: SelectRelationFacet;
|
|
120
122
|
private readonly lazyRelations: Set<string>;
|
|
121
123
|
private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
|
|
122
|
-
private readonly entityConstructor?: EntityConstructor;
|
|
123
|
-
private readonly includeTree: NormalizedRelationIncludeTree;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
private readonly entityConstructor?: EntityConstructor;
|
|
125
|
+
private readonly includeTree: NormalizedRelationIncludeTree;
|
|
126
|
+
private readonly cacheFacet: CacheFacet;
|
|
127
|
+
private readonly cacheContext: CacheFacetContext;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a new SelectQueryBuilder instance
|
|
127
131
|
* @param table - Table definition to query
|
|
128
132
|
* @param state - Optional initial query state
|
|
129
133
|
* @param hydration - Optional hydration manager
|
|
130
134
|
* @param dependencies - Optional query builder dependencies
|
|
131
135
|
*/
|
|
132
|
-
constructor(
|
|
133
|
-
table: TTable,
|
|
134
|
-
state?: SelectQueryState,
|
|
135
|
-
hydration?: HydrationManager,
|
|
136
|
-
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
137
|
-
lazyRelations?: Set<string>,
|
|
138
|
-
lazyRelationOptions?: Map<string, RelationIncludeOptions>,
|
|
139
|
-
entityConstructor?: EntityConstructor,
|
|
140
|
-
includeTree?: NormalizedRelationIncludeTree
|
|
141
|
-
|
|
136
|
+
constructor(
|
|
137
|
+
table: TTable,
|
|
138
|
+
state?: SelectQueryState,
|
|
139
|
+
hydration?: HydrationManager,
|
|
140
|
+
dependencies?: Partial<SelectQueryBuilderDependencies>,
|
|
141
|
+
lazyRelations?: Set<string>,
|
|
142
|
+
lazyRelationOptions?: Map<string, RelationIncludeOptions>,
|
|
143
|
+
entityConstructor?: EntityConstructor,
|
|
144
|
+
includeTree?: NormalizedRelationIncludeTree,
|
|
145
|
+
cacheContext?: CacheFacetContext
|
|
146
|
+
) {
|
|
142
147
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
143
148
|
this.env = { table, deps };
|
|
144
149
|
const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
|
|
@@ -150,9 +155,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
150
155
|
};
|
|
151
156
|
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
152
157
|
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
153
|
-
this.entityConstructor = entityConstructor;
|
|
154
|
-
this.includeTree = includeTree ?? {};
|
|
155
|
-
this.
|
|
158
|
+
this.entityConstructor = entityConstructor;
|
|
159
|
+
this.includeTree = includeTree ?? {};
|
|
160
|
+
this.cacheFacet = new CacheFacet();
|
|
161
|
+
this.cacheContext = cacheContext ?? { state: {} };
|
|
162
|
+
this.columnSelector = deps.createColumnSelector(this.env);
|
|
156
163
|
const relationManager = deps.createRelationManager(this.env);
|
|
157
164
|
this.fromFacet = new SelectFromFacet(this.env, createAstService);
|
|
158
165
|
this.joinFacet = new SelectJoinFacet(this.env, createAstService);
|
|
@@ -169,23 +176,25 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
169
176
|
* @param lazyRelations - Updated lazy relations set
|
|
170
177
|
* @returns New SelectQueryBuilder instance
|
|
171
178
|
*/
|
|
172
|
-
private clone<TNext = T>(
|
|
173
|
-
context: SelectQueryBuilderContext = this.context,
|
|
174
|
-
lazyRelations = new Set(this.lazyRelations),
|
|
175
|
-
lazyRelationOptions = new Map(this.lazyRelationOptions),
|
|
176
|
-
includeTree = this.includeTree
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
context.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
private clone<TNext = T>(
|
|
180
|
+
context: SelectQueryBuilderContext = this.context,
|
|
181
|
+
lazyRelations = new Set(this.lazyRelations),
|
|
182
|
+
lazyRelationOptions = new Map(this.lazyRelationOptions),
|
|
183
|
+
includeTree = this.includeTree,
|
|
184
|
+
cacheContext = this.cacheContext
|
|
185
|
+
): SelectQueryBuilder<TNext, TTable> {
|
|
186
|
+
return new SelectQueryBuilder(
|
|
187
|
+
this.env.table as TTable,
|
|
188
|
+
context.state,
|
|
189
|
+
context.hydration,
|
|
190
|
+
this.env.deps,
|
|
191
|
+
lazyRelations,
|
|
192
|
+
lazyRelationOptions,
|
|
193
|
+
this.entityConstructor,
|
|
194
|
+
includeTree,
|
|
195
|
+
cacheContext
|
|
196
|
+
) as SelectQueryBuilder<TNext, TTable>;
|
|
197
|
+
}
|
|
189
198
|
|
|
190
199
|
/**
|
|
191
200
|
* Applies an alias to the root FROM table.
|
|
@@ -741,25 +750,84 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
|
|
|
741
750
|
return this;
|
|
742
751
|
}
|
|
743
752
|
|
|
744
|
-
/**
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
* @
|
|
750
|
-
* @
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
753
|
+
/**
|
|
754
|
+
* Configures caching for this query.
|
|
755
|
+
* @param key - Unique cache key
|
|
756
|
+
* @param ttl - Time-to-live (e.g., '1h', '30m', '1d') or milliseconds
|
|
757
|
+
* @param tagsOrConfig - Optional tags for invalidation or configuration object
|
|
758
|
+
* @returns New query builder instance with cache configuration
|
|
759
|
+
* @example
|
|
760
|
+
* // Simple cache with TTL
|
|
761
|
+
* await selectFrom(User).cache('active_users', '1h').execute(session);
|
|
762
|
+
*
|
|
763
|
+
* // Cache with tags for invalidation
|
|
764
|
+
* await selectFrom(User)
|
|
765
|
+
* .cache('users_list', '30m', ['users', 'dashboard'])
|
|
766
|
+
* .execute(session);
|
|
767
|
+
*
|
|
768
|
+
* // Cache with auto-invalidation
|
|
769
|
+
* await selectFrom(User)
|
|
770
|
+
* .cache('users_list', '1h', { autoInvalidate: true })
|
|
771
|
+
* .execute(session);
|
|
772
|
+
*/
|
|
773
|
+
cache(
|
|
774
|
+
key: string,
|
|
775
|
+
ttl: Duration,
|
|
776
|
+
tagsOrConfig?: string[] | { tags?: string[]; autoInvalidate?: boolean }
|
|
777
|
+
): SelectQueryBuilder<T, TTable> {
|
|
778
|
+
const options = CacheFacet.createOptions(key, ttl, tagsOrConfig);
|
|
779
|
+
const nextCacheContext = this.cacheFacet.cache(this.cacheContext, options);
|
|
780
|
+
|
|
781
|
+
const builder = this.clone(
|
|
782
|
+
this.context,
|
|
783
|
+
new Set(this.lazyRelations),
|
|
784
|
+
new Map(this.lazyRelationOptions),
|
|
785
|
+
this.includeTree,
|
|
786
|
+
nextCacheContext
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
return builder;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Executes the query and returns hydrated results.
|
|
794
|
+
* If the builder was created with an entity constructor (e.g. via selectFromEntity),
|
|
795
|
+
* this will automatically return fully materialized entity instances.
|
|
796
|
+
* If caching is configured, results will be cached/retrieved from cache.
|
|
797
|
+
*
|
|
798
|
+
* @param ctx - ORM session context
|
|
799
|
+
* @returns Promise of entity instances (or objects if generic T is not an entity)
|
|
800
|
+
* @example
|
|
801
|
+
* const users = await selectFromEntity(User).execute(session);
|
|
802
|
+
* // users is User[]
|
|
803
|
+
* users[0] instanceof User; // true
|
|
804
|
+
*/
|
|
805
|
+
async execute(ctx: OrmSession): Promise<T[]> {
|
|
806
|
+
const cacheOptions = this.cacheFacet.getOptions(this.cacheContext);
|
|
807
|
+
|
|
808
|
+
// Se não tem cache configurado ou não há cache manager, executa normalmente
|
|
809
|
+
if (!cacheOptions || !ctx.cacheManager) {
|
|
810
|
+
return this.executeWithoutCache(ctx);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Executa com cache
|
|
814
|
+
return ctx.cacheManager.getOrExecute(
|
|
815
|
+
cacheOptions,
|
|
816
|
+
() => this.executeWithoutCache(ctx),
|
|
817
|
+
ctx.tenantId
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Executa a query sem cache (método interno)
|
|
823
|
+
*/
|
|
824
|
+
private async executeWithoutCache(ctx: OrmSession): Promise<T[]> {
|
|
825
|
+
if (this.entityConstructor) {
|
|
826
|
+
return this.executeAs(this.entityConstructor, ctx) as unknown as T[];
|
|
827
|
+
}
|
|
828
|
+
const builder = this.ensureDefaultSelection();
|
|
829
|
+
return executeHydrated(ctx, builder) as unknown as T[];
|
|
830
|
+
}
|
|
763
831
|
|
|
764
832
|
/**
|
|
765
833
|
* Executes the query and returns plain row objects (POJOs), ignoring any entity materialization.
|