@fluojs/mongoose 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +63 -36
- package/README.md +61 -27
- package/dist/connection.d.ts +9 -0
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +107 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +3 -4
- package/dist/transaction.d.ts +15 -20
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +60 -34
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
- [빠른 시작](#빠른-시작)
|
|
12
12
|
- [라이프사이클과 종료](#라이프사이클과-종료)
|
|
13
13
|
- [공통 패턴](#공통-패턴)
|
|
14
|
+
- [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
|
|
15
|
+
- [수동 트랜잭션과 currentSession()](#수동-트랜잭션과-currentsession)
|
|
14
16
|
- [공개 API](#공개-api)
|
|
15
17
|
- [관련 패키지](#관련-패키지)
|
|
16
18
|
- [예제 소스](#예제-소스)
|
|
@@ -26,7 +28,7 @@ pnpm add mongoose
|
|
|
26
28
|
|
|
27
29
|
- Mongoose를 나머지 애플리케이션과 같은 DI 및 라이프사이클 모델에 연결하고 싶을 때.
|
|
28
30
|
- 모든 서비스에서 MongoDB 세션과 트랜잭션을 임시 배관 코드 없이 하나의 wrapper로 다루고 싶을 때.
|
|
29
|
-
-
|
|
31
|
+
- 애플리케이션이 이미 concrete Mongoose connection을 생성·구성하고 있고, fluo가 그 ownership을 대체하지 않고 관측하기를 원할 때.
|
|
30
32
|
|
|
31
33
|
## 빠른 시작
|
|
32
34
|
|
|
@@ -54,78 +56,103 @@ class AppModule {}
|
|
|
54
56
|
|
|
55
57
|
## 라이프사이클과 종료
|
|
56
58
|
|
|
57
|
-
`MongooseModule`은 `MongooseConnection`을 fluo 애플리케이션 라이프사이클에 등록합니다. 이 패키지는 원본 Mongoose 연결을 직접 생성하거나 소유하지 않습니다. 애플리케이션 종료 시 외부 연결을 닫아야 한다면 `dispose` 훅을 전달하세요.
|
|
59
|
+
`MongooseModule`은 `MongooseConnection`을 fluo 애플리케이션 라이프사이클에 등록합니다. 이 패키지는 원본 Mongoose 연결을 직접 생성하거나 소유하지 않습니다. `connection`에는 concrete Mongoose connection object/function을 전달하고, 연결 문자열, pool, plugin, model compilation ownership은 애플리케이션에 남겨두며, 애플리케이션 종료 시 외부 연결을 닫아야 한다면 `dispose` 훅을 전달하세요.
|
|
58
60
|
|
|
59
|
-
종료
|
|
61
|
+
종료 절차는 트랜잭션 정리 순서를 보존하고, 종료가 시작된 뒤에는 새로운 수동 또는 요청 단위 트랜잭션 경계를 거부합니다.
|
|
60
62
|
|
|
61
|
-
1.
|
|
63
|
+
1. 열린 요청 단위 트랜잭션은 `Application shutdown interrupted an open request transaction.`으로 abort됩니다.
|
|
62
64
|
2. 활성 ambient session은 transaction callback과 session cleanup이 settle될 때까지 추적됩니다.
|
|
63
65
|
3. 해당 Mongoose 세션은 `abortTransaction()`과 `endSession()` 정리를 끝냅니다.
|
|
64
66
|
4. 설정한 `dispose(connection)` 훅은 활성 요청 트랜잭션과 ambient session scope가 모두 settled된 뒤에만 실행됩니다.
|
|
65
67
|
|
|
66
|
-
`createMongoosePlatformStatusSnapshot(...)
|
|
68
|
+
`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를 중단할 수 있습니다.
|
|
69
|
+
|
|
67
70
|
기존 수동 `transaction(...)` boundary 안에서 열린 중첩 `requestTransaction(...)` 호출은 ambient session을 재사용하고 `details.activeRequestTransactions`에 계속 표시되며, 종료 중에 abort되어 바깥 수동 transaction이 `dispose(connection)` 실행 전에 rollback할 수 있습니다.
|
|
68
71
|
|
|
69
72
|
## 공통 패턴
|
|
70
73
|
|
|
71
|
-
###
|
|
74
|
+
### 서비스 트랜잭션 경계 (@Transaction)
|
|
72
75
|
|
|
73
|
-
`
|
|
76
|
+
`@Transaction()` 데코레이터는 서비스 레이어에서 트랜잭션 경계를 정의하는 권장 방법입니다. 이 데코레이터가 적용된 메서드 내부에서 발생하는 모든 리포지토리 호출은 동일한 MongoDB 세션을 공유합니다.
|
|
74
77
|
|
|
75
|
-
```
|
|
76
|
-
import {
|
|
78
|
+
```ts
|
|
79
|
+
import { Transaction } from '@fluojs/mongoose';
|
|
80
|
+
import { UserRepository } from './user.repository';
|
|
81
|
+
|
|
82
|
+
export class UserService {
|
|
83
|
+
constructor(private readonly repo: UserRepository) {}
|
|
84
|
+
|
|
85
|
+
@Transaction()
|
|
86
|
+
async onboardUser(dto: CreateUserDto) {
|
|
87
|
+
const user = await this.repo.create(dto);
|
|
88
|
+
await this.repo.initProfile(user._id);
|
|
89
|
+
return user;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
77
92
|
|
|
78
93
|
export class UserRepository {
|
|
79
94
|
constructor(private readonly conn: MongooseConnection) {}
|
|
80
95
|
|
|
81
|
-
async
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
async create(data: any) {
|
|
97
|
+
// @Transaction() 내부에서 conn.model()은 세션 인지형 facade를 반환합니다.
|
|
98
|
+
// create, find, findOne, aggregate, bulkWrite 등의 작업은
|
|
99
|
+
// 자동으로 활성 트랜잭션에 참여합니다.
|
|
100
|
+
return this.conn.model('User').create(data);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async initProfile(userId: any) {
|
|
104
|
+
return this.conn.model('Profile').create({ userId });
|
|
84
105
|
}
|
|
85
106
|
}
|
|
86
107
|
```
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
`@Transaction()` 메서드 호출은 재진입(reentrant)이 가능합니다. 데코레이터가 적용된 메서드가 다른 데코레이터 적용 메서드를 호출하더라도 하나의 동일한 MongoDB 세션 안에서 실행됩니다. 참고로 v1에서 `doc.save()`는 자동으로 세션을 주입하지 않으므로, 자동 트랜잭션 참여가 필요하다면 지원되는 facade 작업(`model.create()`, `model.find()`, `model.findOne()`, `model.aggregate()`, `model.bulkWrite()`)을 사용하세요.
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
### 수동 트랜잭션과 currentSession()
|
|
91
112
|
|
|
92
|
-
|
|
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
|
-
});
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
감싼 연결이 `connection.transaction(...)`을 구현하면 `startSession()`이 직접 노출되지 않아도 fluo는 이를 strict transaction boundary로 취급합니다. 그렇지 않고 연결이 `startSession()`을 구현하지 않으면 트랜잭션은 기본적으로 직접 실행으로 fallback합니다. fallback 대신 예외를 던지려면 `strictTransactions: true`를 설정합니다. 이때 오류 메시지는 `Transaction not supported: Mongoose connection does not implement startSession.`입니다.
|
|
113
|
+
`MongooseConnection`은 활성 MongoDB 세션에 접근하기 위한 `currentSession()`과 루트 연결 handle에 접근하기 위한 `current()` 메서드를 제공합니다. 외부 유틸리티에 세션을 전달하거나 복잡한 수동 처리가 필요한 경우 escape hatch로 사용하세요.
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
```ts
|
|
116
|
+
import { MongooseConnection } from '@fluojs/mongoose';
|
|
105
117
|
|
|
106
|
-
|
|
118
|
+
export class AdvancedRepository {
|
|
119
|
+
constructor(private readonly conn: MongooseConnection) {}
|
|
107
120
|
|
|
108
|
-
|
|
121
|
+
async customOperation() {
|
|
122
|
+
const session = this.conn.currentSession();
|
|
123
|
+
const User = this.conn.current().model('User');
|
|
124
|
+
|
|
125
|
+
// 명시적으로 세션 전달
|
|
126
|
+
return User.find({ status: 'active' }).session(session || null);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
109
130
|
|
|
110
|
-
|
|
111
|
-
import { UseInterceptors } from '@fluojs/http';
|
|
112
|
-
import { MongooseTransactionInterceptor } from '@fluojs/mongoose';
|
|
131
|
+
수동 트랜잭션 블록에는 `conn.transaction()`을 사용하세요:
|
|
113
132
|
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
```ts
|
|
134
|
+
await this.conn.transaction(async () => {
|
|
135
|
+
const User = this.conn.model('User');
|
|
136
|
+
await User.create([{ name: 'Ada' }]);
|
|
137
|
+
});
|
|
116
138
|
```
|
|
117
139
|
|
|
118
|
-
|
|
140
|
+
래핑된 연결이 `connection.transaction(...)`을 구현하고 있다면 fluo는 이를 엄격한 트랜잭션 경계로 취급합니다. 그렇지 않고 `startSession()`이 없는 경우 트랜잭션은 기본값(`strictTransactions: false`)에서 callback 직접 실행으로 fail-open합니다. 이 모드는 local fake나 staged migration에는 유용하지만 rollback 원자성은 제공하지 않습니다. MongoDB transaction 보장이 필요한 production 흐름에서는 `strictTransactions: true`를 설정하세요. 그러면 transaction 지원 누락이 readiness `not-ready`와 helper 예외로 드러납니다.
|
|
141
|
+
|
|
142
|
+
지원되는 facade 메서드에서 fluo는 기존 Mongoose 작업 옵션을 보존하고 올바른 options 인자에 ambient `{ session }`만 병합합니다. 활성 트랜잭션 내부에서 명시적으로 `{ session: null }`을 전달하거나 다른 세션 객체를 사용하면, 의도치 않은 트랜잭션 탈출을 방지하기 위해 세션 충돌 에러를 발생시킵니다.
|
|
119
143
|
|
|
120
144
|
## 공개 API
|
|
121
145
|
|
|
122
146
|
- `MongooseModule.forRoot(options)` / `MongooseModule.forRootAsync(options)`
|
|
123
147
|
- `MongooseConnection`
|
|
124
|
-
- `
|
|
148
|
+
- `MongooseConnection.createPlatformStatusSnapshot()` — platform observability surface를 위해 health/readiness, resource ownership, 활성 request/session drain 수, strict transaction 지원 진단을 보고합니다.
|
|
149
|
+
- `MongooseConnection.model(name, ...args)` — 트랜잭션 밖에서는 raw model을 반환하고, 활성 트랜잭션 안에서는 underlying Mongoose connection을 변형하지 않으면서 `create`, `find`, `findOne`, `aggregate`, `bulkWrite`에 세션을 주입하는 facade를 반환합니다.
|
|
150
|
+
- `Transaction`
|
|
125
151
|
- `MONGOOSE_CONNECTION`, `MONGOOSE_DISPOSE`, `MONGOOSE_OPTIONS`
|
|
126
152
|
- `createMongooseProviders(options)` — 호환성/수동 composition helper입니다. 애플리케이션-facing 등록에서는 module export와 provider visibility가 문서화된 namespace facade와 맞도록 `MongooseModule.forRoot(...)` 또는 `MongooseModule.forRootAsync(...)`를 우선 사용하세요.
|
|
127
153
|
- `createMongoosePlatformStatusSnapshot(...)`
|
|
128
154
|
- sync 및 async 등록 모두에서 `connection`은 실제 object/function handle이어야 하며, 누락된 handle은 모듈 등록 또는 async bootstrap 중 거부됩니다.
|
|
155
|
+
- `Transaction`은 서비스 계층 세션 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 `this.conn`, 데코레이터가 적용된 인스턴스 자체, 또는 하나의 고유한 중첩 `this.*.conn` collaborator를 resolve합니다. `MongooseConnection`이 다른 필드에 있거나 resolution이 모호하다면 accessor를 전달하세요.
|
|
129
156
|
|
|
130
157
|
### 관련 export 타입
|
|
131
158
|
|
|
@@ -139,7 +166,7 @@ HTTP interceptor 밖에서 같은 request-aware transaction boundary가 필요
|
|
|
139
166
|
## 관련 패키지
|
|
140
167
|
|
|
141
168
|
- `@fluojs/runtime`: 애플리케이션 라이프사이클 및 종료 훅을 관리합니다.
|
|
142
|
-
- `@fluojs/http`:
|
|
169
|
+
- `@fluojs/http`: 명시적 `requestTransaction(...)` 경계와 함께 사용할 수 있는 요청 라이프사이클 primitive를 제공합니다.
|
|
143
170
|
- `@fluojs/prisma` / `@fluojs/drizzle`: 대안 데이터베이스 통합 모듈입니다.
|
|
144
171
|
|
|
145
172
|
## 예제 소스
|
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ 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
|
+
- [Manual Transactions and currentSession()](#manual-transactions-and-currentsession)
|
|
14
16
|
- [Public API](#public-api)
|
|
15
17
|
- [Related Packages](#related-packages)
|
|
16
18
|
- [Example Sources](#example-sources)
|
|
@@ -26,7 +28,8 @@ pnpm add mongoose
|
|
|
26
28
|
|
|
27
29
|
- when Mongoose should plug into the same DI and application lifecycle as the rest of the app
|
|
28
30
|
- when MongoDB sessions and transactions need one shared wrapper instead of ad hoc session plumbing in every service
|
|
29
|
-
- when request-scoped transactions
|
|
31
|
+
- when request-scoped transactions need explicit `requestTransaction(...)` boundaries
|
|
32
|
+
- when an application already creates and configures its concrete Mongoose connection and wants fluo to observe, not replace, that ownership
|
|
30
33
|
|
|
31
34
|
## Quick Start
|
|
32
35
|
|
|
@@ -52,7 +55,7 @@ class AppModule {}
|
|
|
52
55
|
|
|
53
56
|
## Lifecycle and Shutdown
|
|
54
57
|
|
|
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.
|
|
58
|
+
`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
59
|
|
|
57
60
|
Shutdown preserves transaction cleanup order and rejects new manual or request-scoped transaction boundaries once shutdown begins:
|
|
58
61
|
|
|
@@ -61,62 +64,93 @@ Shutdown preserves transaction cleanup order and rejects new manual or request-s
|
|
|
61
64
|
3. Their Mongoose sessions finish `abortTransaction()` and `endSession()` cleanup.
|
|
62
65
|
4. The configured `dispose(connection)` hook runs only after active request transactions and ambient session scopes have settled.
|
|
63
66
|
|
|
64
|
-
`createMongoosePlatformStatusSnapshot(...)`
|
|
67
|
+
`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
68
|
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
69
|
|
|
67
70
|
## Common Patterns
|
|
68
71
|
|
|
69
|
-
###
|
|
72
|
+
### Service Transaction Boundary (@Transaction)
|
|
73
|
+
|
|
74
|
+
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
75
|
|
|
71
76
|
```ts
|
|
72
|
-
import {
|
|
77
|
+
import { Transaction } from '@fluojs/mongoose';
|
|
78
|
+
import { UserRepository } from './user.repository';
|
|
79
|
+
|
|
80
|
+
export class UserService {
|
|
81
|
+
constructor(private readonly repo: UserRepository) {}
|
|
82
|
+
|
|
83
|
+
@Transaction()
|
|
84
|
+
async onboardUser(dto: CreateUserDto) {
|
|
85
|
+
const user = await this.repo.create(dto);
|
|
86
|
+
await this.repo.initProfile(user._id);
|
|
87
|
+
return user;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
73
90
|
|
|
74
91
|
export class UserRepository {
|
|
75
92
|
constructor(private readonly conn: MongooseConnection) {}
|
|
76
93
|
|
|
77
|
-
async
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
async create(data: any) {
|
|
95
|
+
// model() returns a session-aware facade inside @Transaction().
|
|
96
|
+
// Operations like create, find, findOne, aggregate, and bulkWrite
|
|
97
|
+
// automatically participate in the ambient transaction.
|
|
98
|
+
return this.conn.model('User').create(data);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async initProfile(userId: any) {
|
|
102
|
+
return this.conn.model('Profile').create({ userId });
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
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.
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
await this.conn.transaction(async () => {
|
|
88
|
-
const session = this.conn.currentSession();
|
|
89
|
-
const User = this.conn.current().model('User');
|
|
109
|
+
### Manual Transactions and currentSession()
|
|
90
110
|
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
```
|
|
111
|
+
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.
|
|
94
112
|
|
|
95
|
-
|
|
113
|
+
```ts
|
|
114
|
+
import { MongooseConnection } from '@fluojs/mongoose';
|
|
115
|
+
|
|
116
|
+
export class AdvancedRepository {
|
|
117
|
+
constructor(private readonly conn: MongooseConnection) {}
|
|
96
118
|
|
|
97
|
-
|
|
119
|
+
async customOperation() {
|
|
120
|
+
const session = this.conn.currentSession();
|
|
121
|
+
const User = this.conn.current().model('User');
|
|
122
|
+
|
|
123
|
+
// Explicitly passing the session
|
|
124
|
+
return User.find({ status: 'active' }).session(session || null);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
98
128
|
|
|
99
|
-
|
|
129
|
+
Use `conn.transaction()` for manual transaction blocks:
|
|
100
130
|
|
|
101
131
|
```ts
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class UserController {}
|
|
132
|
+
await this.conn.transaction(async () => {
|
|
133
|
+
const User = this.conn.model('User');
|
|
134
|
+
await User.create([{ name: 'Ada' }]);
|
|
135
|
+
});
|
|
107
136
|
```
|
|
108
137
|
|
|
109
|
-
|
|
138
|
+
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. 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.
|
|
139
|
+
|
|
140
|
+
For supported facade methods, fluo preserves existing Mongoose operation options and only merges the ambient `{ session }` into the correct options argument. If a model call passes an explicit `{ session: null }` or a different session object inside an ambient transaction, fluo throws a session conflict error to prevent accidental transaction escapes.
|
|
110
141
|
|
|
111
142
|
## Public API
|
|
112
143
|
|
|
113
144
|
- `MongooseModule.forRoot(options)` / `MongooseModule.forRootAsync(options)`
|
|
114
145
|
- `MongooseConnection`
|
|
115
|
-
- `
|
|
146
|
+
- `MongooseConnection.createPlatformStatusSnapshot()` — reports health/readiness, resource ownership, active request/session drain counts, and strict transaction support diagnostics for platform observability surfaces.
|
|
147
|
+
- `MongooseConnection.model(name, ...args)` — returns the raw model outside transactions or a session-aware facade for `create`, `find`, `findOne`, `aggregate`, and `bulkWrite` inside an active transaction without mutating the underlying Mongoose connection.
|
|
148
|
+
- `Transaction`
|
|
116
149
|
- `MONGOOSE_CONNECTION`, `MONGOOSE_DISPOSE`, `MONGOOSE_OPTIONS`
|
|
117
150
|
- `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
151
|
- `createMongoosePlatformStatusSnapshot(...)`
|
|
119
152
|
- `connection` must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
|
|
153
|
+
- `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
154
|
|
|
121
155
|
### Related exported types
|
|
122
156
|
|
|
@@ -130,7 +164,7 @@ Use `MongooseConnection.requestTransaction(...)` directly when you need the same
|
|
|
130
164
|
## Related Packages
|
|
131
165
|
|
|
132
166
|
- `@fluojs/runtime`: manages startup and shutdown hooks
|
|
133
|
-
- `@fluojs/http`: provides
|
|
167
|
+
- `@fluojs/http`: provides request lifecycle primitives that can be paired with explicit `requestTransaction(...)` boundaries
|
|
134
168
|
- `@fluojs/prisma` and `@fluojs/drizzle`: alternate database integrations with different transaction models
|
|
135
169
|
|
|
136
170
|
## Example Sources
|
package/dist/connection.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { MongooseConnectionLike, MongooseHandleProvider, MongooseSessionLik
|
|
|
3
3
|
type MongooseRuntimeOptions = {
|
|
4
4
|
strictTransactions: boolean;
|
|
5
5
|
};
|
|
6
|
+
type MongooseModelLike = Record<PropertyKey, unknown>;
|
|
6
7
|
/**
|
|
7
8
|
* Session-aware Mongoose wrapper that integrates request scoping and shutdown handling with the Fluo runtime.
|
|
8
9
|
*
|
|
@@ -39,6 +40,14 @@ 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
|
+
* @param name Model name passed to the underlying Mongoose connection.
|
|
47
|
+
* @param args Additional model resolver arguments forwarded unchanged.
|
|
48
|
+
* @returns The real model outside transactions, or a model facade inside an active transaction boundary.
|
|
49
|
+
*/
|
|
50
|
+
model(name: string, ...args: unknown[]): MongooseModelLike;
|
|
42
51
|
/** Aborts active request transactions, waits for settlement, then runs the optional dispose hook. */
|
|
43
52
|
onApplicationShutdown(): Promise<void>;
|
|
44
53
|
/** Produces the shared persistence status snapshot for platform diagnostics surfaces. */
|
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;AAuBpB,KAAK,sBAAsB,GAAG;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B,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,EACpB,MAAM,YAAY,CAAC;AAuBpB,KAAK,sBAAsB,GAAG;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,KAAK,iBAAiB,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAyJtD;;;;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;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,iBAAiB;IAa1D,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"}
|
package/dist/connection.js
CHANGED
|
@@ -11,6 +11,95 @@ 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
|
+
const MONGOOSE_CREATE_OPTION_KEYS = new Set(['aggregateErrors', 'checkKeys', 'j', 'ordered', 'populate', 'safe', 'session', 'timestamps', 'validateBeforeSave', 'validateModifiedOnly', 'w', 'writeConcern', 'wtimeout']);
|
|
17
|
+
function hasExplicitSessionOption(value) {
|
|
18
|
+
return value !== null && typeof value === 'object' && 'session' in value;
|
|
19
|
+
}
|
|
20
|
+
function isObjectLike(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null || typeof value === 'function';
|
|
22
|
+
}
|
|
23
|
+
function isMongooseCreateOptionsCandidate(value) {
|
|
24
|
+
if (!isObjectLike(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
for (const key of MONGOOSE_CREATE_OPTION_KEYS) {
|
|
28
|
+
if (key in value) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function isEmptyCreateOptionsCandidate(value) {
|
|
35
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
36
|
+
}
|
|
37
|
+
function resolveCreateOptionsIndex(operationArgs) {
|
|
38
|
+
if (operationArgs.length >= 2 && isMongooseCreateOptionsCandidate(operationArgs[operationArgs.length - 1])) {
|
|
39
|
+
return operationArgs.length - 1;
|
|
40
|
+
}
|
|
41
|
+
if (operationArgs.length === 2 && Array.isArray(operationArgs[0])) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
if (operationArgs.length === 2 && isEmptyCreateOptionsCandidate(operationArgs[1])) {
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
return operationArgs.length;
|
|
48
|
+
}
|
|
49
|
+
function resolveOptionsIndex(operation, operationArgs) {
|
|
50
|
+
if (operation === 'create') {
|
|
51
|
+
return resolveCreateOptionsIndex(operationArgs);
|
|
52
|
+
}
|
|
53
|
+
if (!MODEL_OPERATIONS_WITH_PROJECTION.has(operation)) {
|
|
54
|
+
return operationArgs.length > 1 ? 1 : operationArgs.length;
|
|
55
|
+
}
|
|
56
|
+
if (operationArgs.length >= 3) {
|
|
57
|
+
return 2;
|
|
58
|
+
}
|
|
59
|
+
if (operationArgs.length === 2 && hasExplicitSessionOption(operationArgs[1])) {
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
if (operationArgs.length <= 1) {
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
return operationArgs.length;
|
|
66
|
+
}
|
|
67
|
+
function resolveSessionOptions(opts, ambient) {
|
|
68
|
+
const options = opts && typeof opts === 'object' ? opts : {};
|
|
69
|
+
if (options.session === null) {
|
|
70
|
+
throw new Error('Explicit session: null conflicts with ambient transaction session');
|
|
71
|
+
}
|
|
72
|
+
if (options.session !== undefined && options.session !== ambient) {
|
|
73
|
+
throw new Error('Explicit session conflicts with ambient transaction session');
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...options,
|
|
77
|
+
session: ambient
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function createAmbientSessionModelFacade(model, ambient) {
|
|
81
|
+
return new Proxy(model, {
|
|
82
|
+
get(target, prop, receiver) {
|
|
83
|
+
const value = Reflect.get(target, prop, receiver);
|
|
84
|
+
if (!MODEL_OPERATIONS_WITH_OPTIONS.has(prop) || typeof value !== 'function') {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
return (...args) => {
|
|
88
|
+
const operationArgs = [...args];
|
|
89
|
+
const optionsIndex = resolveOptionsIndex(prop, operationArgs);
|
|
90
|
+
operationArgs[optionsIndex] = resolveSessionOptions(operationArgs[optionsIndex], ambient);
|
|
91
|
+
return value.apply(target, operationArgs);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function resolveModelFactory(connection) {
|
|
97
|
+
if (!isObjectLike(connection)) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const modelConnection = connection;
|
|
101
|
+
return modelConnection.model;
|
|
102
|
+
}
|
|
14
103
|
async function executeSessionTransaction(session, fn) {
|
|
15
104
|
try {
|
|
16
105
|
await session.startTransaction();
|
|
@@ -77,6 +166,23 @@ class MongooseConnection {
|
|
|
77
166
|
return this.sessions.getStore();
|
|
78
167
|
}
|
|
79
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Returns a model from the root connection, injecting the ambient transaction session into conservative operations.
|
|
171
|
+
*
|
|
172
|
+
* @param name Model name passed to the underlying Mongoose connection.
|
|
173
|
+
* @param args Additional model resolver arguments forwarded unchanged.
|
|
174
|
+
* @returns The real model outside transactions, or a model facade inside an active transaction boundary.
|
|
175
|
+
*/
|
|
176
|
+
model(name, ...args) {
|
|
177
|
+
const modelFactory = resolveModelFactory(this.connection);
|
|
178
|
+
if (typeof modelFactory !== 'function') {
|
|
179
|
+
throw new Error('Mongoose connection does not implement model().');
|
|
180
|
+
}
|
|
181
|
+
const model = modelFactory.call(this.connection, name, ...args);
|
|
182
|
+
const ambient = this.currentSession();
|
|
183
|
+
return ambient ? createAmbientSessionModelFacade(model, ambient) : model;
|
|
184
|
+
}
|
|
185
|
+
|
|
80
186
|
/** Aborts active request transactions, waits for settlement, then runs the optional dispose hook. */
|
|
81
187
|
async onApplicationShutdown() {
|
|
82
188
|
this.lifecycleState = 'shutting-down';
|
|
@@ -117,11 +223,11 @@ class MongooseConnection {
|
|
|
117
223
|
* @returns The callback result after the session transaction finishes or the direct-execution fallback completes.
|
|
118
224
|
*/
|
|
119
225
|
async transaction(fn) {
|
|
226
|
+
this.assertTransactionsAvailable();
|
|
120
227
|
const currentSession = this.sessions.getStore();
|
|
121
228
|
if (currentSession) {
|
|
122
229
|
return fn();
|
|
123
230
|
}
|
|
124
|
-
this.assertTransactionsAvailable();
|
|
125
231
|
if (typeof this.connection.transaction === 'function') {
|
|
126
232
|
return this.runConnectionTransaction(fn);
|
|
127
233
|
}
|
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
|
|
33
|
+
* @returns A module definition that exports `MongooseConnection`.
|
|
34
34
|
*/
|
|
35
35
|
static forRoot<TConnection extends MongooseConnectionLike>(options: MongooseModuleOptions<TConnection>): ModuleType;
|
|
36
36
|
/**
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAahF;;;;;;GAMG;AACH,MAAM,MAAM,0BAA0B,CAAC,WAAW,SAAS,sBAAsB,IAAI,kBAAkB,CACrG,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,CACnD,GAAG,IAAI,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;AA2EvD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,SAAS,sBAAsB,EAChF,OAAO,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAC1C,QAAQ,EAAE,CAOZ;AA0BD;;GAEG;AACH,qBAAa,cAAc;IACzB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,SAAS,sBAAsB,EAAE,OAAO,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAAG,UAAU;IAInH;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CAAC,WAAW,SAAS,sBAAsB,EAC5D,OAAO,EAAE,0BAA0B,CAAC,WAAW,CAAC,GAC/C,UAAU;CAGd"}
|
package/dist/module.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { defineModule } from '@fluojs/runtime';
|
|
2
2
|
import { MongooseConnection } from './connection.js';
|
|
3
3
|
import { MONGOOSE_CONNECTION, MONGOOSE_DISPOSE, MONGOOSE_OPTIONS } from './tokens.js';
|
|
4
|
-
import { MongooseTransactionInterceptor } from './transaction.js';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Async registration options accepted by `MongooseModule.forRootAsync(...)`.
|
|
@@ -12,7 +11,7 @@ import { MongooseTransactionInterceptor } from './transaction.js';
|
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
const MONGOOSE_NORMALIZED_OPTIONS = Symbol('fluo.mongoose.normalized-options');
|
|
15
|
-
const MONGOOSE_MODULE_EXPORTS = [MongooseConnection
|
|
14
|
+
const MONGOOSE_MODULE_EXPORTS = [MongooseConnection];
|
|
16
15
|
function isObjectLike(value) {
|
|
17
16
|
return typeof value === 'object' && value !== null || typeof value === 'function';
|
|
18
17
|
}
|
|
@@ -43,7 +42,7 @@ function createMongooseRuntimeProviders(normalizedOptionsProvider) {
|
|
|
43
42
|
inject: [MONGOOSE_NORMALIZED_OPTIONS],
|
|
44
43
|
provide: MONGOOSE_OPTIONS,
|
|
45
44
|
useFactory: options => createRuntimeOptionsProviderValue(options.strictTransactions)
|
|
46
|
-
}, MongooseConnection
|
|
45
|
+
}, MongooseConnection];
|
|
47
46
|
}
|
|
48
47
|
function createMongooseProvidersAsync(options) {
|
|
49
48
|
const factory = options.useFactory;
|
|
@@ -105,7 +104,7 @@ export class MongooseModule {
|
|
|
105
104
|
* Registers Mongoose providers from static options.
|
|
106
105
|
*
|
|
107
106
|
* @param options Mongoose module options with connection handle, optional dispose hook, and strict transaction mode.
|
|
108
|
-
* @returns A module definition that exports `MongooseConnection
|
|
107
|
+
* @returns A module definition that exports `MongooseConnection`.
|
|
109
108
|
*/
|
|
110
109
|
static forRoot(options) {
|
|
111
110
|
return buildMongooseModule(options);
|
package/dist/transaction.d.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
type TransactionConnection = {
|
|
2
|
+
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
3
|
+
};
|
|
4
|
+
type TransactionMethod<THost, TArgs extends unknown[], TResult> = (this: THost, ...args: TArgs) => Promise<TResult>;
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Wraps a service method in a `MongooseConnection.transaction(...)` boundary.
|
|
6
7
|
*
|
|
7
8
|
* @remarks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* This is a TC39 standard method decorator. By default it uses `this.conn` when present, the decorated instance
|
|
10
|
+
* itself when it is transaction-capable, or one unique nested `this.*.conn` collaborator. Pass an accessor when the
|
|
11
|
+
* connection lives under a different field or more than one nested collaborator exposes a connection; the decorator
|
|
12
|
+
* does not bind arbitrary transaction-capable properties to avoid selecting the wrong persistence handle.
|
|
13
|
+
* Nested decorated calls reuse the ambient Mongoose session through `MongooseConnection.transaction(...)`.
|
|
14
|
+
*
|
|
15
|
+
* @param accessor Optional connection resolver for the decorated service instance.
|
|
16
|
+
* @returns A standard method decorator that executes the original method inside a Mongoose transaction.
|
|
10
17
|
*/
|
|
11
|
-
export declare
|
|
12
|
-
|
|
13
|
-
constructor(connection: MongooseConnection<MongooseConnectionLike>);
|
|
14
|
-
/**
|
|
15
|
-
* Runs the downstream handler inside a Mongoose request transaction boundary.
|
|
16
|
-
*
|
|
17
|
-
* @param context Interceptor context that supplies the request abort signal.
|
|
18
|
-
* @param next Downstream handler chain.
|
|
19
|
-
* @returns The downstream handler result after the request transaction settles.
|
|
20
|
-
*/
|
|
21
|
-
intercept(context: InterceptorContext, next: {
|
|
22
|
-
handle(): Promise<unknown>;
|
|
23
|
-
}): Promise<unknown>;
|
|
24
|
-
}
|
|
18
|
+
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>;
|
|
19
|
+
export {};
|
|
25
20
|
//# sourceMappingURL=transaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,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"}
|
package/dist/transaction.js
CHANGED
|
@@ -1,39 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { Inject } from '@fluojs/core';
|
|
8
|
-
import { MongooseConnection } from './connection.js';
|
|
9
|
-
let _MongooseTransactionI;
|
|
10
|
-
/**
|
|
11
|
-
* HTTP interceptor that wraps each request in a Mongoose request transaction boundary.
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* Pair this with repository/service code that reads `MongooseConnection.current()` and `currentSession()` so downstream
|
|
15
|
-
* calls share the same request-scoped session.
|
|
16
|
-
*/
|
|
17
|
-
class MongooseTransactionInterceptor {
|
|
18
|
-
static {
|
|
19
|
-
[_MongooseTransactionI, _initClass] = _applyDecs(this, [Inject(MongooseConnection)], []).c;
|
|
1
|
+
function isTransactionConnection(value) {
|
|
2
|
+
return (typeof value === 'object' && value !== null || typeof value === 'function') && typeof value.transaction === 'function';
|
|
3
|
+
}
|
|
4
|
+
function collectNestedConnCandidates(self) {
|
|
5
|
+
if ((typeof self !== 'object' || self === null) && typeof self !== 'function') {
|
|
6
|
+
return [];
|
|
20
7
|
}
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
const candidates = new Set();
|
|
9
|
+
for (const value of Object.values(self)) {
|
|
10
|
+
if ((typeof value !== 'object' || value === null) && typeof value !== 'function') {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const nestedConn = value.conn;
|
|
14
|
+
if (isTransactionConnection(nestedConn)) {
|
|
15
|
+
candidates.add(nestedConn);
|
|
16
|
+
}
|
|
23
17
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
return Array.from(candidates);
|
|
19
|
+
}
|
|
20
|
+
function resolveTransactionConnection(self, accessor) {
|
|
21
|
+
if (accessor) {
|
|
22
|
+
const connection = accessor(self);
|
|
23
|
+
if (isTransactionConnection(connection)) {
|
|
24
|
+
return connection;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('Mongoose @Transaction() accessor did not return a transaction-capable connection.');
|
|
27
|
+
}
|
|
28
|
+
const fallbackHost = self;
|
|
29
|
+
if (isTransactionConnection(fallbackHost.conn)) {
|
|
30
|
+
return fallbackHost.conn;
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (isTransactionConnection(self)) {
|
|
33
|
+
return self;
|
|
37
34
|
}
|
|
35
|
+
const nestedConnCandidates = collectNestedConnCandidates(self);
|
|
36
|
+
if (nestedConnCandidates.length === 1) {
|
|
37
|
+
return nestedConnCandidates[0];
|
|
38
|
+
}
|
|
39
|
+
if (nestedConnCandidates.length > 1) {
|
|
40
|
+
throw new Error('Mongoose @Transaction() found multiple nested this.*.conn candidates; pass an accessor.');
|
|
41
|
+
}
|
|
42
|
+
throw new Error('Mongoose @Transaction() could not resolve a transaction-capable connection from this.conn.');
|
|
38
43
|
}
|
|
39
|
-
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wraps a service method in a `MongooseConnection.transaction(...)` boundary.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* This is a TC39 standard method decorator. By default it uses `this.conn` when present, the decorated instance
|
|
50
|
+
* itself when it is transaction-capable, or one unique nested `this.*.conn` collaborator. Pass an accessor when the
|
|
51
|
+
* connection lives under a different field or more than one nested collaborator exposes a connection; the decorator
|
|
52
|
+
* does not bind arbitrary transaction-capable properties to avoid selecting the wrong persistence handle.
|
|
53
|
+
* Nested decorated calls reuse the ambient Mongoose session through `MongooseConnection.transaction(...)`.
|
|
54
|
+
*
|
|
55
|
+
* @param accessor Optional connection resolver for the decorated service instance.
|
|
56
|
+
* @returns A standard method decorator that executes the original method inside a Mongoose transaction.
|
|
57
|
+
*/
|
|
58
|
+
export function Transaction(accessor) {
|
|
59
|
+
return function transactionDecorator(value, _context) {
|
|
60
|
+
return async function transactionWrappedMethod(...args) {
|
|
61
|
+
const connection = resolveTransactionConnection(this, accessor);
|
|
62
|
+
return connection.transaction(() => value.apply(this, args));
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -48,6 +48,14 @@ export interface MongooseHandleProvider<TConnection extends MongooseConnectionLi
|
|
|
48
48
|
current(): TConnection;
|
|
49
49
|
/** Returns the ambient Mongoose session for the current async context, when one exists. */
|
|
50
50
|
currentSession(): MongooseSessionLike | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Returns a Mongoose model handle, or a session-aware facade inside an active transaction.
|
|
53
|
+
*
|
|
54
|
+
* @param name Model name passed to the underlying Mongoose connection.
|
|
55
|
+
* @param args Additional model resolver arguments forwarded unchanged.
|
|
56
|
+
* @returns The root model outside transactions, or a model facade inside an active transaction boundary.
|
|
57
|
+
*/
|
|
58
|
+
model(name: string, ...args: unknown[]): Record<PropertyKey, unknown>;
|
|
51
59
|
/**
|
|
52
60
|
* Opens a Mongoose session transaction boundary around `fn`.
|
|
53
61
|
*
|
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;;;;;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;;;;;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;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACtE;;;;;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
|
|
12
|
+
"version": "1.1.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@fluojs/core": "^1.0.3",
|
|
40
|
-
"@fluojs/http": "^1.1.
|
|
41
|
-
"@fluojs/di": "^1.0
|
|
42
|
-
"@fluojs/runtime": "^1.1.
|
|
40
|
+
"@fluojs/http": "^1.1.2",
|
|
41
|
+
"@fluojs/di": "^1.1.0",
|
|
42
|
+
"@fluojs/runtime": "^1.1.8"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"mongoose": ">=7.0.0"
|