metal-orm 1.1.8 → 1.1.10
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 +769 -764
- package/dist/index.cjs +2352 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +605 -40
- package/dist/index.d.ts +605 -40
- package/dist/index.js +2324 -226
- package/dist/index.js.map +1 -1
- package/package.json +22 -17
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +89 -0
- package/src/bulk/bulk-executor.base.ts +73 -0
- package/src/bulk/bulk-insert-executor.ts +74 -0
- package/src/bulk/bulk-types.ts +70 -0
- package/src/bulk/bulk-update-executor.ts +192 -0
- package/src/bulk/bulk-upsert-executor.ts +95 -0
- package/src/bulk/bulk-utils.ts +91 -0
- package/src/bulk/index.ts +18 -0
- package/src/codegen/typescript.ts +30 -21
- package/src/core/ast/expression-builders.ts +107 -10
- package/src/core/ast/expression-nodes.ts +52 -22
- package/src/core/ast/expression-visitor.ts +23 -13
- package/src/core/dialect/abstract.ts +30 -17
- package/src/core/dialect/mysql/index.ts +20 -5
- package/src/core/execution/db-executor.ts +96 -64
- package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
- package/src/core/execution/executors/mssql-executor.ts +66 -34
- package/src/core/execution/executors/mysql-executor.ts +98 -66
- package/src/core/execution/executors/postgres-executor.ts +33 -11
- package/src/core/execution/executors/sqlite-executor.ts +86 -30
- package/src/decorators/bootstrap.ts +482 -398
- package/src/decorators/column-decorator.ts +87 -96
- package/src/decorators/decorator-metadata.ts +100 -24
- package/src/decorators/entity.ts +27 -24
- package/src/decorators/relations.ts +231 -149
- package/src/decorators/transformers/transformer-decorators.ts +26 -29
- package/src/decorators/validators/country-validators-decorators.ts +9 -15
- package/src/dto/apply-filter.ts +568 -551
- package/src/index.ts +16 -9
- package/src/orm/entity-hydration.ts +116 -72
- package/src/orm/entity-metadata.ts +347 -301
- package/src/orm/entity-relations.ts +264 -207
- package/src/orm/entity.ts +199 -199
- package/src/orm/execute.ts +13 -13
- package/src/orm/lazy-batch/morph-many.ts +70 -0
- package/src/orm/lazy-batch/morph-one.ts +69 -0
- package/src/orm/lazy-batch/morph-to.ts +59 -0
- package/src/orm/lazy-batch.ts +4 -1
- package/src/orm/orm-session.ts +170 -104
- package/src/orm/pooled-executor-factory.ts +99 -58
- package/src/orm/query-logger.ts +49 -40
- package/src/orm/relation-change-processor.ts +198 -96
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -0
- package/src/orm/relations/morph-one.ts +151 -0
- package/src/orm/relations/morph-to.ts +162 -0
- package/src/orm/save-graph.ts +116 -1
- package/src/query-builder/expression-table-mapper.ts +5 -0
- package/src/query-builder/hydration-manager.ts +345 -345
- package/src/query-builder/hydration-planner.ts +178 -148
- package/src/query-builder/relation-conditions.ts +171 -151
- package/src/query-builder/relation-cte-builder.ts +5 -1
- package/src/query-builder/relation-filter-utils.ts +9 -6
- package/src/query-builder/relation-include-strategies.ts +44 -2
- package/src/query-builder/relation-join-strategies.ts +8 -1
- package/src/query-builder/relation-service.ts +250 -241
- package/src/query-builder/select/cursor-pagination.ts +323 -0
- package/src/query-builder/select/select-operations.ts +110 -105
- package/src/query-builder/select.ts +42 -1
- package/src/query-builder/update-include.ts +4 -0
- package/src/schema/relation.ts +296 -188
- package/src/schema/types.ts +138 -123
- package/src/tree/tree-decorator.ts +127 -137
package/src/orm/orm-session.ts
CHANGED
|
@@ -32,11 +32,16 @@ import { runInTransaction } from './transaction-runner.js';
|
|
|
32
32
|
import { saveGraphInternal, patchGraphInternal, SaveGraphOptions } from './save-graph.js';
|
|
33
33
|
import type { SaveGraphInputPayload, PatchGraphInputPayload } from './save-graph-types.js';
|
|
34
34
|
import type { QueryCacheManager } from '../cache/query-cache-manager.js';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
|
|
36
|
+
const NESTED_TRANSACTIONS_REQUIRE_SAVEPOINTS =
|
|
37
|
+
'Nested session.transaction calls require savepoint support in this executor';
|
|
38
|
+
const ROLLBACK_ONLY_TRANSACTION =
|
|
39
|
+
'Cannot commit transaction because an inner transaction failed';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Interface for ORM interceptors that allow hooking into the flush lifecycle.
|
|
43
|
+
*/
|
|
44
|
+
export interface OrmInterceptor {
|
|
40
45
|
/**
|
|
41
46
|
* Called before the flush operation begins.
|
|
42
47
|
* @param ctx - The entity context
|
|
@@ -54,22 +59,22 @@ export interface OrmInterceptor {
|
|
|
54
59
|
* Options for creating an OrmSession instance.
|
|
55
60
|
* @template E - The domain event type
|
|
56
61
|
*/
|
|
57
|
-
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
58
|
-
/** The ORM instance */
|
|
59
|
-
orm: Orm<E>;
|
|
60
|
-
/** The database executor */
|
|
61
|
-
executor: DbExecutor;
|
|
62
|
-
/** Optional query logger for debugging */
|
|
63
|
-
queryLogger?: QueryLogger;
|
|
64
|
-
/** Optional interceptors for flush lifecycle hooks */
|
|
65
|
-
interceptors?: OrmInterceptor[];
|
|
66
|
-
/** Optional domain event handlers */
|
|
67
|
-
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
68
|
-
/** Optional cache manager for query caching */
|
|
69
|
-
cacheManager?: QueryCacheManager;
|
|
70
|
-
/** Optional tenant ID for multi-tenancy */
|
|
71
|
-
tenantId?: string | number;
|
|
72
|
-
}
|
|
62
|
+
export interface OrmSessionOptions<E extends DomainEvent = OrmDomainEvent> {
|
|
63
|
+
/** The ORM instance */
|
|
64
|
+
orm: Orm<E>;
|
|
65
|
+
/** The database executor */
|
|
66
|
+
executor: DbExecutor;
|
|
67
|
+
/** Optional query logger for debugging */
|
|
68
|
+
queryLogger?: QueryLogger;
|
|
69
|
+
/** Optional interceptors for flush lifecycle hooks */
|
|
70
|
+
interceptors?: OrmInterceptor[];
|
|
71
|
+
/** Optional domain event handlers */
|
|
72
|
+
domainEventHandlers?: InitialHandlers<E, OrmSession<E>>;
|
|
73
|
+
/** Optional cache manager for query caching */
|
|
74
|
+
cacheManager?: QueryCacheManager;
|
|
75
|
+
/** Optional tenant ID for multi-tenancy */
|
|
76
|
+
tenantId?: string | number;
|
|
77
|
+
}
|
|
73
78
|
|
|
74
79
|
export interface SaveGraphSessionOptions extends SaveGraphOptions {
|
|
75
80
|
/** Wrap the save operation in a transaction (default: true). */
|
|
@@ -93,15 +98,18 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
93
98
|
readonly unitOfWork: UnitOfWork;
|
|
94
99
|
/** The domain event bus */
|
|
95
100
|
readonly domainEvents: DomainEventBus<E, OrmSession<E>>;
|
|
96
|
-
/** The relation change processor */
|
|
97
|
-
readonly relationChanges: RelationChangeProcessor;
|
|
98
|
-
/** The cache manager for query caching */
|
|
99
|
-
readonly cacheManager?: QueryCacheManager;
|
|
100
|
-
/** The tenant ID for multi-tenancy support */
|
|
101
|
-
readonly tenantId?: string | number;
|
|
102
|
-
|
|
101
|
+
/** The relation change processor */
|
|
102
|
+
readonly relationChanges: RelationChangeProcessor;
|
|
103
|
+
/** The cache manager for query caching */
|
|
104
|
+
readonly cacheManager?: QueryCacheManager;
|
|
105
|
+
/** The tenant ID for multi-tenancy support */
|
|
106
|
+
readonly tenantId?: string | number;
|
|
107
|
+
|
|
103
108
|
private readonly interceptors: OrmInterceptor[];
|
|
104
109
|
private saveGraphDefaults?: SaveGraphSessionOptions;
|
|
110
|
+
private transactionDepth = 0;
|
|
111
|
+
private savepointCounter = 0;
|
|
112
|
+
private rollbackOnly = false;
|
|
105
113
|
|
|
106
114
|
/**
|
|
107
115
|
* Creates a new OrmSession instance.
|
|
@@ -112,13 +120,13 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
112
120
|
this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
|
|
113
121
|
this.interceptors = [...(opts.interceptors ?? [])];
|
|
114
122
|
|
|
115
|
-
this.identityMap = new IdentityMap();
|
|
116
|
-
this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
|
|
117
|
-
this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
|
|
118
|
-
this.domainEvents = new DomainEventBus<E, OrmSession<E>>(opts.domainEventHandlers);
|
|
119
|
-
this.cacheManager = opts.cacheManager;
|
|
120
|
-
this.tenantId = opts.tenantId;
|
|
121
|
-
}
|
|
123
|
+
this.identityMap = new IdentityMap();
|
|
124
|
+
this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
|
|
125
|
+
this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
|
|
126
|
+
this.domainEvents = new DomainEventBus<E, OrmSession<E>>(opts.domainEventHandlers);
|
|
127
|
+
this.cacheManager = opts.cacheManager;
|
|
128
|
+
this.tenantId = opts.tenantId;
|
|
129
|
+
}
|
|
122
130
|
|
|
123
131
|
/**
|
|
124
132
|
* Releases resources associated with this session (executor/pool leases) and resets tracking.
|
|
@@ -533,37 +541,71 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
533
541
|
* @returns The result of the function
|
|
534
542
|
* @throws If the transaction fails
|
|
535
543
|
*/
|
|
536
|
-
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
537
|
-
// If the executor can't do transactions, just run and commit once.
|
|
538
|
-
if (!this.executor.capabilities.transactions) {
|
|
539
|
-
const result = await fn(this);
|
|
540
|
-
await this.commit();
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
await this.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
544
|
+
async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
|
|
545
|
+
// If the executor can't do transactions, just run and commit once.
|
|
546
|
+
if (!this.executor.capabilities.transactions) {
|
|
547
|
+
const result = await fn(this);
|
|
548
|
+
await this.commit();
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const isOutermost = this.transactionDepth === 0;
|
|
553
|
+
let savepointName: string | null = null;
|
|
554
|
+
|
|
555
|
+
if (isOutermost) {
|
|
556
|
+
this.rollbackOnly = false;
|
|
557
|
+
await this.executor.beginTransaction();
|
|
558
|
+
} else {
|
|
559
|
+
this.assertSavepointSupport();
|
|
560
|
+
savepointName = this.nextSavepointName();
|
|
561
|
+
await this.executor.savepoint!(savepointName);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.transactionDepth += 1;
|
|
565
|
+
try {
|
|
566
|
+
const result = await fn(this);
|
|
567
|
+
this.throwIfRollbackOnly();
|
|
568
|
+
await this.flushWithHooks();
|
|
569
|
+
this.throwIfRollbackOnly();
|
|
570
|
+
|
|
571
|
+
if (isOutermost) {
|
|
572
|
+
await this.executor.commitTransaction();
|
|
573
|
+
await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
|
|
574
|
+
} else {
|
|
575
|
+
await this.executor.releaseSavepoint!(savepointName!);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return result;
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (isOutermost) {
|
|
581
|
+
await this.rollback();
|
|
582
|
+
} else {
|
|
583
|
+
this.rollbackOnly = true;
|
|
584
|
+
await this.executor.rollbackToSavepoint!(savepointName!);
|
|
585
|
+
}
|
|
586
|
+
throw err;
|
|
587
|
+
} finally {
|
|
588
|
+
this.transactionDepth = Math.max(0, this.transactionDepth - 1);
|
|
589
|
+
if (this.transactionDepth === 0) {
|
|
590
|
+
this.rollbackOnly = false;
|
|
591
|
+
this.savepointCounter = 0;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
556
595
|
|
|
557
596
|
/**
|
|
558
597
|
* Rolls back the current transaction.
|
|
559
598
|
*/
|
|
560
|
-
async rollback(): Promise<void> {
|
|
561
|
-
if (this.executor.capabilities.transactions) {
|
|
562
|
-
await this.executor.rollbackTransaction();
|
|
563
|
-
}
|
|
564
|
-
this.
|
|
565
|
-
this.
|
|
566
|
-
|
|
599
|
+
async rollback(): Promise<void> {
|
|
600
|
+
if (this.executor.capabilities.transactions) {
|
|
601
|
+
await this.executor.rollbackTransaction();
|
|
602
|
+
}
|
|
603
|
+
this.transactionDepth = 0;
|
|
604
|
+
this.savepointCounter = 0;
|
|
605
|
+
this.rollbackOnly = false;
|
|
606
|
+
this.unitOfWork.reset();
|
|
607
|
+
this.relationChanges.reset();
|
|
608
|
+
}
|
|
567
609
|
|
|
568
610
|
/**
|
|
569
611
|
* Gets the execution context.
|
|
@@ -591,55 +633,79 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
|
|
|
591
633
|
};
|
|
592
634
|
}
|
|
593
635
|
|
|
594
|
-
/**
|
|
595
|
-
* Invalidates cache by specific tags.
|
|
596
|
-
* @param tags - Tags to invalidate
|
|
597
|
-
* @throws Error if no cache manager is configured
|
|
598
|
-
* @example
|
|
599
|
-
* await session.invalidateCacheTags(['users', 'dashboard']);
|
|
600
|
-
*/
|
|
601
|
-
async invalidateCacheTags(tags: string[]): Promise<void> {
|
|
602
|
-
if (!this.cacheManager) {
|
|
603
|
-
throw new Error('No cache manager configured. Please provide cacheManager when creating the session.');
|
|
604
|
-
}
|
|
605
|
-
await this.cacheManager.invalidateTags(tags);
|
|
636
|
+
/**
|
|
637
|
+
* Invalidates cache by specific tags.
|
|
638
|
+
* @param tags - Tags to invalidate
|
|
639
|
+
* @throws Error if no cache manager is configured
|
|
640
|
+
* @example
|
|
641
|
+
* await session.invalidateCacheTags(['users', 'dashboard']);
|
|
642
|
+
*/
|
|
643
|
+
async invalidateCacheTags(tags: string[]): Promise<void> {
|
|
644
|
+
if (!this.cacheManager) {
|
|
645
|
+
throw new Error('No cache manager configured. Please provide cacheManager when creating the session.');
|
|
646
|
+
}
|
|
647
|
+
await this.cacheManager.invalidateTags(tags);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Invalidates cache by key prefix (useful for multi-tenancy).
|
|
652
|
+
* @param prefix - Prefix to match cache keys
|
|
653
|
+
* @throws Error if no cache manager is configured
|
|
654
|
+
* @example
|
|
655
|
+
* await session.invalidateCachePrefix('tenant:123:');
|
|
656
|
+
*/
|
|
657
|
+
async invalidateCachePrefix(prefix: string): Promise<void> {
|
|
658
|
+
if (!this.cacheManager) {
|
|
659
|
+
throw new Error('No cache manager configured. Please provide cacheManager when creating the session.');
|
|
660
|
+
}
|
|
661
|
+
await this.cacheManager.invalidatePrefix(prefix);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Invalidates a specific cache key.
|
|
666
|
+
* @param key - Cache key to invalidate
|
|
667
|
+
* @throws Error if no cache manager is configured
|
|
668
|
+
* @example
|
|
669
|
+
* await session.invalidateCacheKey('active_users');
|
|
670
|
+
*/
|
|
671
|
+
async invalidateCacheKey(key: string): Promise<void> {
|
|
672
|
+
if (!this.cacheManager) {
|
|
673
|
+
throw new Error('No cache manager configured. Please provide cacheManager when creating the session.');
|
|
674
|
+
}
|
|
675
|
+
await this.cacheManager.invalidateKey(key, this.tenantId);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Merges session defaults with per-call saveGraph options.
|
|
680
|
+
* @param options - Per-call saveGraph options
|
|
681
|
+
* @returns Combined options with per-call values taking precedence
|
|
682
|
+
*/
|
|
683
|
+
private resolveSaveGraphOptions(options?: SaveGraphSessionOptions): SaveGraphSessionOptions {
|
|
684
|
+
return { ...(this.saveGraphDefaults ?? {}), ...(options ?? {}) };
|
|
606
685
|
}
|
|
607
686
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
throw new Error(
|
|
687
|
+
private assertSavepointSupport(): void {
|
|
688
|
+
if (!this.executor.capabilities.savepoints) {
|
|
689
|
+
throw new Error(NESTED_TRANSACTIONS_REQUIRE_SAVEPOINTS);
|
|
690
|
+
}
|
|
691
|
+
if (
|
|
692
|
+
typeof this.executor.savepoint !== 'function' ||
|
|
693
|
+
typeof this.executor.releaseSavepoint !== 'function' ||
|
|
694
|
+
typeof this.executor.rollbackToSavepoint !== 'function'
|
|
695
|
+
) {
|
|
696
|
+
throw new Error(NESTED_TRANSACTIONS_REQUIRE_SAVEPOINTS);
|
|
618
697
|
}
|
|
619
|
-
await this.cacheManager.invalidatePrefix(prefix);
|
|
620
698
|
}
|
|
621
699
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
* @throws Error if no cache manager is configured
|
|
626
|
-
* @example
|
|
627
|
-
* await session.invalidateCacheKey('active_users');
|
|
628
|
-
*/
|
|
629
|
-
async invalidateCacheKey(key: string): Promise<void> {
|
|
630
|
-
if (!this.cacheManager) {
|
|
631
|
-
throw new Error('No cache manager configured. Please provide cacheManager when creating the session.');
|
|
632
|
-
}
|
|
633
|
-
await this.cacheManager.invalidateKey(key, this.tenantId);
|
|
700
|
+
private nextSavepointName(): string {
|
|
701
|
+
this.savepointCounter += 1;
|
|
702
|
+
return `metalorm_sp_${this.savepointCounter}`;
|
|
634
703
|
}
|
|
635
704
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
*/
|
|
641
|
-
private resolveSaveGraphOptions(options?: SaveGraphSessionOptions): SaveGraphSessionOptions {
|
|
642
|
-
return { ...(this.saveGraphDefaults ?? {}), ...(options ?? {}) };
|
|
705
|
+
private throwIfRollbackOnly(): void {
|
|
706
|
+
if (this.rollbackOnly) {
|
|
707
|
+
throw new Error(ROLLBACK_ONLY_TRANSACTION);
|
|
708
|
+
}
|
|
643
709
|
}
|
|
644
710
|
}
|
|
645
711
|
|
|
@@ -1,39 +1,42 @@
|
|
|
1
|
-
import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
|
|
2
|
-
import { toExecutionPayload } from '../core/execution/db-executor.js';
|
|
3
|
-
import { rowsToQueryResult } from '../core/execution/db-executor.js';
|
|
1
|
+
import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
|
|
2
|
+
import { toExecutionPayload } from '../core/execution/db-executor.js';
|
|
3
|
+
import { rowsToQueryResult } from '../core/execution/db-executor.js';
|
|
4
4
|
import type { Pool } from '../core/execution/pooling/pool.js';
|
|
5
5
|
import type { DbExecutorFactory } from './orm.js';
|
|
6
6
|
|
|
7
7
|
export interface PooledConnectionAdapter<TConn> {
|
|
8
|
-
query(
|
|
9
|
-
conn: TConn,
|
|
10
|
-
sql: string,
|
|
11
|
-
params?: unknown[]
|
|
12
|
-
): Promise<
|
|
13
|
-
| Array<Record<string, unknown>>
|
|
14
|
-
| Array<Array<Record<string, unknown>>>
|
|
15
|
-
| QueryResult[]
|
|
16
|
-
>;
|
|
17
|
-
|
|
18
|
-
beginTransaction(conn: TConn): Promise<void>;
|
|
19
|
-
commitTransaction(conn: TConn): Promise<void>;
|
|
20
|
-
rollbackTransaction(conn: TConn): Promise<void>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Array.isArray(value)
|
|
36
|
-
|
|
8
|
+
query(
|
|
9
|
+
conn: TConn,
|
|
10
|
+
sql: string,
|
|
11
|
+
params?: unknown[]
|
|
12
|
+
): Promise<
|
|
13
|
+
| Array<Record<string, unknown>>
|
|
14
|
+
| Array<Array<Record<string, unknown>>>
|
|
15
|
+
| QueryResult[]
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
beginTransaction(conn: TConn): Promise<void>;
|
|
19
|
+
commitTransaction(conn: TConn): Promise<void>;
|
|
20
|
+
rollbackTransaction(conn: TConn): Promise<void>;
|
|
21
|
+
savepoint?(conn: TConn, name: string): Promise<void>;
|
|
22
|
+
releaseSavepoint?(conn: TConn, name: string): Promise<void>;
|
|
23
|
+
rollbackToSavepoint?(conn: TConn, name: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type PooledExecutorFactoryOptions<TConn> = {
|
|
27
|
+
pool: Pool<TConn>;
|
|
28
|
+
adapter: PooledConnectionAdapter<TConn>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isQueryResult = (value: unknown): value is QueryResult =>
|
|
32
|
+
typeof value === 'object' &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
Array.isArray((value as QueryResult).columns) &&
|
|
35
|
+
Array.isArray((value as QueryResult).values);
|
|
36
|
+
|
|
37
|
+
const isRowArray = (value: unknown): value is Array<Record<string, unknown>> =>
|
|
38
|
+
Array.isArray(value) &&
|
|
39
|
+
value.every(item => typeof item === 'object' && item !== null && !Array.isArray(item));
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Creates a first-class DbExecutorFactory backed by MetalORM's Pool.
|
|
@@ -45,11 +48,15 @@ const isRowArray = (value: unknown): value is Array<Record<string, unknown>> =>
|
|
|
45
48
|
*/
|
|
46
49
|
export function createPooledExecutorFactory<TConn>(
|
|
47
50
|
opts: PooledExecutorFactoryOptions<TConn>
|
|
48
|
-
): DbExecutorFactory {
|
|
49
|
-
const { pool, adapter } = opts;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
): DbExecutorFactory {
|
|
52
|
+
const { pool, adapter } = opts;
|
|
53
|
+
const supportsSavepoints =
|
|
54
|
+
typeof adapter.savepoint === 'function' &&
|
|
55
|
+
typeof adapter.releaseSavepoint === 'function' &&
|
|
56
|
+
typeof adapter.rollbackToSavepoint === 'function';
|
|
57
|
+
|
|
58
|
+
const makeExecutor = (mode: 'session' | 'sticky'): DbExecutor => {
|
|
59
|
+
let lease: Awaited<ReturnType<typeof pool.acquire>> | null = null;
|
|
53
60
|
|
|
54
61
|
const getLease = async () => {
|
|
55
62
|
if (lease) return lease;
|
|
@@ -61,19 +68,29 @@ export function createPooledExecutorFactory<TConn>(
|
|
|
61
68
|
conn: TConn,
|
|
62
69
|
sql: string,
|
|
63
70
|
params?: unknown[]
|
|
64
|
-
) => {
|
|
65
|
-
const rows = await adapter.query(conn, sql, params);
|
|
66
|
-
if (Array.isArray(rows) && rows.length > 0 && rows.every(isQueryResult)) {
|
|
67
|
-
return toExecutionPayload(rows);
|
|
68
|
-
}
|
|
69
|
-
if (Array.isArray(rows) && rows.length > 0 && rows.every(isRowArray)) {
|
|
70
|
-
return toExecutionPayload(rows.map(set => rowsToQueryResult(set)));
|
|
71
|
+
) => {
|
|
72
|
+
const rows = await adapter.query(conn, sql, params);
|
|
73
|
+
if (Array.isArray(rows) && rows.length > 0 && rows.every(isQueryResult)) {
|
|
74
|
+
return toExecutionPayload(rows);
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(rows) && rows.length > 0 && rows.every(isRowArray)) {
|
|
77
|
+
return toExecutionPayload(rows.map(set => rowsToQueryResult(set)));
|
|
71
78
|
}
|
|
72
79
|
return toExecutionPayload([rowsToQueryResult(rows as Array<Record<string, unknown>>)]);
|
|
73
80
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
|
|
82
|
+
const requireActiveTransactionLease = () => {
|
|
83
|
+
if (!lease) {
|
|
84
|
+
throw new Error('savepoint operation called without an active transaction');
|
|
85
|
+
}
|
|
86
|
+
return lease;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
capabilities: {
|
|
91
|
+
transactions: true,
|
|
92
|
+
...(supportsSavepoints ? { savepoints: true } : {}),
|
|
93
|
+
},
|
|
77
94
|
|
|
78
95
|
async executeSql(sql, params) {
|
|
79
96
|
// Sticky mode: always reuse a leased connection.
|
|
@@ -114,22 +131,46 @@ export function createPooledExecutorFactory<TConn>(
|
|
|
114
131
|
}
|
|
115
132
|
},
|
|
116
133
|
|
|
117
|
-
async rollbackTransaction() {
|
|
118
|
-
if (!lease) {
|
|
119
|
-
// Nothing to rollback; keep idempotent semantics.
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
134
|
+
async rollbackTransaction() {
|
|
135
|
+
if (!lease) {
|
|
136
|
+
// Nothing to rollback; keep idempotent semantics.
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
122
139
|
const l = lease;
|
|
123
140
|
try {
|
|
124
141
|
await adapter.rollbackTransaction(l.resource);
|
|
125
142
|
} finally {
|
|
126
143
|
lease = null;
|
|
127
|
-
await l.release();
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
async
|
|
132
|
-
if (!
|
|
144
|
+
await l.release();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async savepoint(name: string) {
|
|
149
|
+
if (!supportsSavepoints) {
|
|
150
|
+
throw new Error('Savepoints are not supported by this executor');
|
|
151
|
+
}
|
|
152
|
+
const l = requireActiveTransactionLease();
|
|
153
|
+
await adapter.savepoint!(l.resource, name);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
async releaseSavepoint(name: string) {
|
|
157
|
+
if (!supportsSavepoints) {
|
|
158
|
+
throw new Error('Savepoints are not supported by this executor');
|
|
159
|
+
}
|
|
160
|
+
const l = requireActiveTransactionLease();
|
|
161
|
+
await adapter.releaseSavepoint!(l.resource, name);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
async rollbackToSavepoint(name: string) {
|
|
165
|
+
if (!supportsSavepoints) {
|
|
166
|
+
throw new Error('Savepoints are not supported by this executor');
|
|
167
|
+
}
|
|
168
|
+
const l = requireActiveTransactionLease();
|
|
169
|
+
await adapter.rollbackToSavepoint!(l.resource, name);
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async dispose() {
|
|
173
|
+
if (!lease) return;
|
|
133
174
|
const l = lease;
|
|
134
175
|
lease = null;
|
|
135
176
|
await l.release();
|
package/src/orm/query-logger.ts
CHANGED
|
@@ -1,47 +1,56 @@
|
|
|
1
|
-
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Represents a single SQL query log entry
|
|
5
|
-
*/
|
|
6
|
-
export interface QueryLogEntry {
|
|
7
|
-
/** The SQL query that was executed */
|
|
8
|
-
sql: string;
|
|
9
|
-
/** Parameters used in the query */
|
|
10
|
-
params?: unknown[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Function type for query logging callbacks
|
|
15
|
-
* @param entry - The query log entry to process
|
|
16
|
-
*/
|
|
17
|
-
export type QueryLogger = (entry: QueryLogEntry) => void;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Creates a wrapped database executor that logs all SQL queries
|
|
21
|
-
* @param executor - Original database executor to wrap
|
|
22
|
-
* @param logger - Optional logger function to receive query log entries
|
|
23
|
-
* @returns Wrapped executor that logs queries before execution
|
|
24
|
-
*/
|
|
25
|
-
export const createQueryLoggingExecutor = (
|
|
26
|
-
executor: DbExecutor,
|
|
27
|
-
logger?: QueryLogger
|
|
28
|
-
): DbExecutor => {
|
|
29
|
-
if (!logger) {
|
|
30
|
-
return executor;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const wrapped: DbExecutor = {
|
|
34
|
-
capabilities: executor.capabilities,
|
|
35
|
-
async executeSql(sql, params) {
|
|
36
|
-
logger({ sql, params });
|
|
37
|
-
return executor.executeSql(sql, params);
|
|
1
|
+
import type { DbExecutor } from '../core/execution/db-executor.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a single SQL query log entry
|
|
5
|
+
*/
|
|
6
|
+
export interface QueryLogEntry {
|
|
7
|
+
/** The SQL query that was executed */
|
|
8
|
+
sql: string;
|
|
9
|
+
/** Parameters used in the query */
|
|
10
|
+
params?: unknown[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Function type for query logging callbacks
|
|
15
|
+
* @param entry - The query log entry to process
|
|
16
|
+
*/
|
|
17
|
+
export type QueryLogger = (entry: QueryLogEntry) => void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a wrapped database executor that logs all SQL queries
|
|
21
|
+
* @param executor - Original database executor to wrap
|
|
22
|
+
* @param logger - Optional logger function to receive query log entries
|
|
23
|
+
* @returns Wrapped executor that logs queries before execution
|
|
24
|
+
*/
|
|
25
|
+
export const createQueryLoggingExecutor = (
|
|
26
|
+
executor: DbExecutor,
|
|
27
|
+
logger?: QueryLogger
|
|
28
|
+
): DbExecutor => {
|
|
29
|
+
if (!logger) {
|
|
30
|
+
return executor;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const wrapped: DbExecutor = {
|
|
34
|
+
capabilities: executor.capabilities,
|
|
35
|
+
async executeSql(sql, params) {
|
|
36
|
+
logger({ sql, params });
|
|
37
|
+
return executor.executeSql(sql, params);
|
|
38
38
|
}
|
|
39
39
|
,
|
|
40
40
|
beginTransaction: () => executor.beginTransaction(),
|
|
41
41
|
commitTransaction: () => executor.commitTransaction(),
|
|
42
42
|
rollbackTransaction: () => executor.rollbackTransaction(),
|
|
43
|
+
savepoint: executor.savepoint
|
|
44
|
+
? (name: string) => executor.savepoint!(name)
|
|
45
|
+
: undefined,
|
|
46
|
+
releaseSavepoint: executor.releaseSavepoint
|
|
47
|
+
? (name: string) => executor.releaseSavepoint!(name)
|
|
48
|
+
: undefined,
|
|
49
|
+
rollbackToSavepoint: executor.rollbackToSavepoint
|
|
50
|
+
? (name: string) => executor.rollbackToSavepoint!(name)
|
|
51
|
+
: undefined,
|
|
43
52
|
dispose: () => executor.dispose(),
|
|
44
53
|
};
|
|
45
|
-
|
|
46
|
-
return wrapped;
|
|
47
|
-
};
|
|
54
|
+
|
|
55
|
+
return wrapped;
|
|
56
|
+
};
|