@fluojs/drizzle 1.1.0 → 2.0.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.ko.md +80 -24
- package/README.md +80 -24
- package/dist/database.d.ts +8 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +189 -41
- package/dist/module.d.ts +21 -5
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +21 -77
- package/dist/named-registration.d.ts +20 -0
- package/dist/named-registration.d.ts.map +1 -0
- package/dist/named-registration.js +84 -0
- package/dist/registration-name.d.ts +9 -0
- package/dist/registration-name.d.ts.map +1 -0
- package/dist/registration-name.js +17 -0
- package/dist/registration-providers.d.ts +44 -0
- package/dist/registration-providers.d.ts.map +1 -0
- package/dist/registration-providers.js +120 -0
- package/dist/tokens.d.ts +29 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +47 -1
- package/dist/transaction.d.ts +28 -3
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +50 -5
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
package/README.ko.md
CHANGED
|
@@ -14,6 +14,7 @@ Node.js 전용 트랜잭션 인지형 데이터베이스 래퍼와 선택적 dis
|
|
|
14
14
|
- [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
|
|
15
15
|
- [수동 트랜잭션과 current()](#수동-트랜잭션과-current)
|
|
16
16
|
- [요청 전체 컨트롤러 경계](#요청-전체-컨트롤러-경계)
|
|
17
|
+
- [이름 있는 클라이언트](#이름-있는-클라이언트)
|
|
17
18
|
- [종료와 상태 계약](#종료와-상태-계약)
|
|
18
19
|
- [수동 모듈 구성](#수동-모듈-구성)
|
|
19
20
|
- [공개 API 개요](#공개-api-개요)
|
|
@@ -23,14 +24,16 @@ Node.js 전용 트랜잭션 인지형 데이터베이스 래퍼와 선택적 dis
|
|
|
23
24
|
## 설치
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
npm install @fluojs/drizzle drizzle-orm
|
|
27
|
+
npm install @fluojs/drizzle drizzle-orm@^0.45.2
|
|
27
28
|
# 사용하는 Drizzle adapter에 맞는 driver도 함께 설치하세요. 예:
|
|
28
29
|
npm install pg
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
`@fluojs/drizzle`은 Drizzle ORM `>=0.45.2`를 요구합니다. 더 오래된 Drizzle ORM release를 사용하던 consumer는 이 major `@fluojs/drizzle` release를 적용하기 전에 peer를 업그레이드하고 lockfile을 갱신해야 합니다. fluo integration API는 그대로지만, 애플리케이션은 업그레이드된 ORM을 대상으로 driver별 query 및 migration test를 실행해야 합니다.
|
|
33
|
+
|
|
31
34
|
## 런타임 지원
|
|
32
35
|
|
|
33
|
-
루트 `@fluojs/drizzle` 패키지는
|
|
36
|
+
루트 `@fluojs/drizzle` 패키지는 Node.js `>=24.0.0 <27`을 요구합니다. ambient transaction context를 유지하기 위해 Node의 `node:async_hooks` 모듈을 import하며 패키지 자체의 지원 범위를 package manifest에 선언합니다. Node 20 및 Node 22 host를 Node.js `>=24.0.0 <27`로 올리세요. Node 24 미만과 Node 27 이상은 지원하지 않습니다.
|
|
34
37
|
|
|
35
38
|
Drizzle ORM 자체는 Bun SQL이나 Cloudflare D1 같은 driver도 대상으로 할 수 있지만, 비 Node transaction-context adapter가 문서화되기 전까지 해당 driver runtime은 이 fluo wrapper 범위 밖입니다.
|
|
36
39
|
|
|
@@ -38,14 +41,14 @@ Drizzle ORM 자체는 Bun SQL이나 Cloudflare D1 같은 driver도 대상으로
|
|
|
38
41
|
|
|
39
42
|
## 사용 시점
|
|
40
43
|
|
|
41
|
-
- Node.js
|
|
44
|
+
- Node.js `>=24.0.0 <27` 애플리케이션에서 Drizzle을 다른 fluo 모듈과 같은 DI·모듈·라이프사이클 모델 안에 넣고 싶을 때
|
|
42
45
|
- repository 코드가 root handle과 현재 트랜잭션 handle 사이를 `current()` 하나로 다루고 싶을 때
|
|
43
46
|
- 애플리케이션 종료 시 underlying driver 정리 로직도 함께 실행해야 할 때
|
|
44
47
|
|
|
45
48
|
## 빠른 시작
|
|
46
49
|
|
|
47
50
|
```ts
|
|
48
|
-
import { ConfigService } from '@fluojs/config';
|
|
51
|
+
import { ConfigModule, ConfigService } from '@fluojs/config';
|
|
49
52
|
import { Module } from '@fluojs/core';
|
|
50
53
|
import { DrizzleModule } from '@fluojs/drizzle';
|
|
51
54
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
@@ -53,6 +56,12 @@ import { Pool } from 'pg';
|
|
|
53
56
|
|
|
54
57
|
@Module({
|
|
55
58
|
imports: [
|
|
59
|
+
ConfigModule.forRoot({
|
|
60
|
+
global: true,
|
|
61
|
+
processEnv: {
|
|
62
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
56
65
|
DrizzleModule.forRootAsync({
|
|
57
66
|
inject: [ConfigService],
|
|
58
67
|
useFactory: async (config: ConfigService) => {
|
|
@@ -73,6 +82,8 @@ import { Pool } from 'pg';
|
|
|
73
82
|
export class AppModule {}
|
|
74
83
|
```
|
|
75
84
|
|
|
85
|
+
`forRootAsync(...)`는 factory 의존성으로 `inject`와 `useFactory`만 받으며 NestJS `imports`, `useClass`, `useExisting`, decorator metadata를 탐색하지 않습니다. 생성되는 async module에는 `imports`가 없으므로 sibling module이 export하거나 parent module이 import한 token은 option provider에 보이지 않습니다. 대신 factory 의존성을 global module로 등록하세요. 위의 `ConfigModule.forRoot(...)` 등록은 기본적으로 `ConfigService`를 전역 export하며, 생성된 async Drizzle module이 `ConfigService`를 볼 수 있는 전역 export임을 명확히 하기 위해 `global: true`를 명시했습니다. 다른 token도 importing application의 `providers`나 imports에 의존하지 말고, 해당 token을 소유하고 export하는 module을 bootstrap 전에 global로 만드세요.
|
|
86
|
+
|
|
76
87
|
## 주요 패턴
|
|
77
88
|
|
|
78
89
|
### 서비스 트랜잭션 경계 (@Transaction)
|
|
@@ -80,41 +91,50 @@ export class AppModule {}
|
|
|
80
91
|
`@Transaction()` 데코레이터는 서비스 레이어에서 트랜잭션 경계를 정의하는 권장 방법입니다. 이 데코레이터가 적용된 메서드 내부에서 발생하는 모든 리포지토리 호출은 동일한 Drizzle 트랜잭션을 공유합니다.
|
|
81
92
|
|
|
82
93
|
```ts
|
|
94
|
+
import { Inject } from '@fluojs/core';
|
|
83
95
|
import { Transaction, DrizzleDatabase, type DrizzleDatabaseFacade } from '@fluojs/drizzle';
|
|
84
96
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
85
97
|
import { users, profiles } from './schema';
|
|
86
98
|
|
|
87
99
|
type AppDatabase = ReturnType<typeof drizzle>;
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
constructor(private readonly repo: UserRepository) {}
|
|
91
|
-
|
|
92
|
-
@Transaction()
|
|
93
|
-
async onboardUser(dto: any) {
|
|
94
|
-
const user = await this.repo.create(dto);
|
|
95
|
-
await this.repo.initProfile(user.id);
|
|
96
|
-
return user;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
101
|
+
@Inject(DrizzleDatabase)
|
|
100
102
|
export class UserRepository {
|
|
101
103
|
constructor(private readonly db: DrizzleDatabaseFacade<AppDatabase>) {}
|
|
102
104
|
|
|
103
105
|
async create(data: any) {
|
|
104
106
|
// facade 타입은 표준 Drizzle 메서드를 노출합니다.
|
|
105
107
|
// @Transaction() 내부에서 호출되면 자동으로 활성 트랜잭션에 참여합니다.
|
|
106
|
-
|
|
108
|
+
const [user] = await this.db.insert(users).values(data).returning();
|
|
109
|
+
|
|
110
|
+
if (!user) {
|
|
111
|
+
throw new Error('User insert did not return a row.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return user;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
async initProfile(userId: string) {
|
|
110
118
|
return this.db.insert(profiles).values({ userId });
|
|
111
119
|
}
|
|
112
120
|
}
|
|
121
|
+
|
|
122
|
+
@Inject(UserRepository)
|
|
123
|
+
export class UserService {
|
|
124
|
+
constructor(private readonly repo: UserRepository) {}
|
|
125
|
+
|
|
126
|
+
@Transaction()
|
|
127
|
+
async onboardUser(dto: any) {
|
|
128
|
+
const user = await this.repo.create(dto);
|
|
129
|
+
await this.repo.initProfile(user.id);
|
|
130
|
+
return user;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
113
133
|
```
|
|
114
134
|
|
|
115
135
|
`@Transaction()` 메서드 호출은 재진입(reentrant)이 가능합니다. 데코레이터가 적용된 메서드가 다른 데코레이터 적용 메서드를 호출하더라도 하나의 동일한 Drizzle 트랜잭션 안에서 실행됩니다.
|
|
116
136
|
|
|
117
|
-
기본적으로 `@Transaction()`은 작은 host-object heuristic으로 대상을 고릅니다. 먼저 `this.db`를 확인하고, 그다음 데코레이터가 붙은 인스턴스의 직접 property, 마지막으로 그 값들의 중첩 `.db` property 중 `transaction(...)` 메서드를 노출하는 첫 값을 사용합니다. 이 덕분에 `constructor(private readonly db: DrizzleDatabase<...>)` 같은 일반
|
|
137
|
+
기본적으로 `@Transaction()`은 작은 host-object heuristic으로 대상을 고릅니다. 먼저 `this.db`를 확인하고, 그다음 데코레이터가 붙은 인스턴스의 직접 property, 마지막으로 그 값들의 중첩 `.db` property 중 `transaction(...)` 메서드를 노출하는 첫 값을 사용합니다. 이 후보들이 모두 맞지 않으면 데코레이터가 붙은 인스턴스 자체를 transaction 대상으로 사용합니다. 이 덕분에 `constructor(private readonly db: DrizzleDatabase<...>)` 같은 일반 서비스와 자체 facade host는 간결하게 유지할 수 있지만, 하나의 서비스가 Drizzle wrapper를 둘 이상 소유한다면 property 순서에 의존하지 마세요. 데코레이터가 붙은 host가 여러 transaction-capable client를 갖거나 `.db`를 노출하는 repository를 감싸는 경우에는 `@Transaction((self) => self.ordersDb)` 또는 `@Transaction((self) => self.analyticsDb, options)`처럼 명시적 accessor를 전달하세요.
|
|
118
138
|
|
|
119
139
|
### 수동 트랜잭션과 current()
|
|
120
140
|
|
|
@@ -152,20 +172,25 @@ await this.db.transaction(async () => {
|
|
|
152
172
|
|
|
153
173
|
중첩 호출은 활성 transaction boundary를 재사용합니다. 이미 boundary가 활성화되어 있는데 중첩 호출이 transaction option을 전달하면, 기존 transaction을 조용히 바꾸지 않고 해당 중첩 option을 거부합니다.
|
|
154
174
|
|
|
155
|
-
`database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`(기본값)이면 `transaction()`과 `requestTransaction()`은 의도적으로 fail-open(fail-open fallback)하여 callback을 root handle에서 직접 실행합니다. 이는 local fake, read-only adapter, 점진적 migration에는 유용하지만 원자적이지 않으므로 실제 데이터베이스 transaction으로 취급하면 안 됩니다. rollback 보장이 필요한 production 경로에서는 `strictTransactions: true`를 설정하세요. 그러면 startup 및 readiness 진단에서 누락된 `database.transaction(...)` 지원을 드러내고, transaction helper는 트랜잭션 없이 조용히 실행하는 대신 예외를 던집니다.
|
|
175
|
+
`database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`(기본값)이면 `transaction()`과 `requestTransaction()`은 의도적으로 fail-open(fail-open fallback)하여 callback을 root handle에서 직접 실행합니다. 이는 local fake, read-only adapter, 점진적 migration에는 유용하지만 원자적이지 않으므로 실제 데이터베이스 transaction으로 취급하면 안 됩니다. rollback 보장이 필요한 production 경로에서는 `strictTransactions: true`를 설정하세요. 그러면 startup 및 readiness 진단에서 누락된 `database.transaction(...)` 지원을 드러내고, transaction helper는 트랜잭션 없이 조용히 실행하는 대신 예외를 던집니다. Fail-open callback도 root-handle ALS context에서 실행되므로 중첩 helper는 fallback boundary를 재사용하고, 중첩 request 작업은 ambient request `AbortSignal`을 상속하며, shutdown은 dispose 전에 중첩 직접 실행을 drain합니다. 이 context 보존은 rollback 원자성을 추가하지 않습니다.
|
|
176
|
+
|
|
177
|
+
Transaction 안에서 생성된 async 작업은 소유 transaction이 commit, rollback 또는 다른 방식으로 settle된 뒤 실행되더라도 ALS context를 상속할 수 있습니다. 이렇게 상속된 continuation에서 나중에 호출하는 `transaction(...)` 또는 `requestTransaction(...)`은 닫힌 transaction handle을 재사용하지 않고 lifecycle tracking이 적용된 새 root로 처리됩니다. Shutdown은 `dispose(database)` 전에 이 새 root를 drain하며, owner가 settle되기 전에 시작한 호출은 계속 활성 boundary를 공유합니다.
|
|
156
178
|
|
|
157
179
|
### 요청 전체 컨트롤러 경계
|
|
158
180
|
|
|
159
181
|
비즈니스 작업에는 서비스 레벨 `@Transaction()`을 우선 사용하세요. 전체 요청을 하나의 transaction으로 감싸던 NestJS controller/interceptor 패턴을 마이그레이션해야 한다면 controller, route adapter, request orchestration 경계에서 `requestTransaction(...)`을 명시적으로 호출하고 가능한 경우 request `AbortSignal`을 전달하세요.
|
|
160
182
|
|
|
161
183
|
```ts
|
|
162
|
-
import {
|
|
184
|
+
import { Inject } from '@fluojs/core';
|
|
185
|
+
import { Controller, Post, type RequestContext } from '@fluojs/http';
|
|
163
186
|
import { DrizzleDatabase } from '@fluojs/drizzle';
|
|
164
187
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
188
|
+
import { CheckoutService } from './checkout.service';
|
|
165
189
|
|
|
166
190
|
type AppDatabase = ReturnType<typeof drizzle>;
|
|
167
191
|
|
|
168
192
|
@Controller('/checkout')
|
|
193
|
+
@Inject(DrizzleDatabase, CheckoutService)
|
|
169
194
|
export class CheckoutController {
|
|
170
195
|
constructor(
|
|
171
196
|
private readonly db: DrizzleDatabase<AppDatabase>,
|
|
@@ -173,20 +198,46 @@ export class CheckoutController {
|
|
|
173
198
|
) {}
|
|
174
199
|
|
|
175
200
|
@Post()
|
|
176
|
-
create(input: CheckoutInput,
|
|
201
|
+
create(input: CheckoutInput, context: RequestContext) {
|
|
177
202
|
return this.db.requestTransaction(
|
|
178
203
|
() => this.checkout.createOrder(input),
|
|
179
|
-
|
|
204
|
+
context.request.signal,
|
|
180
205
|
);
|
|
181
206
|
}
|
|
182
207
|
}
|
|
183
208
|
```
|
|
184
209
|
|
|
185
|
-
import
|
|
210
|
+
`DrizzleTransactionInterceptor`는 기존 NestJS interceptor import를 위한 deprecated 1.x 호환성 bridge입니다. 이 interceptor는 `requestTransaction(...)`에 위임하고 request `AbortSignal`을 전달합니다. 새 코드에서는 비즈니스 transaction boundary를 서비스로 옮기고, 전체 request 작업이 서비스 메서드 하나가 아니라 같은 boundary를 공유해야 하는 드문 controller-level 사례에만 명시적 `requestTransaction(...)`을 사용하세요. controller가 명시적 `DrizzleDatabase` 대상을 소유한다면 controller method에 `@Transaction()`을 붙이는 방식도 호환성 경로로 유지되지만, request `AbortSignal`을 직접 받을 수 있는 `requestTransaction(...)`이 더 명확한 request-wide API입니다.
|
|
211
|
+
|
|
212
|
+
### 이름 있는 클라이언트
|
|
213
|
+
|
|
214
|
+
추가 client는 비어 있지 않은 `name`으로 등록하고 `DrizzleDatabase` class token 대신 package-owned token을 주입합니다.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const ANALYTICS_DRIZZLE = getDrizzleHandleProviderToken('analytics');
|
|
218
|
+
|
|
219
|
+
DrizzleModule.forRoot({ database: primaryDatabase });
|
|
220
|
+
DrizzleModule.forRoot({ database: analyticsDatabase, name: 'analytics' });
|
|
221
|
+
|
|
222
|
+
@Inject(ANALYTICS_DRIZZLE)
|
|
223
|
+
class AnalyticsService {
|
|
224
|
+
constructor(private readonly analytics: DrizzleDatabase<AnalyticsDatabase>) {}
|
|
225
|
+
|
|
226
|
+
@Transaction((self: AnalyticsService) => self.analytics)
|
|
227
|
+
async rebuild() {}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`getDrizzleDatabaseToken`, `getDrizzleDisposeToken`, `getDrizzleOptionsToken`,
|
|
232
|
+
`getDrizzleHandleProviderToken`은 trim된 이름마다 서로 다른 안정적인 identity를 반환합니다. 이름 있는 client는
|
|
233
|
+
non-global이며 ALS transaction context, shutdown drain, disposal, status를 독립적으로 소유합니다. consumer는 일치하는
|
|
234
|
+
이름 있는 token을 export하는 module을 import해야 하며, 이름이 runtime container를 분리하지는 않습니다. `name`을 생략하면
|
|
235
|
+
기존 default token, `DrizzleDatabase` class token, interceptor 동작이 유지됩니다.
|
|
186
236
|
|
|
187
237
|
### 종료와 상태 계약
|
|
188
238
|
|
|
189
|
-
애플리케이션 종료 중에는 `DrizzleDatabase`가 아직 활성 상태인 요청 트랜잭션을 abort하고, 열린 요청 및 수동 transaction callback이 settle되거나 rollback될 때까지 기다린 뒤 선택적 `dispose(database)` hook을 실행합니다.
|
|
239
|
+
애플리케이션 종료 중에는 `DrizzleDatabase`가 아직 활성 상태인 요청 트랜잭션을 abort하고, 열린 요청 및 수동 transaction callback이 settle되거나 rollback될 때까지 기다린 뒤 선택적 `dispose(database)` hook을 실행합니다. 여기에는 `database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`일 때의 fail-open 수동 `transaction(...)` callback도 포함되므로, 직접 실행 fallback도 pool이나 외부 관리 리소스를 닫기 전에 drain됩니다.
|
|
240
|
+
상속한 owner가 settle된 뒤 새 boundary를 시작하는 transaction continuation은 닫힌 transaction handle을 더 이상 재사용하지 않습니다. 이 continuation은 독립적으로 tracking되는 root가 되며, shutdown은 disposal 전에 해당 continuation root를 기다립니다.
|
|
190
241
|
기존 요청 boundary 안에서 열린 중첩 `requestTransaction(...)` 호출은 활성 Drizzle transaction을 재사용하면서도 ambient request abort signal을 관찰합니다. 기존 수동 transaction boundary 안에서 열린 중첩 `requestTransaction(...)` 호출도 두 번째 Drizzle transaction을 열지 않고 shutdown settlement tracking에 참여하며, 해당 settlement handle은 바깥 수동 transaction이 settle될 때까지 tracking에 남아 shutdown이 `dispose(database)`를 실행하기 전에 그 바깥 경계까지 drain하게 합니다. 단, platform status activity count는 더 짧게 유지됩니다. 중첩 request callback이 settle되는 즉시, 바깥 수동 transaction이 계속 실행 중이어도 `details.activeRequestTransactions`는 감소합니다.
|
|
191
242
|
종료가 시작된 뒤 새 `transaction(...)` 및 `requestTransaction(...)` 호출은 거부되므로, 종료 boundary를 지난 뒤 시작되는 늦은 트랜잭션보다 dispose가 먼저 실행되는 상황을 방지합니다.
|
|
192
243
|
요청 callback이 완료된 뒤 underlying Drizzle transaction runner가 commit 또는 rollback을 끝내기 전에 request signal이 abort되면, `requestTransaction(...)`은 먼저 해당 runner가 settle될 때까지 기다린 다음 abort reason으로 reject합니다. 이 동작은 Drizzle cleanup을 request cancellation과 직렬화하면서, 완료된 callback 결과를 반환하는 대신 늦은 request abort를 caller에게 드러냅니다.
|
|
@@ -223,8 +274,10 @@ defineModule(ManualDrizzleModule, {
|
|
|
223
274
|
- `DrizzleModule.forRoot(options)` / `DrizzleModule.forRootAsync(options)`
|
|
224
275
|
- `DrizzleDatabase`
|
|
225
276
|
- `DrizzleDatabaseFacade<TDatabase>`
|
|
277
|
+
- `DrizzleTransactionInterceptor` (deprecated 1.x request-transaction compatibility bridge)
|
|
226
278
|
- `Transaction`
|
|
227
279
|
- `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_HANDLE_PROVIDER`, `DRIZZLE_OPTIONS`
|
|
280
|
+
- `getDrizzleDatabaseToken(name?)`, `getDrizzleDisposeToken(name?)`, `getDrizzleHandleProviderToken(name?)`, `getDrizzleOptionsToken(name?)`
|
|
228
281
|
- `DrizzleDatabase.createFacade(...)` (호환성 전용 provider wiring helper; 애플리케이션 등록은 `DrizzleModule.forRoot(...)` / `forRootAsync(...)`를 우선 사용)
|
|
229
282
|
- `createDrizzlePlatformStatusSnapshot(...)`
|
|
230
283
|
- `DrizzleDatabaseLike`
|
|
@@ -233,9 +286,11 @@ defineModule(ManualDrizzleModule, {
|
|
|
233
286
|
|
|
234
287
|
`DRIZZLE_HANDLE_PROVIDER`는 lifecycle-aware `DrizzleDatabase` wrapper를 가리키는 alias token입니다. `@fluojs/terminus` 같은 health integration은 이 token을 통해 raw database ping으로 fallback하기 전에 `createPlatformStatusSnapshot()`을 읽습니다.
|
|
235
288
|
|
|
289
|
+
`DrizzleModule`은 importing module을 위해 `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_OPTIONS`를 export합니다. `DRIZZLE_DATABASE`는 설정된 raw Drizzle handle을 주입하므로 lifecycle-aware facade와 ambient transaction handle 선택을 우회합니다. 애플리케이션 repository에는 `DrizzleDatabase` 또는 `DrizzleDatabaseFacade`를 우선 사용하고, 설정된 driver handle이 꼭 필요한 integration에만 raw token을 주입하세요. `DRIZZLE_DISPOSE`는 설정된 선택적 cleanup hook을, `DRIZZLE_OPTIONS`는 정규화된 runtime option을 노출합니다.
|
|
290
|
+
|
|
236
291
|
provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createPlatformStatusSnapshot()` 같은 wrapper 메서드만 필요로 하면 `DrizzleDatabase<TDatabase>`를 사용하세요. 리포지토리 주입에서 Drizzle query 메서드를 직접 호출해야 한다면 `DrizzleDatabaseFacade<TDatabase>`를 사용합니다. 이 facade는 활성 트랜잭션 handle이 있으면 그 handle로, 없으면 root handle로 호출을 전달합니다. `DrizzleDatabase.createFacade(...)`는 module provider wiring을 위한 low-level compatibility helper로 유지됩니다. 애플리케이션 코드는 `DrizzleModule.forRoot(...)` / `forRootAsync(...)`를 우선 사용하세요.
|
|
237
292
|
|
|
238
|
-
`Transaction`은 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 데코레이터가 붙은 host에서 `this.db`, 직접 property, 중첩 `.db` property 순서로 transaction-capable 대상을 resolve
|
|
293
|
+
`Transaction`은 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 데코레이터가 붙은 host에서 `this.db`, 직접 property, 중첩 `.db` property 순서로 transaction-capable 대상을 resolve한 뒤, 후보가 없으면 데코레이터가 붙은 인스턴스 자체로 fallback합니다. 명시적 client 선택에는 accessor를 받을 수 있으며, 외부 경계에는 Drizzle transaction option을 전달할 수 있습니다.
|
|
239
294
|
|
|
240
295
|
### `DrizzleModule`
|
|
241
296
|
|
|
@@ -243,6 +298,7 @@ provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createP
|
|
|
243
298
|
- `forRootAsync(...)`는 database/dispose/transaction 설정을 factory에서 반환하는 DI-aware Drizzle 옵션을 받습니다. provider를 전역으로 노출해야 할 때는 최상위 async 등록 옵션에 `global`을 전달하세요.
|
|
244
299
|
- `forRootAsync(...)`는 애플리케이션 container마다 옵션을 한 번 resolve합니다. 테스트나 multi-app process에서 같은 module definition을 재사용해도 memoized factory result를 공유하지 않고 각 container가 독립적인 database/dispose 결과를 받습니다.
|
|
245
300
|
- `strictTransactions: true`를 설정하면 transaction 지원이 없는 database handle에서 예외를 던집니다.
|
|
301
|
+
- 추가 이름 있는 등록은 non-global입니다. Consumer는 일치하는 `getDrizzle*Token(name)`을 export하는 module을 import하고 해당 token으로 주입하며, 이름이 runtime container를 분리하지는 않습니다. 각 등록은 독립 ALS transaction context, drain, disposal, status를 소유하고 `@Transaction((self) => self.analytics)`로 명시적으로 선택합니다.
|
|
246
302
|
- sync 및 async 등록 모두에서 `database`는 실제 object/function handle이어야 하며, 누락된 handle은 모듈 등록 또는 async bootstrap 중 거부됩니다.
|
|
247
303
|
|
|
248
304
|
## 관련 패키지
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Node.js-only Drizzle ORM integration for fluo with a transaction-aware database
|
|
|
14
14
|
- [Service Transaction Boundary (@Transaction)](#service-transaction-boundary-transaction)
|
|
15
15
|
- [Manual Transactions and current()](#manual-transactions-and-current)
|
|
16
16
|
- [Request-Wide Controller Boundaries](#request-wide-controller-boundaries)
|
|
17
|
+
- [Named clients](#named-clients)
|
|
17
18
|
- [Shutdown and Status Contracts](#shutdown-and-status-contracts)
|
|
18
19
|
- [Manual Module Composition](#manual-module-composition)
|
|
19
20
|
- [Public API Overview](#public-api-overview)
|
|
@@ -23,14 +24,16 @@ Node.js-only Drizzle ORM integration for fluo with a transaction-aware database
|
|
|
23
24
|
## Installation
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
npm install @fluojs/drizzle drizzle-orm
|
|
27
|
+
npm install @fluojs/drizzle drizzle-orm@^0.45.2
|
|
27
28
|
# Install the driver for your Drizzle adapter as well, for example:
|
|
28
29
|
npm install pg
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
`@fluojs/drizzle` requires Drizzle ORM `>=0.45.2`. Consumers using an older Drizzle ORM release must upgrade the peer and refresh their lockfile before adopting this major `@fluojs/drizzle` release. The fluo integration API is unchanged, but applications should run their driver-specific query and migration tests against the upgraded ORM.
|
|
33
|
+
|
|
31
34
|
## Runtime Support
|
|
32
35
|
|
|
33
|
-
The root `@fluojs/drizzle` package
|
|
36
|
+
The root `@fluojs/drizzle` package requires Node.js `>=24.0.0 <27`. It imports Node's `node:async_hooks` module to maintain the ambient transaction context and its package manifest declares that package-owned support contract. Upgrade Node 20 and Node 22 hosts to Node.js `>=24.0.0 <27`; Node versions below 24 and Node 27+ are unsupported.
|
|
34
37
|
|
|
35
38
|
Drizzle ORM itself can target drivers such as Bun SQL or Cloudflare D1, but those driver runtimes are outside this fluo wrapper until a non-Node transaction-context adapter is documented.
|
|
36
39
|
|
|
@@ -38,14 +41,14 @@ Non-Node runtimes should not import the root package. For Bun, Deno, Cloudflare
|
|
|
38
41
|
|
|
39
42
|
## When to Use
|
|
40
43
|
|
|
41
|
-
- when
|
|
44
|
+
- when an application running Node.js `>=24.0.0 <27` needs Drizzle to participate in the same module, DI, and lifecycle model as the rest of the app
|
|
42
45
|
- when repositories need a single `current()` seam that switches between the root handle and the active transaction handle
|
|
43
46
|
- when application shutdown should also run an explicit cleanup hook for the underlying driver resources
|
|
44
47
|
|
|
45
48
|
## Quick Start
|
|
46
49
|
|
|
47
50
|
```ts
|
|
48
|
-
import { ConfigService } from '@fluojs/config';
|
|
51
|
+
import { ConfigModule, ConfigService } from '@fluojs/config';
|
|
49
52
|
import { Module } from '@fluojs/core';
|
|
50
53
|
import { DrizzleModule } from '@fluojs/drizzle';
|
|
51
54
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
@@ -53,6 +56,12 @@ import { Pool } from 'pg';
|
|
|
53
56
|
|
|
54
57
|
@Module({
|
|
55
58
|
imports: [
|
|
59
|
+
ConfigModule.forRoot({
|
|
60
|
+
global: true,
|
|
61
|
+
processEnv: {
|
|
62
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
56
65
|
DrizzleModule.forRootAsync({
|
|
57
66
|
inject: [ConfigService],
|
|
58
67
|
useFactory: async (config: ConfigService) => {
|
|
@@ -73,6 +82,8 @@ import { Pool } from 'pg';
|
|
|
73
82
|
export class AppModule {}
|
|
74
83
|
```
|
|
75
84
|
|
|
85
|
+
`forRootAsync(...)` accepts only `inject` and `useFactory` for its factory dependencies; it does not discover NestJS `imports`, `useClass`, `useExisting`, or decorator metadata. Its generated async module has no `imports`, so a token exported only by a sibling module or by a parent module's import is not visible to the options provider. Register factory dependencies through a global module instead. The `ConfigModule.forRoot(...)` registration above exports `ConfigService` globally by default; `global: true` is shown explicitly because that global export makes `ConfigService` visible to the generated async Drizzle module. For another token, make the module that owns and exports it global before bootstrap rather than relying on the importing application's `providers` or imports.
|
|
86
|
+
|
|
76
87
|
## Common Patterns
|
|
77
88
|
|
|
78
89
|
### Service Transaction Boundary (@Transaction)
|
|
@@ -80,41 +91,50 @@ export class AppModule {}
|
|
|
80
91
|
The `@Transaction()` decorator is the recommended way to define transaction boundaries in your service layer. It ensures that all repository calls made within the decorated method share the same Drizzle transaction.
|
|
81
92
|
|
|
82
93
|
```ts
|
|
94
|
+
import { Inject } from '@fluojs/core';
|
|
83
95
|
import { Transaction, DrizzleDatabase, type DrizzleDatabaseFacade } from '@fluojs/drizzle';
|
|
84
96
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
85
97
|
import { users, profiles } from './schema';
|
|
86
98
|
|
|
87
99
|
type AppDatabase = ReturnType<typeof drizzle>;
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
constructor(private readonly repo: UserRepository) {}
|
|
91
|
-
|
|
92
|
-
@Transaction()
|
|
93
|
-
async onboardUser(dto: any) {
|
|
94
|
-
const user = await this.repo.create(dto);
|
|
95
|
-
await this.repo.initProfile(user.id);
|
|
96
|
-
return user;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
101
|
+
@Inject(DrizzleDatabase)
|
|
100
102
|
export class UserRepository {
|
|
101
103
|
constructor(private readonly db: DrizzleDatabaseFacade<AppDatabase>) {}
|
|
102
104
|
|
|
103
105
|
async create(data: any) {
|
|
104
106
|
// The facade type exposes standard Drizzle methods.
|
|
105
107
|
// When called inside @Transaction(), they automatically participate in the ambient transaction.
|
|
106
|
-
|
|
108
|
+
const [user] = await this.db.insert(users).values(data).returning();
|
|
109
|
+
|
|
110
|
+
if (!user) {
|
|
111
|
+
throw new Error('User insert did not return a row.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return user;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
async initProfile(userId: string) {
|
|
110
118
|
return this.db.insert(profiles).values({ userId });
|
|
111
119
|
}
|
|
112
120
|
}
|
|
121
|
+
|
|
122
|
+
@Inject(UserRepository)
|
|
123
|
+
export class UserService {
|
|
124
|
+
constructor(private readonly repo: UserRepository) {}
|
|
125
|
+
|
|
126
|
+
@Transaction()
|
|
127
|
+
async onboardUser(dto: any) {
|
|
128
|
+
const user = await this.repo.create(dto);
|
|
129
|
+
await this.repo.initProfile(user.id);
|
|
130
|
+
return user;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
113
133
|
```
|
|
114
134
|
|
|
115
135
|
Calls to `@Transaction()` methods are reentrant. If a decorated method calls another decorated method, they share the same underlying Drizzle transaction.
|
|
116
136
|
|
|
117
|
-
By default, `@Transaction()` selects its target with a small host-object heuristic: it first checks `this.db`, then direct properties on the decorated instance, then a nested `.db` property on those values, and uses the first value that exposes a `transaction(...)` method. This keeps common `constructor(private readonly db: DrizzleDatabase<...>)` services concise, but services with more than one Drizzle wrapper should not rely on property order. Pass an explicit accessor such as `@Transaction((self) => self.ordersDb)` or `@Transaction((self) => self.analyticsDb, options)` whenever the decorated host owns multiple transaction-capable clients or wraps a repository that also exposes `.db`.
|
|
137
|
+
By default, `@Transaction()` selects its target with a small host-object heuristic: it first checks `this.db`, then direct properties on the decorated instance, then a nested `.db` property on those values, and uses the first value that exposes a `transaction(...)` method. If none of those candidates match, the decorated instance itself becomes the transaction target. This keeps common `constructor(private readonly db: DrizzleDatabase<...>)` services and self-contained facade hosts concise, but services with more than one Drizzle wrapper should not rely on property order. Pass an explicit accessor such as `@Transaction((self) => self.ordersDb)` or `@Transaction((self) => self.analyticsDb, options)` whenever the decorated host owns multiple transaction-capable clients or wraps a repository that also exposes `.db`.
|
|
118
138
|
|
|
119
139
|
### Manual Transactions and current()
|
|
120
140
|
|
|
@@ -152,20 +172,25 @@ await this.db.transaction(async () => {
|
|
|
152
172
|
|
|
153
173
|
Nested calls reuse the active transaction boundary. If a nested call passes transaction options while a boundary is already active, the package rejects those nested options instead of silently changing the existing transaction.
|
|
154
174
|
|
|
155
|
-
When `database.transaction(...)` is unavailable and `strictTransactions` is `false` (the default), `transaction()` and `requestTransaction()` intentionally fail open (fail-open fallback) by running the callback directly against the root handle. This is useful for local fakes, read-only adapters, or gradual migrations, but it is not atomic and should not be treated as a real database transaction. Set `strictTransactions: true` in production paths that require rollback guarantees; startup and readiness diagnostics then surface missing `database.transaction(...)` support and transaction helpers throw instead of silently running without a transaction.
|
|
175
|
+
When `database.transaction(...)` is unavailable and `strictTransactions` is `false` (the default), `transaction()` and `requestTransaction()` intentionally fail open (fail-open fallback) by running the callback directly against the root handle. This is useful for local fakes, read-only adapters, or gradual migrations, but it is not atomic and should not be treated as a real database transaction. Set `strictTransactions: true` in production paths that require rollback guarantees; startup and readiness diagnostics then surface missing `database.transaction(...)` support and transaction helpers throw instead of silently running without a transaction. Fail-open callbacks still run in a root-handle ALS context, so nested helpers reuse the fallback boundary, nested request work inherits the ambient request `AbortSignal`, and shutdown drains nested direct execution before disposal. This context preservation does not add rollback atomicity.
|
|
176
|
+
|
|
177
|
+
Async work created inside a transaction can inherit its ALS context even when it runs after the owning transaction has committed, rolled back, or otherwise settled. A later `transaction(...)` or `requestTransaction(...)` call from that inherited continuation is treated as a fresh lifecycle-tracked root instead of reusing the closed transaction handle. Shutdown drains that fresh root before `dispose(database)`, while calls that begin before the owner settles continue to share the active boundary.
|
|
156
178
|
|
|
157
179
|
### Request-Wide Controller Boundaries
|
|
158
180
|
|
|
159
181
|
Prefer service-level `@Transaction()` for business operations. If you are migrating a NestJS controller/interceptor pattern where an entire request must be transactional, call `requestTransaction(...)` explicitly at the controller, route adapter, or request orchestration boundary and pass the request `AbortSignal` when one is available:
|
|
160
182
|
|
|
161
183
|
```ts
|
|
162
|
-
import {
|
|
184
|
+
import { Inject } from '@fluojs/core';
|
|
185
|
+
import { Controller, Post, type RequestContext } from '@fluojs/http';
|
|
163
186
|
import { DrizzleDatabase } from '@fluojs/drizzle';
|
|
164
187
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
188
|
+
import { CheckoutService } from './checkout.service';
|
|
165
189
|
|
|
166
190
|
type AppDatabase = ReturnType<typeof drizzle>;
|
|
167
191
|
|
|
168
192
|
@Controller('/checkout')
|
|
193
|
+
@Inject(DrizzleDatabase, CheckoutService)
|
|
169
194
|
export class CheckoutController {
|
|
170
195
|
constructor(
|
|
171
196
|
private readonly db: DrizzleDatabase<AppDatabase>,
|
|
@@ -173,20 +198,46 @@ export class CheckoutController {
|
|
|
173
198
|
) {}
|
|
174
199
|
|
|
175
200
|
@Post()
|
|
176
|
-
create(input: CheckoutInput,
|
|
201
|
+
create(input: CheckoutInput, context: RequestContext) {
|
|
177
202
|
return this.db.requestTransaction(
|
|
178
203
|
() => this.checkout.createOrder(input),
|
|
179
|
-
|
|
204
|
+
context.request.signal,
|
|
180
205
|
);
|
|
181
206
|
}
|
|
182
207
|
}
|
|
183
208
|
```
|
|
184
209
|
|
|
185
|
-
|
|
210
|
+
`DrizzleTransactionInterceptor` is a deprecated 1.x compatibility bridge for existing NestJS interceptor imports. It delegates to `requestTransaction(...)` and forwards the request `AbortSignal`. New code should move business transaction boundaries to services and reserve explicit `requestTransaction(...)` for rare controller-level cases where all request work, not just a service method, must share the same boundary. Decorating a controller method with `@Transaction()` remains a compatibility path when the controller owns an explicit `DrizzleDatabase` target, but `requestTransaction(...)` is the clearer request-wide API because it can receive the request `AbortSignal` directly.
|
|
211
|
+
|
|
212
|
+
### Named clients
|
|
213
|
+
|
|
214
|
+
Register each additional client with a non-empty `name` and inject its package-owned token instead of the `DrizzleDatabase` class token:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const ANALYTICS_DRIZZLE = getDrizzleHandleProviderToken('analytics');
|
|
218
|
+
|
|
219
|
+
DrizzleModule.forRoot({ database: primaryDatabase });
|
|
220
|
+
DrizzleModule.forRoot({ database: analyticsDatabase, name: 'analytics' });
|
|
221
|
+
|
|
222
|
+
@Inject(ANALYTICS_DRIZZLE)
|
|
223
|
+
class AnalyticsService {
|
|
224
|
+
constructor(private readonly analytics: DrizzleDatabase<AnalyticsDatabase>) {}
|
|
225
|
+
|
|
226
|
+
@Transaction((self: AnalyticsService) => self.analytics)
|
|
227
|
+
async rebuild() {}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`getDrizzleDatabaseToken`, `getDrizzleDisposeToken`, `getDrizzleOptionsToken`, and
|
|
232
|
+
`getDrizzleHandleProviderToken` return distinct stable identities for each trimmed name. Named clients are
|
|
233
|
+
non-global and independently own ALS transaction context, shutdown drain, disposal, and status. A consumer must import
|
|
234
|
+
a module that exports the matching named token; names do not create isolated runtime containers. Omitting `name`
|
|
235
|
+
preserves the existing default tokens, `DrizzleDatabase` class token, and interceptor behavior.
|
|
186
236
|
|
|
187
237
|
### Shutdown and status contracts
|
|
188
238
|
|
|
189
|
-
During application shutdown, `DrizzleDatabase` aborts any still-active request transaction, waits for open request and manual transaction callbacks to settle or roll back, and only then runs the optional `dispose(database)` hook. This
|
|
239
|
+
During application shutdown, `DrizzleDatabase` aborts any still-active request transaction, waits for open request and manual transaction callbacks to settle or roll back, and only then runs the optional `dispose(database)` hook. This includes fail-open manual `transaction(...)` callbacks when `database.transaction(...)` is unavailable and `strictTransactions` is `false`, so direct-execution fallbacks still drain before pools or externally managed resources are closed.
|
|
240
|
+
Transaction continuations that start a new boundary after their inherited owner settles no longer reuse the closed transaction handle. They become independently tracked roots, and shutdown waits for those continuation roots before disposal.
|
|
190
241
|
Nested `requestTransaction(...)` calls opened inside an existing request boundary observe the ambient request abort signal while still reusing the active Drizzle transaction. Nested `requestTransaction(...)` calls opened inside an existing manual transaction boundary also join shutdown settlement tracking without opening a second Drizzle transaction, and their settlement handle remains tracked until the outer manual transaction settles so shutdown drains that outer boundary before `dispose(database)` runs. The platform status activity count is intentionally shorter lived: once the nested request callback settles, `details.activeRequestTransactions` is decremented even if the outer manual transaction continues running.
|
|
191
242
|
New `transaction(...)` and `requestTransaction(...)` calls are rejected once shutdown begins, so disposal cannot overtake a late transaction that starts after the shutdown boundary is crossed.
|
|
192
243
|
If the request signal aborts after the request callback has completed but before the underlying Drizzle transaction runner finishes committing or rolling back, `requestTransaction(...)` waits for that runner to settle first and then rejects with the abort reason. This keeps Drizzle cleanup serialized with request cancellation while making the late request abort visible to the caller instead of returning the completed callback result.
|
|
@@ -223,8 +274,10 @@ defineModule(ManualDrizzleModule, {
|
|
|
223
274
|
- `DrizzleModule.forRoot(options)` / `DrizzleModule.forRootAsync(options)`
|
|
224
275
|
- `DrizzleDatabase`
|
|
225
276
|
- `DrizzleDatabaseFacade<TDatabase>`
|
|
277
|
+
- `DrizzleTransactionInterceptor` (deprecated 1.x request-transaction compatibility bridge)
|
|
226
278
|
- `Transaction`
|
|
227
279
|
- `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_HANDLE_PROVIDER`, `DRIZZLE_OPTIONS`
|
|
280
|
+
- `getDrizzleDatabaseToken(name?)`, `getDrizzleDisposeToken(name?)`, `getDrizzleHandleProviderToken(name?)`, `getDrizzleOptionsToken(name?)`
|
|
228
281
|
- `DrizzleDatabase.createFacade(...)` (compatibility-only provider wiring helper; prefer `DrizzleModule.forRoot(...)` / `forRootAsync(...)` for application registration)
|
|
229
282
|
- `createDrizzlePlatformStatusSnapshot(...)`
|
|
230
283
|
- `DrizzleDatabaseLike`
|
|
@@ -233,9 +286,11 @@ defineModule(ManualDrizzleModule, {
|
|
|
233
286
|
|
|
234
287
|
`DRIZZLE_HANDLE_PROVIDER` is an alias token for the lifecycle-aware `DrizzleDatabase` wrapper. Health integrations such as `@fluojs/terminus` use this token to read `createPlatformStatusSnapshot()` before falling back to raw database pings.
|
|
235
288
|
|
|
289
|
+
`DrizzleModule` exports `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, and `DRIZZLE_OPTIONS` for importing modules. `DRIZZLE_DATABASE` injects the configured raw Drizzle handle, so it bypasses the lifecycle-aware facade and ambient transaction-handle selection. Prefer `DrizzleDatabase` or `DrizzleDatabaseFacade` for application repositories; inject the raw token only for integrations that require the configured driver handle. `DRIZZLE_DISPOSE` exposes the configured optional cleanup hook, and `DRIZZLE_OPTIONS` exposes normalized runtime options.
|
|
290
|
+
|
|
236
291
|
Use `DrizzleDatabase<TDatabase>` when a provider only needs wrapper methods such as `current()`, `transaction(...)`, `requestTransaction(...)`, or `createPlatformStatusSnapshot()`. Use `DrizzleDatabaseFacade<TDatabase>` for repository injections that call Drizzle query methods directly; the facade forwards those calls to the active transaction handle when one exists and to the root handle otherwise. `DrizzleDatabase.createFacade(...)` is retained as a low-level compatibility helper for module-provider wiring; application code should prefer `DrizzleModule.forRoot(...)` / `forRootAsync(...)`.
|
|
237
292
|
|
|
238
|
-
`Transaction` is a standard TC39 method decorator for service-layer transaction boundaries. It resolves a transaction-capable target from the decorated host by checking `this.db`, then direct properties, then nested `.db` properties, accepts an accessor for explicit client selection
|
|
293
|
+
`Transaction` is a standard TC39 method decorator for service-layer transaction boundaries. It resolves a transaction-capable target from the decorated host by checking `this.db`, then direct properties, then nested `.db` properties, then falling back to the decorated instance itself; it also accepts an accessor for explicit client selection and can forward Drizzle transaction options to the outer boundary.
|
|
239
294
|
|
|
240
295
|
### `DrizzleModule`
|
|
241
296
|
|
|
@@ -243,6 +298,7 @@ Use `DrizzleDatabase<TDatabase>` when a provider only needs wrapper methods such
|
|
|
243
298
|
- `forRootAsync(...)` accepts DI-aware Drizzle options whose factory returns the database/dispose/transaction settings; pass `global` on the top-level async registration when the providers should be visible globally.
|
|
244
299
|
- `forRootAsync(...)` resolves options once per application container. Reusing the same module definition across tests or multi-app processes creates isolated database/dispose results for each container instead of sharing a memoized factory result.
|
|
245
300
|
- Supports `strictTransactions: true` to throw if transaction support is missing.
|
|
301
|
+
- Additional named registrations are non-global. Consumers import a module that exports the matching `getDrizzle*Token(name)` and inject through that token; names do not create isolated runtime containers. Each registration owns independent ALS transaction context, drain, disposal, and status; select it explicitly with `@Transaction((self) => self.analytics)`.
|
|
246
302
|
- `database` must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
|
|
247
303
|
|
|
248
304
|
## Related Packages
|
package/dist/database.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export declare class DrizzleDatabase<TDatabase extends DrizzleDatabaseLike<TTran
|
|
|
26
26
|
* @remarks
|
|
27
27
|
* This compatibility helper is used by `DrizzleModule` provider wiring. Application code should prefer
|
|
28
28
|
* `DrizzleModule.forRoot(...)` or `DrizzleModule.forRootAsync(...)`, then type injected repository handles as
|
|
29
|
-
* `DrizzleDatabaseFacade<TDatabase>` when direct Drizzle methods are needed.
|
|
29
|
+
* `DrizzleDatabaseFacade<TDatabase>` when direct Drizzle methods are needed. Wrapper and lifecycle methods remain
|
|
30
|
+
* bound to the lifecycle owner while unknown Drizzle query properties forward to the ambient `current()` handle.
|
|
30
31
|
*
|
|
31
32
|
* @param database Root Drizzle database handle registered in the module.
|
|
32
33
|
* @param dispose Optional shutdown hook used to close pools or driver resources.
|
|
@@ -79,9 +80,14 @@ export declare class DrizzleDatabase<TDatabase extends DrizzleDatabaseLike<TTran
|
|
|
79
80
|
*/
|
|
80
81
|
requestTransaction<T>(fn: () => Promise<T>, signal?: AbortSignal, options?: TTransactionOptions): Promise<T>;
|
|
81
82
|
private executeTransaction;
|
|
83
|
+
private executeManualRootTransaction;
|
|
82
84
|
private executeRequestTransaction;
|
|
85
|
+
private executeRequestRootTransaction;
|
|
86
|
+
private executeInheritedRequestTransaction;
|
|
87
|
+
private executeNestedManualTransaction;
|
|
83
88
|
private executeNestedRequestTransaction;
|
|
84
89
|
private executeRequestFallback;
|
|
90
|
+
private closeTransactionBoundaryOwner;
|
|
85
91
|
private assertRequestTransactionsAvailable;
|
|
86
92
|
private assertTransactionsAvailable;
|
|
87
93
|
private throwIfRequestAborted;
|
|
@@ -89,6 +95,7 @@ export declare class DrizzleDatabase<TDatabase extends DrizzleDatabaseLike<TTran
|
|
|
89
95
|
private untrackActiveRequestTransaction;
|
|
90
96
|
private markRequestTransactionInactiveForStatus;
|
|
91
97
|
private trackActiveTransactionScope;
|
|
98
|
+
private trackAvailableTransactionScope;
|
|
92
99
|
private resolveTransactionRunner;
|
|
93
100
|
}
|
|
94
101
|
/**
|
package/dist/database.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAU7D,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAgCpB,KAAK,qBAAqB,GAAG;IAC3B,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CAAC;AAkFF;;;;;;GAMG;AACH,qBACa,eAAe,CAC1B,SAAS,SAAS,mBAAmB,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,EAChF,oBAAoB,GAAG,SAAS,EAChC,mBAAmB,GAAG,OAAO,CAC7B,YAAW,qBAAqB,CAAC,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,EAAE,qBAAqB;IAS3G,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe;IATlC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgF;IAC7G,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqC;IAC7E,OAAO,CAAC,mCAAmC,CAAK;IAChD,OAAO,CAAC,cAAc,CAAkD;gBAGrD,QAAQ,EAAE,SAAS,EACnB,OAAO,CAAC,GAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,aAAA,EACvD,eAAe,GAAE,qBAAqD;IAGzF;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,YAAY,CACjB,SAAS,SAAS,mBAAmB,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,EAChF,oBAAoB,GAAG,SAAS,EAChC,mBAAmB,GAAG,OAAO,EAE7B,QAAQ,EAAE,SAAS,EACnB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACvD,eAAe,GAAE,qBAAqD,GACrE,qBAAqB,CAAC,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,CAAC;IAM9E;;;;;;;;;OASG;IACH,OAAO,IAAI,SAAS,GAAG,oBAAoB;IAU3C,qGAAqG;IAC/F,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5C,yFAAyF;IACzF,4BAA4B;IAS5B;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrF;;;;;;;;;;;;OAYG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;YAIpG,kBAAkB;YAuClB,4BAA4B;YA8D5B,yBAAyB;YA8CzB,6BAA6B;YAc7B,kCAAkC;YAsBlC,8BAA8B;YAqB9B,+BAA+B;YAuF/B,sBAAsB;YAsDtB,6BAA6B;IAQ3C,OAAO,CAAC,kCAAkC;IAM1C,OAAO,CAAC,2BAA2B;IAMnC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,6BAA6B;IAOrC,OAAO,CAAC,+BAA+B;IAKvC,OAAO,CAAC,uCAAuC;IAO/C,OAAO,CAAC,2BAA2B;IAkBnC,OAAO,CAAC,8BAA8B;IAMtC,OAAO,CAAC,wBAAwB;CAWjC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,CAC/B,SAAS,SAAS,mBAAmB,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,EAChF,oBAAoB,GAAG,SAAS,EAChC,mBAAmB,GAAG,OAAO,IAC3B,eAAe,CAAC,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,GACvE,IAAI,CAAC,SAAS,EAAE,MAAM,eAAe,CAAC,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,CAAC,CAAC"}
|