@fluojs/prisma 1.0.0-beta.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -14,6 +14,7 @@ fluo 애플리케이션을 위한 Prisma 라이프사이클 및 ALS 기반 트
14
14
  - [여러 클라이언트를 위한 이름 있는 등록](#여러-클라이언트를-위한-이름-있는-등록)
15
15
  - [수동 트랜잭션](#수동-트랜잭션)
16
16
  - [자동 요청 트랜잭션](#자동-요청-트랜잭션)
17
+ - [종료와 status 계약](#종료와-status-계약)
17
18
  - [비동기 설정과 격리](#비동기-설정과-격리)
18
19
  - [수동 모듈 조합](#수동-모듈-조합)
19
20
  - [공개 API 개요](#공개-api-개요)
@@ -135,6 +136,18 @@ class UserController {
135
136
 
136
137
  `PrismaTransactionInterceptor`는 기본 이름 없는 `PrismaService`를 대상으로 합니다. 이름 있는 다중 클라이언트 등록에서는 해당 이름의 `PrismaService`를 주입한 뒤 필요한 위치에서 명시적으로 `transaction()` / `requestTransaction()` 경계를 여세요.
137
138
 
139
+ ### 종료와 status 계약
140
+
141
+ `PrismaService.requestTransaction(...)`은 정상 serving 전과 중에는 사용할 수 있지만, 애플리케이션 종료가 시작된 뒤에는 새 요청 범위 트랜잭션을 거부합니다. 종료 중에는 열린 요청 트랜잭션을 abort하고, 바깥 트랜잭션 경계가 settle될 때까지 추적한 뒤, 모두 drain한 다음 `$disconnect()`를 실행합니다. 기존 수동 `transaction(...)` 경계 안에서 열린 중첩 `requestTransaction(...)` 호출도 여기에 포함됩니다. 이 호출은 ambient Prisma transaction client를 재사용하고, 바깥 경계가 끝날 때까지 `details.activeRequestTransactions`에 남으며, 두 번째 Prisma 트랜잭션을 열지 않습니다.
142
+
143
+ `createPrismaPlatformStatusSnapshot(...)`와 `PrismaService.createPlatformStatusSnapshot()`은 같은 라이프사이클 계약을 진단 surface에 노출합니다.
144
+
145
+ - `readiness.status`는 Prisma가 종료 중이거나 stopped 상태일 때, 그리고 `strictTransactions`가 켜져 있는데 `$transaction(...)`을 지원하지 않을 때 `not-ready`입니다.
146
+ - `health.status`는 종료 중 요청 트랜잭션을 drain하는 동안 `degraded`, disconnect 이후 `unhealthy`입니다.
147
+ - `details.activeRequestTransactions`, `details.lifecycleState`, `details.strictTransactions`, `details.supportsTransaction`, `details.transactionAbortSignalSupport`는 현재 요청 트랜잭션과 트랜잭션 capability 상태를 설명합니다.
148
+ - `details.transactionContext: 'als'`는 요청 및 서비스 트랜잭션 경계가 사용하는 async-local transaction context를 식별합니다.
149
+ - `ownership.externallyManaged: false`와 `ownership.ownsResources: true`는 패키지가 fluo 애플리케이션 라이프사이클 안에서 등록된 클라이언트의 `$connect()` / `$disconnect()` lifecycle hook을 소유한다는 의미입니다.
150
+
138
151
  ### 비동기 설정과 격리
139
152
 
140
153
  주입된 설정이나 다른 비동기 소스에서 Prisma 클라이언트를 만들어야 할 때는 `PrismaModule.forRootAsync(...)`를 사용하세요. 비동기 factory는 애플리케이션 컨테이너마다 한 번 resolve되며, 테스트나 여러 앱을 띄우는 프로세스에서 같은 모듈 정의를 재사용하더라도 별도 bootstrap 사이에서 공유되지 않습니다.
package/README.md CHANGED
@@ -14,6 +14,7 @@ Prisma lifecycle and ALS-backed transaction context for fluo applications. Conne
14
14
  - [Named Registrations for Multiple Clients](#named-registrations-for-multiple-clients)
15
15
  - [Manual Transactions](#manual-transactions)
16
16
  - [Automatic Request Transactions](#automatic-request-transactions)
17
+ - [Shutdown and Status Contracts](#shutdown-and-status-contracts)
17
18
  - [Async Configuration and Isolation](#async-configuration-and-isolation)
18
19
  - [Manual Module Composition](#manual-module-composition)
19
20
  - [Public API Overview](#public-api-overview)
@@ -135,6 +136,18 @@ class UserController {
135
136
 
136
137
  `PrismaTransactionInterceptor` targets the default unnamed `PrismaService`. For named multi-client registrations, inject the corresponding named `PrismaService` and open explicit `transaction()` / `requestTransaction()` boundaries where needed.
137
138
 
139
+ ### Shutdown and Status Contracts
140
+
141
+ `PrismaService.requestTransaction(...)` is available before and during normal serving, but new request-scoped transactions are rejected once application shutdown has started. During shutdown, open request transactions are aborted, tracked until their outer transaction boundary has settled, and drained before `$disconnect()` runs. This includes nested `requestTransaction(...)` calls opened inside an existing manual `transaction(...)` boundary: they reuse the ambient Prisma transaction client, stay visible in `details.activeRequestTransactions` until the outer boundary finishes, and do not open a second Prisma transaction.
142
+
143
+ `createPrismaPlatformStatusSnapshot(...)` and `PrismaService.createPlatformStatusSnapshot()` expose the same lifecycle contract to diagnostics surfaces:
144
+
145
+ - `readiness.status` is `not-ready` while Prisma is shutting down or stopped, and when `strictTransactions` is enabled without `$transaction(...)` support.
146
+ - `health.status` is `degraded` while request transactions are draining during shutdown and `unhealthy` after disconnect.
147
+ - `details.activeRequestTransactions`, `details.lifecycleState`, `details.strictTransactions`, `details.supportsTransaction`, and `details.transactionAbortSignalSupport` describe the current request transaction and transaction-capability state.
148
+ - `details.transactionContext: 'als'` identifies the async-local transaction context used by request and service transaction boundaries.
149
+ - `ownership.externallyManaged: false` and `ownership.ownsResources: true` mean the package owns the registered client's `$connect()` / `$disconnect()` lifecycle hooks inside the fluo application lifecycle.
150
+
138
151
  ### Async Configuration and Isolation
139
152
 
140
153
  Use `PrismaModule.forRootAsync(...)` when the Prisma client must be created from injected configuration or another async source. The async factory is resolved once per application container and is not shared across separate bootstraps, even when the same module definition is reused in tests or multi-app processes.
package/dist/service.d.ts CHANGED
@@ -77,6 +77,10 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
77
77
  * error type/message depends on the runtime abort implementation.
78
78
  */
79
79
  requestTransaction<T>(fn: () => Promise<T>, signal?: AbortSignal, options?: TTransactionOptions): Promise<T>;
80
+ private runWithRequestTransactionClient;
81
+ private runNestedRequestTransaction;
82
+ private assertRequestTransactionsAvailable;
83
+ private throwIfRequestAborted;
80
84
  private runRequestTransactionWithAbortSignal;
81
85
  private canAttemptTransactionAbortSignalOption;
82
86
  private runTransactionWithAbortSignalFallback;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAKpB,UAAU,oBAAoB;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAcD;;;;;;GAMG;AACH,qBACa,aAAa,CACxB,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,CAE5D,YAAW,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,YAAY,EAAE,qBAAqB;IAQpH,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA+C;IAC5E,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,6BAA6B,CAA4C;IACjF,OAAO,CAAC,cAAc,CAAgE;gBAGnE,MAAM,EAAE,OAAO,EACf,cAAc,GAAE,oBAAoD;IAGvF;;;;;;;;;OASG;IACH,OAAO,IAAI,OAAO,GAAG,kBAAkB;YAIzB,wBAAwB;IA2BhC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5C;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;;;;;;;;;;;OAiBG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAQrF;;;;;;;;;;;;;;;;;;OAkBG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBlH,OAAO,CAAC,oCAAoC;IAY5C,OAAO,CAAC,sCAAsC;YAYhC,qCAAqC;IAyBnD,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,+BAA+B;CAGxC"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB,UAAU,oBAAoB;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAoBD;;;;;;GAMG;AACH,qBACa,aAAa,CACxB,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,CAE5D,YAAW,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,YAAY,EAAE,qBAAqB;IAQpH,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmE;IAChG,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,6BAA6B,CAA4C;IACjF,OAAO,CAAC,cAAc,CAAgE;gBAGnE,MAAM,EAAE,OAAO,EACf,cAAc,GAAE,oBAAoD;IAGvF;;;;;;;;;OASG;IACH,OAAO,IAAI,OAAO,GAAG,kBAAkB;YAIzB,wBAAwB;IAuChC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5C;;;;OAIG;IACH,4BAA4B;IAY5B;;;;;;;;;;;;;;;;;OAiBG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAQrF;;;;;;;;;;;;;;;;;;OAkBG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;YAkCpG,+BAA+B;YAuB/B,2BAA2B;IAqCzC,OAAO,CAAC,kCAAkC;IAM1C,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,oCAAoC;IAY5C,OAAO,CAAC,sCAAsC;YAYhC,qCAAqC;IAyBnD,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,+BAA+B;CAGxC"}
package/dist/service.js CHANGED
@@ -5,11 +5,12 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
5
5
  function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
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 { AsyncLocalStorage } from 'node:async_hooks';
8
- import { createRequestAbortContext, raceWithAbort, trackActiveRequestTransaction, untrackActiveRequestTransaction } from '@fluojs/runtime';
8
+ import { createAbortError, createRequestAbortContext, raceWithAbort, trackActiveRequestTransaction, untrackActiveRequestTransaction } from '@fluojs/runtime';
9
9
  import { Inject } from '@fluojs/core';
10
10
  import { createPrismaPlatformStatusSnapshot } from './status.js';
11
11
  import { PRISMA_CLIENT, PRISMA_OPTIONS } from './tokens.js';
12
12
  const NESTED_TRANSACTION_OPTIONS_NOT_SUPPORTED_ERROR = 'Nested Prisma transaction options are not supported because the active transaction context is reused.';
13
+ const REQUEST_TRANSACTION_UNAVAILABLE_ERROR = 'Prisma request transactions are not available during shutdown.';
13
14
  let _PrismaService;
14
15
  /**
15
16
  * Prisma runtime facade that owns lifecycle hooks and transaction context access.
@@ -44,7 +45,7 @@ class PrismaService {
44
45
  * @returns The request/transaction-scoped client when a transaction is active; otherwise the root client.
45
46
  */
46
47
  current() {
47
- return this.transactions.getStore() ?? this.client;
48
+ return this.transactions.getStore()?.client ?? this.client;
48
49
  }
49
50
  async runWithTransactionClient(fn, run, options) {
50
51
  if (this.transactions.getStore()) {
@@ -59,7 +60,17 @@ class PrismaService {
59
60
  }
60
61
  return fn();
61
62
  }
62
- return run(transactionClient => this.transactions.run(transactionClient, fn), options);
63
+ const deferredRequestTransactionHandles = new Set();
64
+ try {
65
+ return await run(transactionClient => this.transactions.run({
66
+ client: transactionClient,
67
+ deferredRequestTransactionHandles
68
+ }, fn), options);
69
+ } finally {
70
+ for (const handle of deferredRequestTransactionHandles) {
71
+ this.untrackActiveRequestTransaction(handle);
72
+ }
73
+ }
63
74
  }
64
75
  async onModuleInit() {
65
76
  if (typeof this.client.$connect === 'function') {
@@ -138,15 +149,72 @@ class PrismaService {
138
149
  * error type/message depends on the runtime abort implementation.
139
150
  */
140
151
  async requestTransaction(fn, signal, options) {
152
+ const current = this.transactions.getStore();
153
+ if (current) {
154
+ if (options !== undefined) {
155
+ throw new Error(NESTED_TRANSACTION_OPTIONS_NOT_SUPPORTED_ERROR);
156
+ }
157
+ return this.runNestedRequestTransaction(current, fn, signal);
158
+ }
159
+ this.assertRequestTransactionsAvailable();
141
160
  const abortContext = createRequestAbortContext(signal);
142
161
  const active = this.trackActiveRequestTransaction(abortContext.controller);
143
162
  try {
144
- return await this.runWithTransactionClient(() => raceWithAbort(fn, abortContext.signal), (callback, transactionOptions) => this.runRequestTransactionWithAbortSignal(callback, abortContext.signal, transactionOptions), options);
163
+ const result = await this.runWithRequestTransactionClient(() => raceWithAbort(fn, abortContext.signal), (callback, transactionOptions) => this.runRequestTransactionWithAbortSignal(callback, abortContext.signal, transactionOptions), options, abortContext.signal);
164
+ this.throwIfRequestAborted(abortContext.signal);
165
+ return result;
145
166
  } finally {
146
167
  abortContext.cleanup();
147
168
  this.untrackActiveRequestTransaction(active);
148
169
  }
149
170
  }
171
+ async runWithRequestTransactionClient(fn, run, options, signal) {
172
+ if (typeof this.client.$transaction !== 'function') {
173
+ if (this.serviceOptions.strictTransactions) {
174
+ throw new Error('Transaction not supported: Prisma client does not implement $transaction.');
175
+ }
176
+ return fn();
177
+ }
178
+ return run(transactionClient => this.transactions.run({
179
+ client: transactionClient,
180
+ requestAbortSignal: signal
181
+ }, fn), options);
182
+ }
183
+ async runNestedRequestTransaction(current, fn, signal) {
184
+ if (current.requestAbortSignal) {
185
+ if (signal) {
186
+ return raceWithAbort(fn, signal);
187
+ }
188
+ return fn();
189
+ }
190
+ this.assertRequestTransactionsAvailable();
191
+ const abortContext = createRequestAbortContext(signal);
192
+ const active = this.trackActiveRequestTransaction(abortContext.controller);
193
+ current.deferredRequestTransactionHandles?.add(active);
194
+ try {
195
+ const result = await this.transactions.run({
196
+ client: current.client,
197
+ requestAbortSignal: abortContext.signal
198
+ }, () => raceWithAbort(fn, abortContext.signal));
199
+ this.throwIfRequestAborted(abortContext.signal);
200
+ return result;
201
+ } finally {
202
+ abortContext.cleanup();
203
+ if (!current.deferredRequestTransactionHandles) {
204
+ this.untrackActiveRequestTransaction(active);
205
+ }
206
+ }
207
+ }
208
+ assertRequestTransactionsAvailable() {
209
+ if (this.lifecycleState === 'shutting-down' || this.lifecycleState === 'stopped') {
210
+ throw new Error(REQUEST_TRANSACTION_UNAVAILABLE_ERROR);
211
+ }
212
+ }
213
+ throwIfRequestAborted(signal) {
214
+ if (signal.aborted) {
215
+ throw createAbortError(signal.reason);
216
+ }
217
+ }
150
218
  runRequestTransactionWithAbortSignal(callback, signal, options) {
151
219
  if (!this.canAttemptTransactionAbortSignalOption(options)) {
152
220
  return this.client.$transaction(callback, options);
package/dist/status.js CHANGED
@@ -63,8 +63,8 @@ export function createPrismaPlatformStatusSnapshot(input) {
63
63
  },
64
64
  health: createHealth(input),
65
65
  ownership: {
66
- externallyManaged: true,
67
- ownsResources: false
66
+ externallyManaged: false,
67
+ ownsResources: true
68
68
  },
69
69
  readiness: createReadiness(input)
70
70
  };
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "transaction",
10
10
  "als"
11
11
  ],
12
- "version": "1.0.0-beta.5",
12
+ "version": "1.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,11 +36,11 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0-beta.5",
40
- "@fluojs/validation": "^1.0.0-beta.4",
41
- "@fluojs/http": "^1.0.0-beta.10",
42
- "@fluojs/di": "^1.0.0-beta.7",
43
- "@fluojs/runtime": "^1.0.0-beta.12"
39
+ "@fluojs/core": "^1.0.0",
40
+ "@fluojs/validation": "^1.0.0",
41
+ "@fluojs/http": "^1.0.0",
42
+ "@fluojs/di": "^1.0.0",
43
+ "@fluojs/runtime": "^1.0.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@prisma/client": ">=5.0.0"