@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 +94 -36
- package/README.md +90 -26
- package/dist/connection.d.ts +13 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +162 -14
- package/dist/module.d.ts +1 -1
- package/dist/module.js +1 -1
- package/dist/transaction.d.ts +29 -10
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +76 -8
- package/dist/types.d.ts +39 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
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
|
-
- 요청
|
|
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.
|
|
62
|
-
2. 활성 ambient session
|
|
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
|
-
|
|
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
|
-
###
|
|
80
|
+
### 서비스 트랜잭션 경계 (@Transaction)
|
|
72
81
|
|
|
73
|
-
`
|
|
82
|
+
`@Transaction()` 데코레이터는 서비스 레이어에서 트랜잭션 경계를 정의하는 권장 방법입니다. 이 데코레이터가 적용된 메서드 내부에서 발생하는 모든 리포지토리 호출은 동일한 MongoDB 세션을 공유합니다.
|
|
74
83
|
|
|
75
|
-
```
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
120
|
+
### 요청 트랜잭션 인터셉터 호환성
|
|
91
121
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
138
|
+
새 비즈니스 작업에는 서비스 계층 `@Transaction()`을 우선 사용하세요. 기존 request-wide boundary를 migration하는 동안에만 이 interceptor를 유지하고, request orchestration에서 경계를 명시해야 한다면 `requestTransaction(...)` 직접 호출로 교체하세요.
|
|
103
139
|
|
|
104
|
-
|
|
140
|
+
### 수동 트랜잭션과 currentSession()
|
|
105
141
|
|
|
106
|
-
|
|
142
|
+
`MongooseConnection`은 활성 MongoDB 세션에 접근하기 위한 `currentSession()`과 루트 연결 handle에 접근하기 위한 `current()` 메서드를 제공합니다. 외부 유틸리티에 세션을 전달하거나 복잡한 수동 처리가 필요한 경우 escape hatch로 사용하세요.
|
|
107
143
|
|
|
108
|
-
|
|
144
|
+
```ts
|
|
145
|
+
import { MongooseConnection } from '@fluojs/mongoose';
|
|
109
146
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
|
|
147
|
+
export class AdvancedRepository {
|
|
148
|
+
constructor(private readonly conn: MongooseConnection) {}
|
|
113
149
|
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
+
### Manual Transactions and currentSession()
|
|
98
138
|
|
|
99
|
-
|
|
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 {
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
|
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
|
package/dist/connection.d.ts
CHANGED
|
@@ -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;
|
package/dist/connection.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/connection.js
CHANGED
|
@@ -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
|
|
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
|
|
149
|
-
if (
|
|
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
|
|
261
|
+
return await raceWithAbortAndDrainCallback(fn, abortContext.signal);
|
|
155
262
|
} finally {
|
|
156
263
|
abortContext.cleanup();
|
|
157
|
-
|
|
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
|
-
|
|
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
|
|
284
|
+
return await raceWithAbortAndDrainCallback(fn, abortContext.signal);
|
|
173
285
|
}
|
|
174
|
-
return await this.runManualSessionTransaction(resolvedSession, () =>
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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);
|
package/dist/transaction.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
9
|
+
* Wraps a service method in a `MongooseConnection.transaction(...)` boundary.
|
|
6
10
|
*
|
|
7
11
|
* @remarks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
35
|
+
* Runs the downstream handler inside the compatibility request transaction.
|
|
16
36
|
*
|
|
17
|
-
* @param context Interceptor context
|
|
37
|
+
* @param context Interceptor context containing the request cancellation signal.
|
|
18
38
|
* @param next Downstream handler chain.
|
|
19
|
-
* @returns The downstream
|
|
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;
|
|
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"}
|
package/dist/transaction.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
76
|
+
* Compatibility HTTP interceptor that opens a Mongoose request transaction around a routed handler.
|
|
12
77
|
*
|
|
13
78
|
* @remarks
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
94
|
+
* Runs the downstream handler inside the compatibility request transaction.
|
|
27
95
|
*
|
|
28
|
-
* @param context Interceptor context
|
|
96
|
+
* @param context Interceptor context containing the request cancellation signal.
|
|
29
97
|
* @param next Downstream handler chain.
|
|
30
|
-
* @returns The downstream
|
|
98
|
+
* @returns The downstream result after the request transaction settles.
|
|
31
99
|
*/
|
|
32
100
|
async intercept(context, next) {
|
|
33
|
-
return this.connection.requestTransaction(
|
|
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
|
|
3
|
+
* Minimal Mongoose connection seam that optionally supports session transaction APIs.
|
|
4
4
|
*
|
|
5
5
|
* @remarks
|
|
6
|
-
* Fluo
|
|
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
|
|
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
|
*
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
40
|
-
"@fluojs/http": "^
|
|
41
|
-
"@fluojs/di": "^
|
|
42
|
-
"@fluojs/runtime": "^
|
|
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"
|