@fluojs/mongoose 1.0.5 → 1.1.1

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 CHANGED
@@ -11,6 +11,9 @@
11
11
  - [빠른 시작](#빠른-시작)
12
12
  - [라이프사이클과 종료](#라이프사이클과-종료)
13
13
  - [공통 패턴](#공통-패턴)
14
+ - [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
15
+ - [요청 트랜잭션 인터셉터 호환성](#요청-트랜잭션-인터셉터-호환성)
16
+ - [수동 트랜잭션과 currentSession()](#수동-트랜잭션과-currentsession)
14
17
  - [공개 API](#공개-api)
15
18
  - [관련 패키지](#관련-패키지)
16
19
  - [예제 소스](#예제-소스)
@@ -26,7 +29,10 @@ pnpm add mongoose
26
29
 
27
30
  - Mongoose를 나머지 애플리케이션과 같은 DI 및 라이프사이클 모델에 연결하고 싶을 때.
28
31
  - 모든 서비스에서 MongoDB 세션과 트랜잭션을 임시 배관 코드 없이 하나의 wrapper로 다루고 싶을 때.
29
- - 요청 범위 트랜잭션을 interceptor로 명시적으로 켜고 싶을 때.
32
+ - 요청 단위 트랜잭션에 명시적 `requestTransaction(...)` 경계가 필요할 때.
33
+ - 애플리케이션이 이미 concrete Mongoose connection을 생성·구성하고 있고, fluo가 그 ownership을 대체하지 않고 관측하기를 원할 때.
34
+
35
+ Root `@fluojs/mongoose` wrapper는 ambient transaction context에 Node.js `node:async_hooks`를 사용하며, package manifest의 `engines.node >=20.0.0`과 동일하게 Node.js 20 이상을 지원합니다. 비 Node 런타임에서는 runtime-specific transaction-context adapter가 문서화되기 전까지 root wrapper를 import하지 말고 raw Mongoose-compatible handle을 애플리케이션 소유 provider 뒤에 등록하세요.
30
36
 
31
37
  ## 빠른 시작
32
38
 
@@ -54,78 +60,129 @@ class AppModule {}
54
60
 
55
61
  ## 라이프사이클과 종료
56
62
 
57
- `MongooseModule`은 `MongooseConnection`을 fluo 애플리케이션 라이프사이클에 등록합니다. 이 패키지는 원본 Mongoose 연결을 직접 생성하거나 소유하지 않습니다. 애플리케이션 종료 시 외부 연결을 닫아야 한다면 `dispose` 훅을 전달하세요.
63
+ `MongooseModule`은 `MongooseConnection`을 fluo 애플리케이션 라이프사이클에 등록합니다. 이 패키지는 원본 Mongoose 연결을 직접 생성하거나 소유하지 않습니다. `connection`에는 concrete Mongoose connection object/function을 전달하고, 연결 문자열, pool, plugin, model compilation ownership은 애플리케이션에 남겨두며, 애플리케이션 종료 시 외부 연결을 닫아야 한다면 `dispose` 훅을 전달하세요.
58
64
 
59
- 종료 과정은 트랜잭션 정리 순서를 보존하며, 종료가 시작된 뒤에는 수동 또는 요청 범위 트랜잭션 경계를 거부합니다.
65
+ 종료 절차는 트랜잭션 정리 순서를 보존하고, 종료가 시작된 뒤에는 새로운 수동 또는 요청 단위 트랜잭션 경계를 거부합니다.
60
66
 
61
- 1. 열려 있는 요청 범위 트랜잭션은 `Application shutdown interrupted an open request transaction.` 오류로 abort됩니다.
62
- 2. 활성 ambient session transaction callback session cleanup이 settle될 때까지 추적됩니다.
63
- 3. 해당 Mongoose 세션은 `abortTransaction()`과 `endSession()` 정리를 끝냅니다.
67
+ 1. 열린 요청 단위 트랜잭션은 `Application shutdown interrupted an open request transaction.`으로 abort됩니다.
68
+ 2. 활성 ambient session, 원본 request callback, fail-open 직접 실행 transaction callback 작업이 settle될 때까지 추적됩니다.
69
+ 3. 해당 Mongoose 세션은 시작된 callback이 settle된 뒤에만 `abortTransaction()`과 `endSession()` 정리를 끝냅니다.
64
70
  4. 설정한 `dispose(connection)` 훅은 활성 요청 트랜잭션과 ambient session scope가 모두 settled된 뒤에만 실행됩니다.
65
71
 
66
- `createMongoosePlatformStatusSnapshot(...)`은 트래픽 처리 중에는 `ready`, 요청 트랜잭션 drain 중에는 `shutting-down`, dispose 완료 뒤에는 `stopped`를 보고합니다. 상태 details에는 `sessionStrategy`, `transactionContext: 'als'`, 활성 요청/session 개수, 리소스 소유권, strict/session 지원 진단이 포함됩니다. 수동 `transaction()`도 요청 범위 트랜잭션과 같은 명시적 세션 계약을 사용하므로, 트랜잭션에 참여해야 하는 Mongoose 모델 작업에는 repository 코드가 `conn.currentSession()`을 전달해야 합니다. 감싼 Mongoose 연결이 `connection.transaction(...)`을 노출하면 fluo는 Mongoose 자체 ambient-session scope를 보존하기 위해 그 API에 transaction boundary를 위임하면서도 같은 session `currentSession()`으로 노출합니다. 요청 범위 트랜잭션은 session획득하는 동안과 위임된 `connection.transaction(...)` 작업을 시작하는 동안에도 request `AbortSignal`을 관찰하므로, 요청 취소가 사용자 callback 실행 전에 이러한 시작 단계를 중단할 수 있습니다.
72
+ Request cancellation 또는 shutdown이 callback 시작 boundary를 abort하면, boundary는 abort 결과를 보존하되 원본 callback이 settle될 때까지 기다린 다음 rollback, session 종료, connection dispose를 진행합니다. 따라서 ALS-backed 작업이 이미 정리된 session이나 connection사용하며 계속 실행되지 않습니다.
73
+
74
+ `MongooseConnection.createPlatformStatusSnapshot()`과 export된 low-level `createMongoosePlatformStatusSnapshot(...)` helper는 serving 중에는 `ready`, 요청 트랜잭션을 drain하는 shutdown 중에는 `shutting-down`, dispose hook 완료 후에는 `stopped`를 보고합니다. status details에는 `sessionStrategy`, `transactionContext: 'als'`, 활성 요청/세션 수, 리소스 소유권, strict/session 지원 진단이 포함됩니다. 수동 `transaction()` 호출과 서비스 `@Transaction()` 메서드는 같은 ambient session을 `conn.model(...)`에 노출합니다. 지원되는 facade 메서드(`create`, `find`, `findOne`, `aggregate`, `bulkWrite`)는 해당 세션을 자동으로 첨부합니다. 자동 세션 주입은 `MongooseConnection.model(...)` wrapper 메서드에만 scope되며, `conn.current()`가 반환하는 raw `connection.model(...)` cache/compile 경로를 교체하거나 변형하지 않습니다. 지원되지 않는 model 메서드, `doc.save()`, 외부 유틸리티에 명시적 세션 배관이 필요할 때는 `conn.currentSession()`을 사용하세요. 래핑된 Mongoose connection이 `connection.transaction(...)`을 제공하면 fluo는 Mongoose 자체 ambient-session scope를 보존하면서 동일한 세션을 `currentSession()`으로 노출하도록 해당 API에 트랜잭션 경계를 위임합니다. 요청 단위 트랜잭션은 세션을 획득하는 동안과 위임된 `connection.transaction(...)` 작업을 시작하는 동안 request `AbortSignal`을 관찰하므로, request cancellation은 사용자 callback이 실행되기 전의 startup phase를 중단할 수 있습니다.
75
+
67
76
  기존 수동 `transaction(...)` boundary 안에서 열린 중첩 `requestTransaction(...)` 호출은 ambient session을 재사용하고 `details.activeRequestTransactions`에 계속 표시되며, 종료 중에 abort되어 바깥 수동 transaction이 `dispose(connection)` 실행 전에 rollback할 수 있습니다.
68
77
 
69
78
  ## 공통 패턴
70
79
 
71
- ### `MongooseConnection`을 통한 연결 접근
80
+ ### 서비스 트랜잭션 경계 (@Transaction)
72
81
 
73
- `MongooseConnection` 래퍼는 기본 Mongoose 연결에 대한 접근을 제공합니다.
82
+ `@Transaction()` 데코레이터는 서비스 레이어에서 트랜잭션 경계를 정의하는 권장 방법입니다. 이 데코레이터가 적용된 메서드 내부에서 발생하는 모든 리포지토리 호출은 동일한 MongoDB 세션을 공유합니다.
74
83
 
75
- ```typescript
76
- import { MongooseConnection } from '@fluojs/mongoose';
84
+ ```ts
85
+ import { MongooseConnection, Transaction, type MongooseModelFacade } from '@fluojs/mongoose';
86
+
87
+ type UserDocument = { readonly _id: string; readonly name: string };
88
+ type UserCreateModel = MongooseModelFacade<Promise<readonly [UserDocument]>>;
89
+ type ProfileCreateModel = MongooseModelFacade<Promise<readonly { readonly userId: string }[]>>;
90
+
91
+ export class UserService {
92
+ constructor(private readonly repo: UserRepository) {}
93
+
94
+ @Transaction()
95
+ async onboardUser(dto: CreateUserDto) {
96
+ const [user] = await this.repo.create(dto);
97
+ await this.repo.initProfile(user._id);
98
+ return user;
99
+ }
100
+ }
77
101
 
78
102
  export class UserRepository {
79
103
  constructor(private readonly conn: MongooseConnection) {}
80
104
 
81
- async findById(id: string) {
82
- const User = this.conn.current().model('User');
83
- return User.findById(id);
105
+ async create(data: CreateUserDto) {
106
+ // @Transaction() 내부에서 conn.model()은 세션 인지형 facade를 반환합니다.
107
+ // create, find, findOne, aggregate, bulkWrite 등의 작업은
108
+ // 자동으로 활성 트랜잭션에 참여합니다.
109
+ return this.conn.model<UserCreateModel>('User').create([data]);
110
+ }
111
+
112
+ async initProfile(userId: string) {
113
+ return this.conn.model<ProfileCreateModel>('Profile').create([{ userId }]);
84
114
  }
85
115
  }
86
116
  ```
87
117
 
88
- ### 수동 트랜잭션과 세션
118
+ `@Transaction()` 메서드 호출은 재진입(reentrant)이 가능합니다. 데코레이터가 적용된 메서드가 다른 데코레이터 적용 메서드를 호출하더라도 하나의 동일한 MongoDB 세션 안에서 실행됩니다. 참고로 v1에서 `doc.save()`는 자동으로 세션을 주입하지 않으므로, 자동 트랜잭션 참여가 필요하다면 지원되는 facade 작업(`model.create()`, `model.find()`, `model.findOne()`, `model.aggregate()`, `model.bulkWrite()`)을 사용하세요.
89
119
 
90
- `conn.transaction()`으로 세션 경계를 만들고, Mongoose 모델 작업에는 세션을 명시적으로 전달합니다.
120
+ ### 요청 트랜잭션 인터셉터 호환성
91
121
 
92
- ```typescript
93
- await this.conn.transaction(async () => {
94
- const session = this.conn.currentSession();
95
- const User = this.conn.current().model('User');
96
-
97
- // 작업에 세션을 명시적으로 전달
98
- await User.create([{ name: 'Ada' }], { session });
99
- });
122
+ `MongooseTransactionInterceptor`는 기존 request-wide `@UseInterceptors(...)` boundary를 위한 deprecated 1.x 호환성 export로 복원되었습니다. `MongooseModule.forRoot(...)`와 `forRootAsync(...)`가 이 interceptor를 provider 및 export로 제공하며, `MongooseConnection.requestTransaction(...)`에 위임하고 request `AbortSignal`을 전달합니다.
123
+
124
+ ```ts
125
+ import { Controller, Post, UseInterceptors } from '@fluojs/http';
126
+ import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
127
+
128
+ @Controller('/orders')
129
+ export class OrdersController {
130
+ @Post('/')
131
+ @UseInterceptors(MongooseTransactionInterceptor)
132
+ createOrder() {
133
+ return this.orders.create();
134
+ }
135
+ }
100
136
  ```
101
137
 
102
- 감싼 연결이 `connection.transaction(...)`을 구현하면 `startSession()`이 직접 노출되지 않아도 fluo는 이를 strict transaction boundary로 취급합니다. 그렇지 않고 연결이 `startSession()`을 구현하지 않으면 트랜잭션은 기본적으로 직접 실행으로 fallback합니다. fallback 대신 예외를 던지려면 `strictTransactions: true`를 설정합니다. 이때 오류 메시지는 `Transaction not supported: Mongoose connection does not implement startSession.`입니다.
138
+ 비즈니스 작업에는 서비스 계층 `@Transaction()`을 우선 사용하세요. 기존 request-wide boundary를 migration하는 동안에만 interceptor를 유지하고, request orchestration에서 경계를 명시해야 한다면 `requestTransaction(...)` 직접 호출로 교체하세요.
103
139
 
104
- Fluo는 Mongoose operation option을 다시 쓰지 않습니다. 모델 호출이 명시적인 `{ session }`을 전달하면 그 option은 그대로 유지되며, 생략한 경우 fluo가 session을 자동 부착한다고 가정하면 안 됩니다. 같은 session에서 병렬 작업이나 중첩 transaction 기대치는 보수적으로 유지하세요. 중첩된 `MongooseConnection.transaction(...)` 호출은 같은 session에 두 번째 MongoDB transaction을 여는 대신 활성 boundary를 재사용합니다.
140
+ ### 수동 트랜잭션과 currentSession()
105
141
 
106
- ### 요청 범위 트랜잭션
142
+ `MongooseConnection`은 활성 MongoDB 세션에 접근하기 위한 `currentSession()`과 루트 연결 handle에 접근하기 위한 `current()` 메서드를 제공합니다. 외부 유틸리티에 세션을 전달하거나 복잡한 수동 처리가 필요한 경우 escape hatch로 사용하세요.
107
143
 
108
- 컨트롤러나 메서드에 `MongooseTransactionInterceptor`를 적용하면 전체 요청을 MongoDB 세션으로 감쌉니다.
144
+ ```ts
145
+ import { MongooseConnection } from '@fluojs/mongoose';
109
146
 
110
- ```typescript
111
- import { UseInterceptors } from '@fluojs/http';
112
- import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
147
+ export class AdvancedRepository {
148
+ constructor(private readonly conn: MongooseConnection) {}
113
149
 
114
- @UseInterceptors(MongooseTransactionInterceptor)
115
- class UserController {}
150
+ async customOperation() {
151
+ const session = this.conn.currentSession();
152
+ const User = this.conn.current().model('User');
153
+
154
+ // 명시적으로 세션 전달
155
+ return User.find({ status: 'active' }).session(session || null);
156
+ }
157
+ }
158
+ ```
159
+
160
+ 수동 트랜잭션 블록에는 `conn.transaction()`을 사용하세요:
161
+
162
+ ```ts
163
+ await this.conn.transaction(async () => {
164
+ const User = this.conn.model('User');
165
+ await User.create([{ name: 'Ada' }]);
166
+ });
116
167
  ```
117
168
 
118
- HTTP interceptor 밖에서 같은 request-aware transaction boundary가 필요하다면 `MongooseConnection.requestTransaction(...)`을 직접 사용할 있습니다. 중첩된 service transaction은 활성 session boundary를 재사용하며, 수동 transaction 안에서 열린 중첩 request boundaryrequest abort와 shutdown tracking에 참여합니다.
169
+ 래핑된 연결이 `connection.transaction(...)`을 구현하고 있다면 fluo는 이를 엄격한 트랜잭션 경계로 취급합니다. 그렇지 않고 `startSession()`이 없는 경우 트랜잭션은 기본값(`strictTransactions: false`)에서 callback 직접 실행으로 fail-open합니다. 모드는 local fake나 staged migration에는 유용하지만 rollback 원자성은 제공하지 않습니다. 열린 fail-open 수동 `transaction(...)` callback종료 중에 drain되므로 `dispose(connection)`은 해당 callback이 settle된 뒤 실행됩니다. MongoDB transaction 보장이 필요한 production 흐름에서는 `strictTransactions: true`를 설정하세요. 그러면 transaction 지원 누락이 readiness `not-ready`와 helper 예외로 드러납니다.
170
+
171
+ 지원되는 facade 메서드에서 fluo는 기존 Mongoose 작업 옵션을 보존하고 올바른 options 인자에 ambient `{ session }`만 병합합니다. `create(...)`는 Mongoose의 array overload인 `create([docs], options?)`를 통해서만 session을 주입합니다. Positional `create(docA, docB)` 인자는 마지막 문서에 `timestamps` 같은 option-like field가 있어도 그대로 전달되며 자동 session 주입을 받지 않습니다. 트랜잭션 참여가 필요하면 array overload를 사용하세요. 활성 트랜잭션 내부에서 명시적으로 `{ session: null }`을 전달하거나 다른 세션 객체를 사용하면, `findOne(filter, projection, options)`의 세 번째 options 인자를 포함해 의도치 않은 트랜잭션 탈출을 방지하는 세션 충돌 에러를 발생시킵니다. Repository code에서 typed operation result가 필요하면 result-specialized `MongooseModelFacade`를 `model<TModel>(...)` 타입 인자로 전달하세요.
119
172
 
120
173
  ## 공개 API
121
174
 
122
175
  - `MongooseModule.forRoot(options)` / `MongooseModule.forRootAsync(options)`
123
176
  - `MongooseConnection`
124
- - `MongooseTransactionInterceptor`
177
+ - `MongooseConnection.createPlatformStatusSnapshot()` — platform observability surface를 위해 health/readiness, resource ownership, 활성 request/session drain 수, strict transaction 지원 진단을 보고합니다.
178
+ - `MongooseConnection.model<TModel>(name, ...args)` — 트랜잭션 밖에서는 callable하고 result-specializable한 `MongooseModelFacade`를 반환하고, 활성 트랜잭션 안에서는 underlying Mongoose connection을 변형하지 않으면서 `create`, `find`, `findOne`, `aggregate`, `bulkWrite`에 세션을 주입하는 버전을 반환합니다.
179
+ - `Transaction`
180
+ - `MongooseTransactionInterceptor` — deprecated request-wide 호환성 interceptor입니다. 새 코드에서는 서비스 `@Transaction()` 또는 명시적 `requestTransaction(...)`을 우선 사용하세요.
125
181
  - `MONGOOSE_CONNECTION`, `MONGOOSE_DISPOSE`, `MONGOOSE_OPTIONS`
126
182
  - `createMongooseProviders(options)` — 호환성/수동 composition helper입니다. 애플리케이션-facing 등록에서는 module export와 provider visibility가 문서화된 namespace facade와 맞도록 `MongooseModule.forRoot(...)` 또는 `MongooseModule.forRootAsync(...)`를 우선 사용하세요.
127
183
  - `createMongoosePlatformStatusSnapshot(...)`
128
184
  - sync 및 async 등록 모두에서 `connection`은 실제 object/function handle이어야 하며, 누락된 handle은 모듈 등록 또는 async bootstrap 중 거부됩니다.
185
+ - `Transaction`은 서비스 계층 세션 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 `this.conn`, 데코레이터가 적용된 인스턴스 자체, 또는 하나의 고유한 중첩 `this.*.conn` collaborator를 resolve합니다. `MongooseConnection`이 다른 필드에 있거나 resolution이 모호하다면 accessor를 전달하세요.
129
186
 
130
187
  ### 관련 export 타입
131
188
 
@@ -133,13 +190,14 @@ HTTP interceptor 밖에서 같은 request-aware transaction boundary가 필요
133
190
  - `MongooseAsyncModuleOptions<TConnection>`
134
191
  - `MongooseConnectionLike`
135
192
  - `MongooseSessionLike`
193
+ - `MongooseModelFacade`
136
194
  - `MongooseHandleProvider`
137
195
  - `MongoosePlatformStatusSnapshotInput`
138
196
 
139
197
  ## 관련 패키지
140
198
 
141
199
  - `@fluojs/runtime`: 애플리케이션 라이프사이클 및 종료 훅을 관리합니다.
142
- - `@fluojs/http`: 인터셉터 시스템을 제공합니다.
200
+ - `@fluojs/http`: 명시적 `requestTransaction(...)` 경계와 함께 사용할 수 있는 요청 라이프사이클 primitive를 제공합니다.
143
201
  - `@fluojs/prisma` / `@fluojs/drizzle`: 대안 데이터베이스 통합 모듈입니다.
144
202
 
145
203
  ## 예제 소스
package/README.md CHANGED
@@ -11,6 +11,9 @@ Mongoose integration for fluo with session-aware transaction handling and lifecy
11
11
  - [Quick Start](#quick-start)
12
12
  - [Lifecycle and Shutdown](#lifecycle-and-shutdown)
13
13
  - [Common Patterns](#common-patterns)
14
+ - [Service Transaction Boundary (@Transaction)](#service-transaction-boundary-transaction)
15
+ - [Request Transaction Interceptor Compatibility](#request-transaction-interceptor-compatibility)
16
+ - [Manual Transactions and currentSession()](#manual-transactions-and-currentsession)
14
17
  - [Public API](#public-api)
15
18
  - [Related Packages](#related-packages)
16
19
  - [Example Sources](#example-sources)
@@ -26,7 +29,10 @@ pnpm add mongoose
26
29
 
27
30
  - when Mongoose should plug into the same DI and application lifecycle as the rest of the app
28
31
  - when MongoDB sessions and transactions need one shared wrapper instead of ad hoc session plumbing in every service
29
- - when request-scoped transactions should be opt-in through an interceptor
32
+ - when request-scoped transactions need explicit `requestTransaction(...)` boundaries
33
+ - when an application already creates and configures its concrete Mongoose connection and wants fluo to observe, not replace, that ownership
34
+
35
+ The root `@fluojs/mongoose` wrapper uses Node.js `node:async_hooks` for ambient transaction context and supports Node.js 20 or newer, matching the package manifest `engines.node >=20.0.0`. For non-Node runtimes, register raw Mongoose-compatible handles behind application-owned providers instead of importing the root wrapper until a runtime-specific transaction-context adapter is documented.
30
36
 
31
37
  ## Quick Start
32
38
 
@@ -52,71 +58,128 @@ class AppModule {}
52
58
 
53
59
  ## Lifecycle and Shutdown
54
60
 
55
- `MongooseModule` registers `MongooseConnection` with the fluo application lifecycle. The package does not create or own the raw Mongoose connection for you; pass a `dispose` hook when the application should close that external connection during shutdown.
61
+ `MongooseModule` registers `MongooseConnection` with the fluo application lifecycle. The package does not create or own the raw Mongoose connection for you; pass a concrete Mongoose connection object/function as `connection`, keep connection-string, pool, plugin, and model compilation ownership in the application, and provide a `dispose` hook when the application should close that external connection during shutdown.
56
62
 
57
63
  Shutdown preserves transaction cleanup order and rejects new manual or request-scoped transaction boundaries once shutdown begins:
58
64
 
59
65
  1. Open request-scoped transactions are aborted with `Application shutdown interrupted an open request transaction.`
60
- 2. Active ambient sessions are tracked until their transaction callback and session cleanup settle.
61
- 3. Their Mongoose sessions finish `abortTransaction()` and `endSession()` cleanup.
66
+ 2. Active ambient sessions, original request callbacks, and fail-open direct-execution transaction callbacks are tracked until their work settles.
67
+ 3. Their Mongoose sessions finish `abortTransaction()` and `endSession()` cleanup only after started callbacks settle.
62
68
  4. The configured `dispose(connection)` hook runs only after active request transactions and ambient session scopes have settled.
63
69
 
64
- `createMongoosePlatformStatusSnapshot(...)` reports `ready` while serving traffic, `shutting-down` while request transactions are draining, and `stopped` after the dispose hook completes. The status details include `sessionStrategy`, `transactionContext: 'als'`, active request/session counts, resource ownership, and strict/session support diagnostics. Manual `transaction()` calls still use the same explicit-session contract as request-scoped transactions: repository code must pass `conn.currentSession()` into Mongoose model operations that participate in the transaction. If the wrapped Mongoose connection exposes `connection.transaction(...)`, fluo delegates the transaction boundary to that API so Mongoose's own ambient-session scope is preserved while still exposing the same session through `currentSession()`. Request-scoped transactions observe the request `AbortSignal` while acquiring sessions and while starting delegated `connection.transaction(...)` work, so request cancellation can interrupt those startup phases before user callbacks run.
70
+ When request cancellation or shutdown aborts a boundary after its callback has started, the boundary preserves the abort result but waits for the original callback to settle before rolling back, ending the session, or disposing the connection. This prevents ALS-backed work from continuing against an already-cleaned-up session or connection.
71
+
72
+ `MongooseConnection.createPlatformStatusSnapshot()` and the exported low-level `createMongoosePlatformStatusSnapshot(...)` helper report `ready` while serving traffic, `shutting-down` while request transactions are draining, and `stopped` after the dispose hook completes. The status details include `sessionStrategy`, `transactionContext: 'als'`, active request/session counts, resource ownership, and strict/session support diagnostics. Manual `transaction()` calls and service `@Transaction()` methods expose the same ambient session to `conn.model(...)`; supported facade methods (`create`, `find`, `findOne`, `aggregate`, and `bulkWrite`) automatically attach that session. Automatic session injection is scoped to the `MongooseConnection.model(...)` wrapper method and does not replace or mutate the raw `connection.model(...)` cache/compile path returned by `conn.current()`. Use `conn.currentSession()` for unsupported model methods, `doc.save()`, or external utilities that need explicit session plumbing. If the wrapped Mongoose connection exposes `connection.transaction(...)`, fluo delegates the transaction boundary to that API so Mongoose's own ambient-session scope is preserved while still exposing the same session through `currentSession()`. Request-scoped transactions observe the request `AbortSignal` while acquiring sessions and while starting delegated `connection.transaction(...)` work, so request cancellation can interrupt those startup phases before user callbacks run.
65
73
  Nested `requestTransaction(...)` calls opened inside an existing manual `transaction(...)` boundary reuse the ambient session, stay visible in `details.activeRequestTransactions`, and are aborted during shutdown so the outer manual transaction can roll back before `dispose(connection)` runs.
66
74
 
67
75
  ## Common Patterns
68
76
 
69
- ### Access the connection through `MongooseConnection`
77
+ ### Service Transaction Boundary (@Transaction)
78
+
79
+ 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 MongoDB session.
70
80
 
71
81
  ```ts
72
- import { MongooseConnection } from '@fluojs/mongoose';
82
+ import { MongooseConnection, Transaction, type MongooseModelFacade } from '@fluojs/mongoose';
83
+
84
+ type UserDocument = { readonly _id: string; readonly name: string };
85
+ type UserCreateModel = MongooseModelFacade<Promise<readonly [UserDocument]>>;
86
+ type ProfileCreateModel = MongooseModelFacade<Promise<readonly { readonly userId: string }[]>>;
87
+
88
+ export class UserService {
89
+ constructor(private readonly repo: UserRepository) {}
90
+
91
+ @Transaction()
92
+ async onboardUser(dto: CreateUserDto) {
93
+ const [user] = await this.repo.create(dto);
94
+ await this.repo.initProfile(user._id);
95
+ return user;
96
+ }
97
+ }
73
98
 
74
99
  export class UserRepository {
75
100
  constructor(private readonly conn: MongooseConnection) {}
76
101
 
77
- async findById(id: string) {
78
- const User = this.conn.current().model('User');
79
- return User.findById(id);
102
+ async create(data: CreateUserDto) {
103
+ // model() returns a session-aware facade inside @Transaction().
104
+ // Operations like create, find, findOne, aggregate, and bulkWrite
105
+ // automatically participate in the ambient transaction.
106
+ return this.conn.model<UserCreateModel>('User').create([data]);
107
+ }
108
+
109
+ async initProfile(userId: string) {
110
+ return this.conn.model<ProfileCreateModel>('Profile').create([{ userId }]);
80
111
  }
81
112
  }
82
113
  ```
83
114
 
84
- ### Manual transactions still need explicit sessions
115
+ Calls to `@Transaction()` methods are reentrant. If a decorated method calls another decorated method, they share the same underlying MongoDB session. Note that `doc.save()` is not automatically session-aware in v1; use the supported facade operations (`model.create()`, `model.find()`, `model.findOne()`, `model.aggregate()`, or `model.bulkWrite()`) for automatic transaction participation.
116
+
117
+ ### Request Transaction Interceptor Compatibility
118
+
119
+ `MongooseTransactionInterceptor` is restored as a deprecated 1.x compatibility export for existing request-wide `@UseInterceptors(...)` boundaries. `MongooseModule.forRoot(...)` and `forRootAsync(...)` provide and export it. It delegates to `MongooseConnection.requestTransaction(...)` and forwards the request `AbortSignal`.
85
120
 
86
121
  ```ts
87
- await this.conn.transaction(async () => {
88
- const session = this.conn.currentSession();
89
- const User = this.conn.current().model('User');
122
+ import { Controller, Post, UseInterceptors } from '@fluojs/http';
123
+ import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
90
124
 
91
- await User.create([{ name: 'Ada' }], { session });
92
- });
125
+ @Controller('/orders')
126
+ export class OrdersController {
127
+ @Post('/')
128
+ @UseInterceptors(MongooseTransactionInterceptor)
129
+ createOrder() {
130
+ return this.orders.create();
131
+ }
132
+ }
93
133
  ```
94
134
 
95
- If the wrapped connection implements `connection.transaction(...)`, fluo treats that as the strict transaction boundary even when `startSession()` is not exposed directly. Otherwise, when the connection does not implement `startSession()`, transactions fall back to direct execution by default. Set `strictTransactions: true` to throw `Transaction not supported: Mongoose connection does not implement startSession.` instead of falling back.
135
+ Prefer service-layer `@Transaction()` for new business operations. Keep this interceptor only while migrating existing request-wide boundaries, or replace it with an explicit `requestTransaction(...)` call when request orchestration must make the boundary visible.
96
136
 
97
- Fluo never rewrites Mongoose operation options. If a model call passes an explicit `{ session }`, that option is left intact; if it omits one, repositories should not assume fluo will attach a session for them. Keep same-session parallel work and nested transaction expectations conservative: nested `MongooseConnection.transaction(...)` calls reuse the active boundary rather than opening a second MongoDB transaction on the same session.
137
+ ### Manual Transactions and currentSession()
98
138
 
99
- ### Request-scoped transactions
139
+ The `MongooseConnection` provides `currentSession()` to access the ambient MongoDB session and `current()` to access the root connection handle. Use these as escape hatches when you need to pass sessions to external utilities or perform advanced manual plumbing.
100
140
 
101
141
  ```ts
102
- import { UseInterceptors } from '@fluojs/http';
103
- import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
142
+ import { MongooseConnection } from '@fluojs/mongoose';
143
+
144
+ export class AdvancedRepository {
145
+ constructor(private readonly conn: MongooseConnection) {}
146
+
147
+ async customOperation() {
148
+ const session = this.conn.currentSession();
149
+ const User = this.conn.current().model('User');
150
+
151
+ // Explicitly passing the session
152
+ return User.find({ status: 'active' }).session(session || null);
153
+ }
154
+ }
155
+ ```
156
+
157
+ Use `conn.transaction()` for manual transaction blocks:
104
158
 
105
- @UseInterceptors(MongooseTransactionInterceptor)
106
- class UserController {}
159
+ ```ts
160
+ await this.conn.transaction(async () => {
161
+ const User = this.conn.model('User');
162
+ await User.create([{ name: 'Ada' }]);
163
+ });
107
164
  ```
108
165
 
109
- Use `MongooseConnection.requestTransaction(...)` directly when you need the same request-aware transaction boundary outside an HTTP interceptor. Nested service transactions reuse the active session boundary, and nested request boundaries opened inside a manual transaction still participate in request abort and shutdown tracking.
166
+ If the wrapped connection implements `connection.transaction(...)`, fluo treats that as the strict transaction boundary. Otherwise, when the connection does not implement `startSession()`, transactions use fail-open direct callback execution by default (`strictTransactions: false`), which is useful for local fakes and staged migrations but provides no rollback atomicity. Open fail-open manual `transaction(...)` callbacks still drain during shutdown before `dispose(connection)` runs. Set `strictTransactions: true` for production flows that require MongoDB transaction guarantees; missing transaction support then makes readiness `not-ready` and causes transaction helpers to throw.
167
+
168
+ For supported facade methods, fluo preserves existing Mongoose operation options and only merges the ambient `{ session }` into the correct options argument. `create(...)` injects the session only through Mongoose's array overload, `create([docs], options?)`. Positional `create(docA, docB)` arguments are forwarded unchanged—even when the last document contains option-like fields such as `timestamps`—and therefore do not receive automatic session injection. Use the array overload for transaction participation. If a model call passes an explicit `{ session: null }` or a different session object inside an ambient transaction, including the third options argument of `findOne(filter, projection, options)`, fluo throws a session conflict error to prevent accidental transaction escapes. Pass a result-specialized `MongooseModelFacade` as the `model<TModel>(...)` type argument when repository code needs typed operation results.
110
169
 
111
170
  ## Public API
112
171
 
113
172
  - `MongooseModule.forRoot(options)` / `MongooseModule.forRootAsync(options)`
114
173
  - `MongooseConnection`
115
- - `MongooseTransactionInterceptor`
174
+ - `MongooseConnection.createPlatformStatusSnapshot()` — reports health/readiness, resource ownership, active request/session drain counts, and strict transaction support diagnostics for platform observability surfaces.
175
+ - `MongooseConnection.model<TModel>(name, ...args)` — returns the callable, result-specializable `MongooseModelFacade` outside transactions or a session-aware version for `create`, `find`, `findOne`, `aggregate`, and `bulkWrite` inside an active transaction without mutating the underlying Mongoose connection.
176
+ - `Transaction`
177
+ - `MongooseTransactionInterceptor` — deprecated request-wide compatibility interceptor; prefer service `@Transaction()` or explicit `requestTransaction(...)` in new code.
116
178
  - `MONGOOSE_CONNECTION`, `MONGOOSE_DISPOSE`, `MONGOOSE_OPTIONS`
117
179
  - `createMongooseProviders(options)` — compatibility/manual composition helper; prefer `MongooseModule.forRoot(...)` or `MongooseModule.forRootAsync(...)` for application-facing registration so module exports and provider visibility stay aligned.
118
180
  - `createMongoosePlatformStatusSnapshot(...)`
119
181
  - `connection` must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
182
+ - `Transaction` is a standard TC39 method decorator for service-layer session transaction boundaries. It resolves `this.conn`, the decorated instance itself, or one unique nested `this.*.conn` collaborator by default; pass an accessor when the `MongooseConnection` lives under a different field or resolution would be ambiguous.
120
183
 
121
184
  ### Related exported types
122
185
 
@@ -124,13 +187,14 @@ Use `MongooseConnection.requestTransaction(...)` directly when you need the same
124
187
  - `MongooseAsyncModuleOptions<TConnection>`
125
188
  - `MongooseConnectionLike`
126
189
  - `MongooseSessionLike`
190
+ - `MongooseModelFacade`
127
191
  - `MongooseHandleProvider`
128
192
  - `MongoosePlatformStatusSnapshotInput`
129
193
 
130
194
  ## Related Packages
131
195
 
132
196
  - `@fluojs/runtime`: manages startup and shutdown hooks
133
- - `@fluojs/http`: provides the interceptor chain for request transactions
197
+ - `@fluojs/http`: provides request lifecycle primitives that can be paired with explicit `requestTransaction(...)` boundaries
134
198
  - `@fluojs/prisma` and `@fluojs/drizzle`: alternate database integrations with different transaction models
135
199
 
136
200
  ## Example Sources
@@ -1,5 +1,5 @@
1
1
  import type { OnApplicationShutdown } from '@fluojs/runtime';
2
- import type { MongooseConnectionLike, MongooseHandleProvider, MongooseSessionLike } from './types.js';
2
+ import type { MongooseConnectionLike, MongooseHandleProvider, MongooseModelFacade, MongooseSessionLike } from './types.js';
3
3
  type MongooseRuntimeOptions = {
4
4
  strictTransactions: boolean;
5
5
  };
@@ -15,6 +15,7 @@ export declare class MongooseConnection<TConnection extends MongooseConnectionLi
15
15
  private readonly sessions;
16
16
  private readonly activeRequestTransactions;
17
17
  private readonly activeSessions;
18
+ private readonly activeTransactionCallbacks;
18
19
  private lifecycleState;
19
20
  constructor(connection: TConnection, dispose?: ((connection: TConnection) => Promise<void> | void) | undefined, connectionOptions?: MongooseRuntimeOptions);
20
21
  /**
@@ -39,6 +40,15 @@ export declare class MongooseConnection<TConnection extends MongooseConnectionLi
39
40
  * @returns The ambient session inside a transaction boundary, or `undefined` outside one.
40
41
  */
41
42
  currentSession(): MongooseSessionLike | undefined;
43
+ /**
44
+ * Returns a model from the root connection, injecting the ambient transaction session into conservative operations.
45
+ *
46
+ * @typeParam TModel Consumer-defined facade result contract for the wrapped model.
47
+ * @param name Model name passed to the underlying Mongoose connection.
48
+ * @param args Additional model resolver arguments forwarded unchanged.
49
+ * @returns The real model outside transactions, or a model facade inside an active transaction boundary.
50
+ */
51
+ model<TModel extends MongooseModelFacade = MongooseModelFacade>(name: string, ...args: unknown[]): TModel;
42
52
  /** Aborts active request transactions, waits for settlement, then runs the optional dispose hook. */
43
53
  onApplicationShutdown(): Promise<void>;
44
54
  /** Produces the shared persistence status snapshot for platform diagnostics surfaces. */
@@ -73,9 +83,11 @@ export declare class MongooseConnection<TConnection extends MongooseConnectionLi
73
83
  private assertTransactionsAvailable;
74
84
  private assertRequestTransactionsAvailable;
75
85
  private runManualSessionTransaction;
86
+ private runDirectTransaction;
76
87
  private resolveSessionForRequest;
77
88
  private runConnectionTransaction;
78
89
  private trackActiveSession;
90
+ private trackActiveTransactionCallback;
79
91
  private trackActiveRequestTransaction;
80
92
  private untrackActiveRequestTransaction;
81
93
  private resolveSession;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAS7D,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAuBpB,KAAK,sBAAsB,GAAG;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CAAC;AAmBF;;;;GAIG;AACH,qBACa,kBAAkB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB,CACjG,YAAW,sBAAsB,CAAC,WAAW,CAAC,EAAE,qBAAqB;IAQnE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IARpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAChE,OAAO,CAAC,cAAc,CAAkD;gBAGrD,UAAU,EAAE,WAAW,EACvB,OAAO,CAAC,GAAE,CAAC,UAAU,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,aAAA,EAC3D,iBAAiB,GAAE,sBAAsD;IAG5F;;;;;;;;;OASG;IACH,OAAO,IAAI,WAAW;IAItB;;;;;;;;;OASG;IACH,cAAc,IAAI,mBAAmB,GAAG,SAAS;IAIjD,qGAAqG;IAC/F,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5C,yFAAyF;IACzF,4BAA4B;IAY5B;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoBtD;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IA+CnF,OAAO,CAAC,2BAA2B;IAMnC,OAAO,CAAC,kCAAkC;YAM5B,2BAA2B;YAc3B,wBAAwB;YA4BxB,wBAAwB;IActC,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,+BAA+B;YAIzB,cAAc;CAW7B"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAS7D,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAqCpB,KAAK,sBAAsB,GAAG;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CAAC;AAkIF;;;;GAIG;AACH,qBACa,kBAAkB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB,CACjG,YAAW,sBAAsB,CAAC,WAAW,CAAC,EAAE,qBAAqB;IASnE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IATpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAChE,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAwC;IACnF,OAAO,CAAC,cAAc,CAAkD;gBAGrD,UAAU,EAAE,WAAW,EACvB,OAAO,CAAC,GAAE,CAAC,UAAU,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,aAAA,EAC3D,iBAAiB,GAAE,sBAAsD;IAG5F;;;;;;;;;OASG;IACH,OAAO,IAAI,WAAW;IAItB;;;;;;;;;OASG;IACH,cAAc,IAAI,mBAAmB,GAAG,SAAS;IAIjD;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM;IAczG,qGAAqG;IAC/F,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5C,yFAAyF;IACzF,4BAA4B;IAY5B;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA+BtD;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAwDnF,OAAO,CAAC,2BAA2B;IAMnC,OAAO,CAAC,kCAAkC;YAM5B,2BAA2B;YAc3B,oBAAoB;YAQpB,wBAAwB;YA4BxB,wBAAwB;IActC,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,8BAA8B;IAkBtC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,+BAA+B;YAIzB,cAAc;CAW7B"}
@@ -11,6 +11,85 @@ import { createMongoosePlatformStatusSnapshot } from './status.js';
11
11
  import { MONGOOSE_CONNECTION, MONGOOSE_DISPOSE, MONGOOSE_OPTIONS } from './tokens.js';
12
12
  const TRANSACTIONS_NOT_SUPPORTED_ERROR = 'Transaction not supported: Mongoose connection does not implement startSession.';
13
13
  const TRANSACTION_UNAVAILABLE_ERROR = 'Mongoose transactions are unavailable during application shutdown.';
14
+ const MODEL_OPERATIONS_WITH_OPTIONS = new Set(['aggregate', 'bulkWrite', 'create', 'find', 'findOne']);
15
+ const MODEL_OPERATIONS_WITH_PROJECTION = new Set(['find', 'findOne']);
16
+ function isObjectLike(value) {
17
+ return typeof value === 'object' && value !== null || typeof value === 'function';
18
+ }
19
+ function resolveCreateOptionsIndex(operationArgs) {
20
+ if (Array.isArray(operationArgs[0])) {
21
+ return 1;
22
+ }
23
+ return undefined;
24
+ }
25
+ function resolveOptionsIndex(operation, operationArgs) {
26
+ if (operation === 'create') {
27
+ return resolveCreateOptionsIndex(operationArgs);
28
+ }
29
+ if (!MODEL_OPERATIONS_WITH_PROJECTION.has(operation)) {
30
+ return operationArgs.length > 1 ? 1 : operationArgs.length;
31
+ }
32
+ if (operationArgs.length >= 3) {
33
+ return 2;
34
+ }
35
+ if (operationArgs.length <= 1) {
36
+ return 2;
37
+ }
38
+ return operationArgs.length;
39
+ }
40
+ function resolveSessionOptions(opts, ambient) {
41
+ const options = opts && typeof opts === 'object' ? opts : {};
42
+ if (options.session === null) {
43
+ throw new Error('Explicit session: null conflicts with ambient transaction session');
44
+ }
45
+ if (options.session !== undefined && options.session !== ambient) {
46
+ throw new Error('Explicit session conflicts with ambient transaction session');
47
+ }
48
+ return {
49
+ ...options,
50
+ session: ambient
51
+ };
52
+ }
53
+ function createAmbientSessionModelFacade(model, ambient) {
54
+ return new Proxy(model, {
55
+ get(target, prop, receiver) {
56
+ const value = Reflect.get(target, prop, receiver);
57
+ if (!MODEL_OPERATIONS_WITH_OPTIONS.has(prop) || typeof value !== 'function') {
58
+ return value;
59
+ }
60
+ return (...args) => {
61
+ const operationArgs = [...args];
62
+ const optionsIndex = resolveOptionsIndex(prop, operationArgs);
63
+ if (optionsIndex === undefined) {
64
+ return value.apply(target, operationArgs);
65
+ }
66
+ operationArgs[optionsIndex] = resolveSessionOptions(operationArgs[optionsIndex], ambient);
67
+ return value.apply(target, operationArgs);
68
+ };
69
+ }
70
+ });
71
+ }
72
+ async function raceWithAbortAndDrainCallback(fn, signal, shouldDrainAfterAbort = () => true) {
73
+ let callback;
74
+ try {
75
+ return await raceWithAbort(() => {
76
+ callback = Promise.resolve().then(fn);
77
+ return callback;
78
+ }, signal);
79
+ } catch (error) {
80
+ if (signal.aborted && callback && shouldDrainAfterAbort()) {
81
+ await callback.then(() => undefined, () => undefined);
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ function resolveModelFactory(connection) {
87
+ if (!isObjectLike(connection)) {
88
+ return undefined;
89
+ }
90
+ const modelConnection = connection;
91
+ return modelConnection.model;
92
+ }
14
93
  async function executeSessionTransaction(session, fn) {
15
94
  try {
16
95
  await session.startTransaction();
@@ -40,6 +119,7 @@ class MongooseConnection {
40
119
  sessions = new AsyncLocalStorage();
41
120
  activeRequestTransactions = new Set();
42
121
  activeSessions = new Set();
122
+ activeTransactionCallbacks = new Set();
43
123
  lifecycleState = 'ready';
44
124
  constructor(connection, dispose, connectionOptions = {
45
125
  strictTransactions: false
@@ -74,7 +154,26 @@ class MongooseConnection {
74
154
  * @returns The ambient session inside a transaction boundary, or `undefined` outside one.
75
155
  */
76
156
  currentSession() {
77
- return this.sessions.getStore();
157
+ return this.sessions.getStore()?.session;
158
+ }
159
+
160
+ /**
161
+ * Returns a model from the root connection, injecting the ambient transaction session into conservative operations.
162
+ *
163
+ * @typeParam TModel Consumer-defined facade result contract for the wrapped model.
164
+ * @param name Model name passed to the underlying Mongoose connection.
165
+ * @param args Additional model resolver arguments forwarded unchanged.
166
+ * @returns The real model outside transactions, or a model facade inside an active transaction boundary.
167
+ */
168
+
169
+ model(name, ...args) {
170
+ const modelFactory = resolveModelFactory(this.connection);
171
+ if (typeof modelFactory !== 'function') {
172
+ throw new Error('Mongoose connection does not implement model().');
173
+ }
174
+ const model = modelFactory.call(this.connection, name, ...args);
175
+ const ambient = this.currentSession();
176
+ return ambient ? createAmbientSessionModelFacade(model, ambient) : model;
78
177
  }
79
178
 
80
179
  /** Aborts active request transactions, waits for settlement, then runs the optional dispose hook. */
@@ -83,7 +182,7 @@ class MongooseConnection {
83
182
  for (const transaction of this.activeRequestTransactions) {
84
183
  transaction.abort(new Error('Application shutdown interrupted an open request transaction.'));
85
184
  }
86
- await Promise.allSettled([...Array.from(this.activeRequestTransactions, transaction => transaction.settled), ...Array.from(this.activeSessions, session => session.settled)]);
185
+ await Promise.allSettled([...Array.from(this.activeRequestTransactions, transaction => transaction.settled), ...Array.from(this.activeSessions, session => session.settled), ...Array.from(this.activeTransactionCallbacks, callback => callback.settled)]);
87
186
  if (this.dispose) {
88
187
  await this.dispose(this.connection);
89
188
  }
@@ -117,18 +216,26 @@ class MongooseConnection {
117
216
  * @returns The callback result after the session transaction finishes or the direct-execution fallback completes.
118
217
  */
119
218
  async transaction(fn) {
219
+ this.assertTransactionsAvailable();
120
220
  const currentSession = this.sessions.getStore();
121
221
  if (currentSession) {
122
222
  return fn();
123
223
  }
124
- this.assertTransactionsAvailable();
125
224
  if (typeof this.connection.transaction === 'function') {
126
225
  return this.runConnectionTransaction(fn);
127
226
  }
128
- const session = await this.resolveSession();
227
+ const activeCallback = this.trackActiveTransactionCallback();
228
+ let session;
229
+ try {
230
+ session = await this.resolveSession();
231
+ } catch (error) {
232
+ activeCallback.settle();
233
+ throw error;
234
+ }
129
235
  if (!session) {
130
- return fn();
236
+ return this.runDirectTransaction(fn, activeCallback);
131
237
  }
238
+ activeCallback.settle();
132
239
  return this.runManualSessionTransaction(session, fn);
133
240
  }
134
241
 
@@ -145,16 +252,16 @@ class MongooseConnection {
145
252
  * @returns The callback result after the request transaction finishes or the direct-execution fallback completes.
146
253
  */
147
254
  async requestTransaction(fn, signal) {
148
- const currentSession = this.sessions.getStore();
149
- if (currentSession) {
255
+ const currentScope = this.sessions.getStore();
256
+ if (currentScope) {
150
257
  this.assertRequestTransactionsAvailable();
151
258
  const abortContext = createRequestAbortContext(signal);
152
259
  const active = this.trackActiveRequestTransaction(abortContext.controller);
153
260
  try {
154
- return await raceWithAbort(fn, abortContext.signal);
261
+ return await raceWithAbortAndDrainCallback(fn, abortContext.signal);
155
262
  } finally {
156
263
  abortContext.cleanup();
157
- this.untrackActiveRequestTransaction(active);
264
+ currentScope.activeSession.retainRequestTransaction(active);
158
265
  }
159
266
  }
160
267
  this.assertRequestTransactionsAvailable();
@@ -163,15 +270,20 @@ class MongooseConnection {
163
270
  let untrackActiveInFinally = true;
164
271
  try {
165
272
  if (typeof this.connection.transaction === 'function') {
166
- return await raceWithAbort(() => this.runConnectionTransaction(() => raceWithAbort(fn, abortContext.signal)), abortContext.signal);
273
+ let delegatedCallbackStarted = false;
274
+ const delegatedTransaction = this.runConnectionTransaction(() => {
275
+ delegatedCallbackStarted = true;
276
+ return raceWithAbortAndDrainCallback(fn, abortContext.signal);
277
+ });
278
+ return await raceWithAbortAndDrainCallback(() => delegatedTransaction, abortContext.signal, () => delegatedCallbackStarted);
167
279
  }
168
280
  const resolvedSession = await this.resolveSessionForRequest(abortContext.signal, active, () => {
169
281
  untrackActiveInFinally = false;
170
282
  });
171
283
  if (!resolvedSession) {
172
- return await raceWithAbort(fn, abortContext.signal);
284
+ return await raceWithAbortAndDrainCallback(fn, abortContext.signal);
173
285
  }
174
- return await this.runManualSessionTransaction(resolvedSession, () => raceWithAbort(fn, abortContext.signal));
286
+ return await this.runManualSessionTransaction(resolvedSession, () => raceWithAbortAndDrainCallback(fn, abortContext.signal));
175
287
  } finally {
176
288
  abortContext.cleanup();
177
289
  if (untrackActiveInFinally) {
@@ -192,7 +304,10 @@ class MongooseConnection {
192
304
  async runManualSessionTransaction(session, fn) {
193
305
  const activeSession = this.trackActiveSession();
194
306
  try {
195
- return await this.sessions.run(session, () => executeSessionTransaction(session, fn));
307
+ return await this.sessions.run({
308
+ activeSession,
309
+ session
310
+ }, () => executeSessionTransaction(session, fn));
196
311
  } finally {
197
312
  try {
198
313
  await session.endSession();
@@ -201,6 +316,13 @@ class MongooseConnection {
201
316
  }
202
317
  }
203
318
  }
319
+ async runDirectTransaction(fn, activeCallback) {
320
+ try {
321
+ return await fn();
322
+ } finally {
323
+ activeCallback.settle();
324
+ }
325
+ }
204
326
  async resolveSessionForRequest(signal, active, deferActiveSettlement) {
205
327
  const sessionPromise = this.resolveSession();
206
328
  try {
@@ -224,7 +346,10 @@ class MongooseConnection {
224
346
  if (typeof this.connection.transaction !== 'function') {
225
347
  throw new Error('Mongoose connection transaction resolver initialization failed.');
226
348
  }
227
- return await this.connection.transaction(session => this.sessions.run(session, fn));
349
+ return await this.connection.transaction(session => this.sessions.run({
350
+ activeSession,
351
+ session
352
+ }, fn));
228
353
  } finally {
229
354
  activeSession.settle();
230
355
  }
@@ -236,14 +361,37 @@ class MongooseConnection {
236
361
  settle = resolve;
237
362
  })
238
363
  };
364
+ const retainedRequestTransactions = new Set();
239
365
  this.activeSessions.add(active);
240
366
  return {
367
+ retainRequestTransaction: handle => {
368
+ retainedRequestTransactions.add(handle);
369
+ },
241
370
  settle: () => {
371
+ for (const handle of retainedRequestTransactions) {
372
+ this.untrackActiveRequestTransaction(handle);
373
+ }
374
+ retainedRequestTransactions.clear();
242
375
  this.activeSessions.delete(active);
243
376
  settle();
244
377
  }
245
378
  };
246
379
  }
380
+ trackActiveTransactionCallback() {
381
+ let settle;
382
+ const active = {
383
+ settled: new Promise(resolve => {
384
+ settle = resolve;
385
+ })
386
+ };
387
+ this.activeTransactionCallbacks.add(active);
388
+ return {
389
+ settle: () => {
390
+ this.activeTransactionCallbacks.delete(active);
391
+ settle();
392
+ }
393
+ };
394
+ }
247
395
  trackActiveRequestTransaction(controller) {
248
396
  return trackActiveRequestTransaction(this.activeRequestTransactions, controller);
249
397
  }
package/dist/module.d.ts CHANGED
@@ -30,7 +30,7 @@ export declare class MongooseModule {
30
30
  * Registers Mongoose providers from static options.
31
31
  *
32
32
  * @param options Mongoose module options with connection handle, optional dispose hook, and strict transaction mode.
33
- * @returns A module definition that exports `MongooseConnection` and `MongooseTransactionInterceptor`.
33
+ * @returns A module definition that exports `MongooseConnection` and its compatibility request interceptor.
34
34
  */
35
35
  static forRoot<TConnection extends MongooseConnectionLike>(options: MongooseModuleOptions<TConnection>): ModuleType;
36
36
  /**
package/dist/module.js CHANGED
@@ -105,7 +105,7 @@ export class MongooseModule {
105
105
  * Registers Mongoose providers from static options.
106
106
  *
107
107
  * @param options Mongoose module options with connection handle, optional dispose hook, and strict transaction mode.
108
- * @returns A module definition that exports `MongooseConnection` and `MongooseTransactionInterceptor`.
108
+ * @returns A module definition that exports `MongooseConnection` and its compatibility request interceptor.
109
109
  */
110
110
  static forRoot(options) {
111
111
  return buildMongooseModule(options);
@@ -1,25 +1,44 @@
1
- import type { Interceptor, InterceptorContext } from '@fluojs/http';
1
+ import type { CallHandler, Interceptor, InterceptorContext } from '@fluojs/http';
2
2
  import { MongooseConnection } from './connection.js';
3
3
  import type { MongooseConnectionLike } from './types.js';
4
+ type TransactionConnection = {
5
+ transaction<T>(fn: () => Promise<T>): Promise<T>;
6
+ };
7
+ type TransactionMethod<THost, TArgs extends unknown[], TResult> = (this: THost, ...args: TArgs) => Promise<TResult>;
4
8
  /**
5
- * HTTP interceptor that wraps each request in a Mongoose request transaction boundary.
9
+ * Wraps a service method in a `MongooseConnection.transaction(...)` boundary.
6
10
  *
7
11
  * @remarks
8
- * Pair this with repository/service code that reads `MongooseConnection.current()` and `currentSession()` so downstream
9
- * calls share the same request-scoped session.
12
+ * This is a TC39 standard method decorator. By default it uses `this.conn` when present, the decorated instance
13
+ * itself when it is transaction-capable, or one unique nested `this.*.conn` collaborator. Pass an accessor when the
14
+ * connection lives under a different field or more than one nested collaborator exposes a connection; the decorator
15
+ * does not bind arbitrary transaction-capable properties to avoid selecting the wrong persistence handle.
16
+ * Nested decorated calls reuse the ambient Mongoose session through `MongooseConnection.transaction(...)`.
17
+ *
18
+ * @param accessor Optional connection resolver for the decorated service instance.
19
+ * @returns A standard method decorator that executes the original method inside a Mongoose transaction.
20
+ */
21
+ export declare function Transaction<THost>(accessor?: (self: THost) => TransactionConnection): <TArgs extends unknown[], TResult>(value: TransactionMethod<THost, TArgs, TResult>, context: ClassMethodDecoratorContext<THost, TransactionMethod<THost, TArgs, TResult>>) => TransactionMethod<THost, TArgs, TResult>;
22
+ /**
23
+ * Compatibility HTTP interceptor that opens a Mongoose request transaction around a routed handler.
24
+ *
25
+ * @remarks
26
+ * This deprecated 1.x bridge forwards the request `AbortSignal` to `MongooseConnection.requestTransaction(...)`.
27
+ * Prefer service-layer `@Transaction()` or an explicit request boundary for new code.
28
+ *
29
+ * @deprecated Prefer service-layer `@Transaction()` or explicit `MongooseConnection.requestTransaction(...)`.
10
30
  */
11
31
  export declare class MongooseTransactionInterceptor implements Interceptor {
12
32
  private readonly connection;
13
33
  constructor(connection: MongooseConnection<MongooseConnectionLike>);
14
34
  /**
15
- * Runs the downstream handler inside a Mongoose request transaction boundary.
35
+ * Runs the downstream handler inside the compatibility request transaction.
16
36
  *
17
- * @param context Interceptor context that supplies the request abort signal.
37
+ * @param context Interceptor context containing the request cancellation signal.
18
38
  * @param next Downstream handler chain.
19
- * @returns The downstream handler result after the request transaction settles.
39
+ * @returns The downstream result after the request transaction settles.
20
40
  */
21
- intercept(context: InterceptorContext, next: {
22
- handle(): Promise<unknown>;
23
- }): Promise<unknown>;
41
+ intercept(context: InterceptorContext, next: CallHandler): Promise<unknown>;
24
42
  }
43
+ export {};
25
44
  //# sourceMappingURL=transaction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,qBACa,8BAA+B,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,kBAAkB,CAAC,sBAAsB,CAAC;IAEnF;;;;;;OAMG;IACG,SAAS,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAGrG"}
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,KAAK,qBAAqB,GAAG;IAC3B,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClD,CAAC;AAEF,KAAK,iBAAiB,CAAC,KAAK,EAAE,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,IAAI,CAChE,IAAI,EAAE,KAAK,EACX,GAAG,IAAI,EAAE,KAAK,KACX,OAAO,CAAC,OAAO,CAAC,CAAC;AA6DtB;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,qBAAqB,GAChD,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAClC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAC/C,OAAO,EAAE,2BAA2B,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,KAClF,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAW5C;AAED;;;;;;;;GAQG;AACH,qBACa,8BAA+B,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,kBAAkB,CAAC,sBAAsB,CAAC;IAEnF;;;;;;OAMG;IACG,SAAS,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAGlF"}
@@ -6,14 +6,82 @@ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.descrip
6
6
  function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
7
  import { Inject } from '@fluojs/core';
8
8
  import { MongooseConnection } from './connection.js';
9
- let _MongooseTransactionI;
9
+ function isTransactionConnection(value) {
10
+ return (typeof value === 'object' && value !== null || typeof value === 'function') && typeof value.transaction === 'function';
11
+ }
12
+ function collectNestedConnCandidates(self) {
13
+ if ((typeof self !== 'object' || self === null) && typeof self !== 'function') {
14
+ return [];
15
+ }
16
+ const candidates = new Set();
17
+ for (const value of Object.values(self)) {
18
+ if ((typeof value !== 'object' || value === null) && typeof value !== 'function') {
19
+ continue;
20
+ }
21
+ const nestedConn = value.conn;
22
+ if (isTransactionConnection(nestedConn)) {
23
+ candidates.add(nestedConn);
24
+ }
25
+ }
26
+ return Array.from(candidates);
27
+ }
28
+ function resolveTransactionConnection(self, accessor) {
29
+ if (accessor) {
30
+ const connection = accessor(self);
31
+ if (isTransactionConnection(connection)) {
32
+ return connection;
33
+ }
34
+ throw new Error('Mongoose @Transaction() accessor did not return a transaction-capable connection.');
35
+ }
36
+ const fallbackHost = self;
37
+ if (isTransactionConnection(fallbackHost.conn)) {
38
+ return fallbackHost.conn;
39
+ }
40
+ if (isTransactionConnection(self)) {
41
+ return self;
42
+ }
43
+ const nestedConnCandidates = collectNestedConnCandidates(self);
44
+ if (nestedConnCandidates.length === 1) {
45
+ return nestedConnCandidates[0];
46
+ }
47
+ if (nestedConnCandidates.length > 1) {
48
+ throw new Error('Mongoose @Transaction() found multiple nested this.*.conn candidates; pass an accessor.');
49
+ }
50
+ throw new Error('Mongoose @Transaction() could not resolve a transaction-capable connection from this.conn.');
51
+ }
52
+
53
+ /**
54
+ * Wraps a service method in a `MongooseConnection.transaction(...)` boundary.
55
+ *
56
+ * @remarks
57
+ * This is a TC39 standard method decorator. By default it uses `this.conn` when present, the decorated instance
58
+ * itself when it is transaction-capable, or one unique nested `this.*.conn` collaborator. Pass an accessor when the
59
+ * connection lives under a different field or more than one nested collaborator exposes a connection; the decorator
60
+ * does not bind arbitrary transaction-capable properties to avoid selecting the wrong persistence handle.
61
+ * Nested decorated calls reuse the ambient Mongoose session through `MongooseConnection.transaction(...)`.
62
+ *
63
+ * @param accessor Optional connection resolver for the decorated service instance.
64
+ * @returns A standard method decorator that executes the original method inside a Mongoose transaction.
65
+ */
66
+ export function Transaction(accessor) {
67
+ return function transactionDecorator(value, _context) {
68
+ return async function transactionWrappedMethod(...args) {
69
+ const connection = resolveTransactionConnection(this, accessor);
70
+ return connection.transaction(() => value.apply(this, args));
71
+ };
72
+ };
73
+ }
74
+
10
75
  /**
11
- * HTTP interceptor that wraps each request in a Mongoose request transaction boundary.
76
+ * Compatibility HTTP interceptor that opens a Mongoose request transaction around a routed handler.
12
77
  *
13
78
  * @remarks
14
- * Pair this with repository/service code that reads `MongooseConnection.current()` and `currentSession()` so downstream
15
- * calls share the same request-scoped session.
79
+ * This deprecated 1.x bridge forwards the request `AbortSignal` to `MongooseConnection.requestTransaction(...)`.
80
+ * Prefer service-layer `@Transaction()` or an explicit request boundary for new code.
81
+ *
82
+ * @deprecated Prefer service-layer `@Transaction()` or explicit `MongooseConnection.requestTransaction(...)`.
16
83
  */
84
+ let _MongooseTransactionI;
17
85
  class MongooseTransactionInterceptor {
18
86
  static {
19
87
  [_MongooseTransactionI, _initClass] = _applyDecs(this, [Inject(MongooseConnection)], []).c;
@@ -23,14 +91,14 @@ class MongooseTransactionInterceptor {
23
91
  }
24
92
 
25
93
  /**
26
- * Runs the downstream handler inside a Mongoose request transaction boundary.
94
+ * Runs the downstream handler inside the compatibility request transaction.
27
95
  *
28
- * @param context Interceptor context that supplies the request abort signal.
96
+ * @param context Interceptor context containing the request cancellation signal.
29
97
  * @param next Downstream handler chain.
30
- * @returns The downstream handler result after the request transaction settles.
98
+ * @returns The downstream result after the request transaction settles.
31
99
  */
32
100
  async intercept(context, next) {
33
- return this.connection.requestTransaction(async () => next.handle(), context.requestContext.request.signal);
101
+ return this.connection.requestTransaction(() => next.handle(), context.requestContext.request.signal);
34
102
  }
35
103
  static {
36
104
  _initClass();
package/dist/types.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { MaybePromise } from '@fluojs/core';
2
2
  /**
3
- * Minimal Mongoose connection seam that optionally supports session creation.
3
+ * Minimal Mongoose connection seam that optionally supports session transaction APIs.
4
4
  *
5
5
  * @remarks
6
- * Fluo only requires `startSession()` to expose transaction helpers; plain connection usage still works without it.
6
+ * Fluo can open transaction helpers through either `connection.transaction(...)` or `startSession()`;
7
+ * plain connection usage still works without either API.
7
8
  */
8
9
  export interface MongooseConnectionLike {
9
10
  startSession?(): Promise<MongooseSessionLike>;
@@ -18,6 +19,32 @@ export interface MongooseSessionLike {
18
19
  abortTransaction(): MaybePromise<void>;
19
20
  endSession(): MaybePromise<void>;
20
21
  }
22
+ /**
23
+ * Callable model facade returned by `MongooseConnection.model(...)`.
24
+ *
25
+ * @remarks
26
+ * The listed operations receive the ambient transaction session automatically when called inside a transaction boundary.
27
+ * Other model properties remain available as `unknown` because fluo does not own application schema or plugin typing.
28
+ *
29
+ * @typeParam TCreateResult Result returned by `create(...)`.
30
+ * @typeParam TFindResult Result returned by `find(...)`.
31
+ * @typeParam TFindOneResult Result returned by `findOne(...)`.
32
+ * @typeParam TAggregateResult Result returned by `aggregate(...)`.
33
+ * @typeParam TBulkWriteResult Result returned by `bulkWrite(...)`.
34
+ */
35
+ export interface MongooseModelFacade<TCreateResult = unknown, TFindResult = unknown, TFindOneResult = unknown, TAggregateResult = unknown, TBulkWriteResult = unknown> {
36
+ /** Runs a Mongoose aggregate operation with ambient session options. */
37
+ aggregate(...args: unknown[]): TAggregateResult;
38
+ /** Runs a Mongoose bulk-write operation with ambient session options. */
39
+ bulkWrite(...args: unknown[]): TBulkWriteResult;
40
+ /** Runs a Mongoose create operation with ambient session options. */
41
+ create(...args: unknown[]): TCreateResult;
42
+ /** Runs a Mongoose find operation with ambient session options. */
43
+ find(...args: unknown[]): TFindResult;
44
+ /** Runs a Mongoose find-one operation with ambient session options. */
45
+ findOne(...args: unknown[]): TFindOneResult;
46
+ readonly [key: PropertyKey]: unknown;
47
+ }
21
48
  /**
22
49
  * Module options for registering a Mongoose connection and optional shutdown disposal hook.
23
50
  *
@@ -31,7 +58,8 @@ export interface MongooseModuleOptions<TConnection extends MongooseConnectionLik
31
58
  /** Whether Mongoose providers should be visible globally. Defaults to `false`. */
32
59
  global?: boolean;
33
60
  /**
34
- * Throws when transaction helpers are used against a connection that does not implement `startSession()`.
61
+ * Throws when transaction helpers are used against a connection that implements neither `connection.transaction(...)` nor
62
+ * `startSession()`.
35
63
  *
36
64
  * @remarks
37
65
  * Leave this disabled when `transaction()` / `requestTransaction()` should fall back to direct execution.
@@ -48,6 +76,14 @@ export interface MongooseHandleProvider<TConnection extends MongooseConnectionLi
48
76
  current(): TConnection;
49
77
  /** Returns the ambient Mongoose session for the current async context, when one exists. */
50
78
  currentSession(): MongooseSessionLike | undefined;
79
+ /**
80
+ * Returns a Mongoose model handle, or a session-aware facade inside an active transaction.
81
+ *
82
+ * @param name Model name passed to the underlying Mongoose connection.
83
+ * @param args Additional model resolver arguments forwarded unchanged.
84
+ * @returns The root model outside transactions, or a model facade inside an active transaction boundary.
85
+ */
86
+ model<TModel extends MongooseModelFacade = MongooseModelFacade>(name: string, ...args: unknown[]): TModel;
51
87
  /**
52
88
  * Opens a Mongoose session transaction boundary around `fn`.
53
89
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/E;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,iBAAiB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACxC,gBAAgB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB;IACxG,kFAAkF;IAClF,UAAU,EAAE,WAAW,CAAC;IACxB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1D,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB;IACzG,uFAAuF;IACvF,OAAO,IAAI,WAAW,CAAC;IACvB,2FAA2F;IAC3F,cAAc,IAAI,mBAAmB,GAAG,SAAS,CAAC;IAClD;;;;;OAKG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/E;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,iBAAiB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACxC,gBAAgB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,mBAAmB,CAClC,aAAa,GAAG,OAAO,EACvB,WAAW,GAAG,OAAO,EACrB,cAAc,GAAG,OAAO,EACxB,gBAAgB,GAAG,OAAO,EAC1B,gBAAgB,GAAG,OAAO;IAE1B,wEAAwE;IACxE,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC;IAChD,yEAAyE;IACzE,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC;IAChD,qEAAqE;IACrE,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC;IAC1C,mEAAmE;IACnE,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC;IACtC,uEAAuE;IACvE,OAAO,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC;IAC5C,QAAQ,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB;IACxG,kFAAkF;IAClF,UAAU,EAAE,WAAW,CAAC;IACxB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1D,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB,CAAC,WAAW,SAAS,sBAAsB,GAAG,sBAAsB;IACzG,uFAAuF;IACvF,OAAO,IAAI,WAAW,CAAC;IACvB,2FAA2F;IAC3F,cAAc,IAAI,mBAAmB,GAAG,SAAS,CAAC;IAClD;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAC1G;;;;;OAKG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/E"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "transaction",
10
10
  "odm"
11
11
  ],
12
- "version": "1.0.5",
12
+ "version": "1.1.1",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.3",
40
- "@fluojs/http": "^1.1.0",
41
- "@fluojs/di": "^1.0.3",
42
- "@fluojs/runtime": "^1.1.2"
39
+ "@fluojs/core": "^1.1.0",
40
+ "@fluojs/http": "^2.0.1",
41
+ "@fluojs/di": "^2.0.0",
42
+ "@fluojs/runtime": "^2.0.1"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "mongoose": ">=7.0.0"