@mosaic-code/prisma-select-for-update 0.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/LICENSE ADDED
@@ -0,0 +1,207 @@
1
+ # Do No Harm License
2
+
3
+ ## 1. Preamble
4
+
5
+ Most software today is developed with little to no thought of how it will be used, or the
6
+ consequences for our society and planet.
7
+
8
+ As software developers, we engineer the infrastructure of the 21st century. We recognise that our
9
+ infrastructure has great power to shape the world and the lives of those we share it with, and we
10
+ choose to consciously take responsibility for the social and environmental impacts of what we build.
11
+
12
+ We envisage a world free from injustice, inequality, and the reckless destruction of lives and our
13
+ planet. We reject slavery in all its forms, whether by force, indebtedness, or by algorithms that
14
+ hack human vulnerabilities. We seek a world where humankind is at peace with our neighbours, nature,
15
+ and ourselves. We want our work to enrich the physical, mental and spiritual wellbeing of all
16
+ society.
17
+
18
+ We build software to further this vision of a just world, or at the very least, to not put that
19
+ vision further from reach.
20
+
21
+ ## 2. Definitions
22
+
23
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by
24
+ Sections 1 through 9 of this document.
25
+
26
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is
27
+ granting the License.
28
+
29
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are
30
+ controlled by, or are under common control with that entity. For the purposes of this definition,
31
+ "control" means (i) the power, direct or indirect, to cause the direction or management of such
32
+ entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
33
+ outstanding shares, or (iii) beneficial ownership of such entity.
34
+
35
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this
36
+ License.
37
+
38
+ "Source" form shall mean the preferred form for making modifications, including but not limited to
39
+ software source code, documentation source, and configuration files.
40
+
41
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a
42
+ Source form, including but not limited to compiled object code, generated documentation, and
43
+ conversions to other media types.
44
+
45
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the
46
+ License, as indicated by a copyright notice that is included in or attached to the work (an example
47
+ is provided in the Appendix below).
48
+
49
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or
50
+ derived from) the Work and for which the editorial revisions, annotations, elaborations, or other
51
+ modifications represent, as a whole, an original work of authorship. For the purposes of this
52
+ License, Derivative Works shall not include works that remain separable from, or merely link (or
53
+ bind by name) to the interfaces of, the Work and Derivative Works thereof.
54
+
55
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any
56
+ modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted
57
+ to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity
58
+ authorized to submit on behalf of the copyright owner. For the purposes of this definition,
59
+ "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or
60
+ its representatives, including but not limited to communication on electronic mailing lists, source
61
+ code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor
62
+ for the purpose of discussing and improving the Work, but excluding communication that is
63
+ conspicuously marked or otherwise designated in writing by the copyright owner as "Not a
64
+ Contribution."
65
+
66
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a
67
+ Contribution has been received by Licensor and subsequently incorporated within the Work.
68
+
69
+ "Forests" shall mean 0.5 or more hectares of trees that were either planted more than 50 years ago
70
+ or were not planted by humans or human made equipment.
71
+
72
+ "Deforestation" shall mean the clearing, burning or destruction of 0.5 or more hectares of forests
73
+ within a 1 year period.
74
+
75
+ ## 3. Grant of Copyright License
76
+
77
+ Subject to the terms and conditions of this License, each Contributor hereby grants to You a
78
+ perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to
79
+ reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and
80
+ distribute the Work and such Derivative Works in Source or Object form.
81
+
82
+ ## 4. Grant of Patent License
83
+
84
+ Subject to the terms and conditions of this License, each Contributor hereby grants to You a
85
+ perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this
86
+ section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer
87
+ the Work, where such license applies only to those patent claims licensable by such Contributor that
88
+ are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s)
89
+ with the Work to which such Contribution(s) was submitted. If You institute patent litigation
90
+ against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or
91
+ a Contribution incorporated within the Work constitutes direct or contributory patent infringement,
92
+ then any patent licenses granted to You under this License for that Work shall terminate as of the
93
+ date such litigation is filed.
94
+
95
+ ## 5. Redistribution
96
+
97
+ You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with
98
+ or without modifications, and in Source or Object form, provided that You meet the following
99
+ conditions:
100
+
101
+ 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
102
+
103
+ 2. You must cause any modified files to carry prominent notices stating that You changed the
104
+ files; and
105
+
106
+ 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright,
107
+ patent, trademark, and attribution notices from the Source form of the Work, excluding those
108
+ notices that do not pertain to any part of the Derivative Works; and
109
+
110
+ 4. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
111
+ or promote products derived from this software without specific prior written permission; and
112
+
113
+ 5. This software must not be used by any organisation, website, product, or service that:
114
+ 1. promotes, lobbies for or derives a majority of income from:
115
+ 1. **abuses of human rights**:
116
+ * human trafficking
117
+ * sex trafficking
118
+ * slavery or indentured servitude
119
+ * discrimination based on age, gender, gender identity, race, sexuality, religion, nationality
120
+ * hate speech
121
+ 2. **environmental destruction**:
122
+ * the extraction or sale of fossil fuels
123
+ * the destruction of habitats for threatened or endangered species, including through deforestation or burning of forests
124
+ * the abuse, inhumane killing or neglect of animals under human control
125
+ * industrial processes that generate waste products that threaten life
126
+ 3. **conflict and war**:
127
+ * warfare
128
+ * war crimes
129
+ * weapons manufacturing
130
+ * violence (except when required to protect public safety)
131
+ 4. **addictive or destructive products and services**:
132
+ * gambling
133
+ * tobacco
134
+ * products that encourage adversely addictive behaviours
135
+
136
+ 2. dissuades, lobbies against, or derives a majority of income from actions that discourage or frustrate:
137
+ * peace
138
+ * access to the rights set out in the Universal Declaration of Human Rights and the Convention on the Rights of the Child
139
+ * democratic processes
140
+ * peaceful assembly and association (including worker associations)
141
+ * a sustainable environment
142
+ ; and
143
+
144
+ 5. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative
145
+ Works that You distribute must include a readable copy of the attribution notices contained
146
+ within such NOTICE file, excluding those notices that do not pertain to any part of the
147
+ Derivative Works, in at least one of the following places: within a NOTICE text file
148
+ distributed as part of the Derivative Works; within the Source form or documentation, if
149
+ provided along with the Derivative Works; or, within a display generated by the Derivative
150
+ Works, if and wherever such third-party notices normally appear. The contents of the NOTICE
151
+ file are for informational purposes only and do not modify the License. You may add Your own
152
+ attribution notices within Derivative Works that You distribute, alongside or as an addendum to
153
+ the NOTICE text from the Work, provided that such additional attribution notices cannot be
154
+ construed as modifying the License.
155
+
156
+ You may add Your own copyright statement to Your modifications and may provide additional or
157
+ different license terms and conditions for use, reproduction, or distribution of Your modifications,
158
+ or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of
159
+ the Work otherwise complies with the conditions stated in this License.
160
+
161
+ ## 6. Submission of Contributions
162
+
163
+ Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the
164
+ Work by You to the Licensor shall be under the terms and conditions of this License, without any
165
+ additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify
166
+ the terms of any separate license agreement you may have executed with Licensor regarding such
167
+ Contributions.
168
+
169
+ ## 7. Trademarks
170
+
171
+ This License does not grant permission to use the trade names, trademarks, service marks, or product
172
+ names of the Licensor, except as required for reasonable and customary use in describing the origin
173
+ of the Work and reproducing the content of the NOTICE file.
174
+
175
+ ## 8. Disclaimer of Warranty
176
+
177
+ Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each
178
+ Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
179
+ KIND, either express or implied, including, without limitation, any warranties or conditions of
180
+ TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely
181
+ responsible for determining the appropriateness of using or redistributing the Work and assume any
182
+ risks associated with Your exercise of permissions under this License.
183
+
184
+ ## 9. Limitation of Liability
185
+
186
+ In no event and under no legal theory, whether in tort (including negligence), contract, or
187
+ otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or
188
+ agreed to in writing, shall any Contributor be liable to You for damages, including any direct,
189
+ indirect, special, incidental, or consequential damages of any character arising as a result of this
190
+ License or out of the use or inability to use the Work (including but not limited to damages for
191
+ loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial
192
+ damages or losses), even if such Contributor has been advised of the possibility of such damages.
193
+
194
+ ## 10. Accepting Warranty or Additional Liability
195
+
196
+ While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee
197
+ for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights
198
+ consistent with this License. However, in accepting such obligations, You may act only on Your own
199
+ behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You
200
+ agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or
201
+ claims asserted against, such Contributor by reason of your accepting any such warranty or
202
+ additional liability.
203
+
204
+ ## Attribution
205
+
206
+ Do No Harm License [Contributor Covenant], (pre 1.0),
207
+ available at https://github.com/raisely/NoHarm
package/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # @mosaic-code/prisma-select-for-update
2
+
3
+ A Prisma v7 extension that adds `SELECT ... FOR UPDATE` row locking functionality for PostgreSQL databases. This extension provides type-safe methods to lock rows during transactions, preventing concurrent modifications.
4
+
5
+ ## Features
6
+
7
+ - **Type-safe row locking** - Three methods: `findUniqueForUpdate`, `findFirstForUpdate`, `findManyForUpdate`
8
+ - **Multiple lock modes** - Support for `FOR UPDATE`, `FOR NO KEY UPDATE`, `FOR SHARE`, and `FOR KEY SHARE`
9
+ - **NOWAIT and SKIP LOCKED** - Options for non-blocking lock acquisition
10
+ - **Prisma-compatible API** - Works seamlessly with existing Prisma queries
11
+ - **PostgreSQL-only** - Optimized for PostgreSQL's row-level locking
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @mosaic-code/prisma-select-for-update
17
+ ```
18
+
19
+ **Peer Dependencies:**
20
+ - `@prisma/client@^7.0.0`
21
+ - `@prisma/adapter-pg@^7.0.0` (for PostgreSQL adapter)
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { PrismaClient } from '@prisma/client'
27
+ import { PrismaPg } from '@prisma/adapter-pg'
28
+ import pg from 'pg'
29
+ import { withForUpdate } from '@mosaic-code/prisma-select-for-update'
30
+
31
+ const { Pool } = pg
32
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL })
33
+ const adapter = new PrismaPg(pool)
34
+
35
+ const basePrisma = new PrismaClient({ adapter })
36
+ const prisma = basePrisma.$extends(withForUpdate())
37
+
38
+ // Use within a transaction
39
+ await prisma.$transaction(async (tx) => {
40
+ const user = await tx.user.findUniqueForUpdate({
41
+ where: { id: 1 },
42
+ })
43
+
44
+ // User row is now locked - safe to modify
45
+ if (user && user.balance >= 100) {
46
+ await tx.user.update({
47
+ where: { id: user.id },
48
+ data: { balance: { decrement: 100 } },
49
+ })
50
+ }
51
+ })
52
+ ```
53
+
54
+ ## API Reference
55
+
56
+ ### Methods
57
+
58
+ All methods **must** be called within a `$transaction` callback. Calling them outside a transaction will throw an error.
59
+
60
+ #### `findUniqueForUpdate`
61
+
62
+ Locks and returns a single row by unique field(s).
63
+
64
+ ```typescript
65
+ const user = await prisma.$transaction(async (tx) => {
66
+ return tx.user.findUniqueForUpdate({
67
+ where: { id: 1 },
68
+ select: { id: true, email: true }, // optional
69
+ lock: { mode: 'ForUpdate' }, // optional, defaults to ForNoKeyUpdate
70
+ })
71
+ })
72
+ ```
73
+
74
+ #### `findFirstForUpdate`
75
+
76
+ Locks and returns the first matching row.
77
+
78
+ ```typescript
79
+ const task = await prisma.$transaction(async (tx) => {
80
+ return tx.task.findFirstForUpdate({
81
+ where: { status: 'pending' },
82
+ orderBy: { priority: 'desc' },
83
+ lock: { skipLocked: true },
84
+ })
85
+ })
86
+ ```
87
+
88
+ #### `findManyForUpdate`
89
+
90
+ Locks and returns multiple matching rows.
91
+
92
+ ```typescript
93
+ const users = await prisma.$transaction(async (tx) => {
94
+ return tx.user.findManyForUpdate({
95
+ where: { balance: { gte: 100 } },
96
+ orderBy: { balance: 'asc' },
97
+ take: 10,
98
+ skip: 0,
99
+ lock: { mode: 'ForShare' },
100
+ })
101
+ })
102
+ ```
103
+
104
+ ### Lock Options
105
+
106
+ ```typescript
107
+ interface LockOptions {
108
+ /** Lock mode - defaults to 'ForNoKeyUpdate' */
109
+ mode?: 'ForUpdate' | 'ForNoKeyUpdate' | 'ForShare' | 'ForKeyShare'
110
+ /** Fail immediately if row is locked (NOWAIT) */
111
+ noWait?: boolean
112
+ /** Skip locked rows instead of waiting (SKIP LOCKED) */
113
+ skipLocked?: boolean
114
+ }
115
+ ```
116
+
117
+ #### Lock Modes
118
+
119
+ - **`ForNoKeyUpdate`** (default) - Exclusive lock for non-key columns, allows other transactions to acquire `FOR KEY SHARE` locks. Less restrictive than `ForUpdate` and common for updates that don't modify key columns.
120
+ - **`ForUpdate`** - Exclusive lock, prevents other transactions from modifying or locking the row
121
+ - **`ForShare`** - Shared lock, allows other transactions to read but not modify
122
+ - **`ForKeyShare`** - Weakest lock, allows other transactions to read and acquire `FOR KEY SHARE` locks
123
+
124
+ #### NOWAIT and SKIP LOCKED
125
+
126
+ ```typescript
127
+ // Fail immediately if row is locked
128
+ const user = await prisma.$transaction(async (tx) => {
129
+ return tx.user.findUniqueForUpdate({
130
+ where: { id: 1 },
131
+ lock: { noWait: true }, // Throws error if row is locked
132
+ })
133
+ })
134
+
135
+ // Skip locked rows (useful for queue processing)
136
+ const tasks = await prisma.$transaction(async (tx) => {
137
+ return tx.task.findManyForUpdate({
138
+ where: { status: 'pending' },
139
+ lock: { skipLocked: true }, // Returns only unlocked rows
140
+ })
141
+ })
142
+ ```
143
+
144
+ ## Examples
145
+
146
+ ### Bank Account Transfer
147
+
148
+ ```typescript
149
+ await prisma.$transaction(async (tx) => {
150
+ // Lock both accounts
151
+ const fromAccount = await tx.account.findUniqueForUpdate({
152
+ where: { id: fromAccountId },
153
+ })
154
+ const toAccount = await tx.account.findUniqueForUpdate({
155
+ where: { id: toAccountId },
156
+ })
157
+
158
+ if (!fromAccount || fromAccount.balance < amount) {
159
+ throw new Error('Insufficient funds')
160
+ }
161
+
162
+ // Update balances
163
+ await tx.account.update({
164
+ where: { id: fromAccountId },
165
+ data: { balance: { decrement: amount } },
166
+ })
167
+ await tx.account.update({
168
+ where: { id: toAccountId },
169
+ data: { balance: { increment: amount } },
170
+ })
171
+ })
172
+ ```
173
+
174
+ ### Queue Processing with SKIP LOCKED
175
+
176
+ ```typescript
177
+ // Worker 1
178
+ const jobs1 = await prisma.$transaction(async (tx) => {
179
+ return tx.job.findManyForUpdate({
180
+ where: { status: 'pending' },
181
+ orderBy: { createdAt: 'asc' },
182
+ take: 10,
183
+ lock: { skipLocked: true }, // Skip rows locked by other workers
184
+ })
185
+ })
186
+
187
+ // Worker 2 (runs concurrently)
188
+ const jobs2 = await prisma.$transaction(async (tx) => {
189
+ return tx.job.findManyForUpdate({
190
+ where: { status: 'pending' },
191
+ orderBy: { createdAt: 'asc' },
192
+ take: 10,
193
+ lock: { skipLocked: true }, // Gets different rows
194
+ })
195
+ })
196
+ ```
197
+
198
+ ### Complex WHERE Clauses
199
+
200
+ All standard Prisma where operators are supported:
201
+
202
+ ```typescript
203
+ const users = await prisma.$transaction(async (tx) => {
204
+ return tx.user.findManyForUpdate({
205
+ where: {
206
+ OR: [
207
+ { balance: { gte: 1000 } },
208
+ { email: { contains: '@company.com' } },
209
+ ],
210
+ AND: [
211
+ { status: 'active' },
212
+ { createdAt: { gte: new Date('2024-01-01') } },
213
+ ],
214
+ NOT: {
215
+ role: 'admin',
216
+ },
217
+ },
218
+ orderBy: [{ balance: 'desc' }, { createdAt: 'asc' }],
219
+ })
220
+ })
221
+ ```
222
+
223
+ ## Supported WHERE Operators
224
+
225
+ - Equality: `equals`, `not`
226
+ - Comparison: `lt`, `lte`, `gt`, `gte`
227
+ - Arrays: `in`, `notIn`
228
+ - Strings: `contains`, `startsWith`, `endsWith`
229
+ - Logical: `AND`, `OR`, `NOT`
230
+ - Null: `null`, `not: null`
231
+
232
+ **Note:** String operators (`contains`, `startsWith`, `endsWith`) escape LIKE wildcards (`%`, `_`) to match Prisma's behavior. For custom LIKE patterns, use `$queryRaw` within your transaction.
233
+
234
+ ## Known Limitations
235
+
236
+ 1. **PostgreSQL-only** - This extension is designed specifically for PostgreSQL's row-level locking. MySQL and SQLite are not supported.
237
+
238
+ 2. **Case-insensitive matching** - The `mode: 'insensitive'` option is not supported. Use `$queryRaw` with `ILIKE` for case-insensitive matching:
239
+
240
+ ```typescript
241
+ await prisma.$transaction(async (tx) => {
242
+ const users = await tx.$queryRaw`
243
+ SELECT * FROM "User"
244
+ WHERE email ILIKE ${'%test%'}
245
+ FOR UPDATE
246
+ `
247
+ })
248
+ ```
249
+
250
+ 3. **Transaction required** - All `forUpdate` methods must be called within a transaction. This is enforced at runtime.
251
+
252
+ 4. **Relation filters** - Relation filters (e.g., `posts: { some: {...} }`) are not supported. Use joins or separate queries instead.
253
+
254
+ ## Type Safety
255
+
256
+ The extension maintains full TypeScript type safety:
257
+
258
+ ```typescript
259
+ const user = await prisma.$transaction(async (tx) => {
260
+ return tx.user.findUniqueForUpdate({
261
+ where: { id: 1 },
262
+ select: { id: true, email: true },
263
+ })
264
+ })
265
+
266
+ // Type: { id: number; email: string } | null
267
+ ```
268
+
269
+ ## Error Handling
270
+
271
+ ```typescript
272
+ try {
273
+ await prisma.$transaction(async (tx) => {
274
+ return tx.user.findUniqueForUpdate({
275
+ where: { id: 1 },
276
+ lock: { noWait: true },
277
+ })
278
+ })
279
+ } catch (error) {
280
+ if (error.message.includes('could not obtain lock')) {
281
+ // Row is locked by another transaction
282
+ }
283
+ }
284
+ ```
285
+
286
+ ## Testing
287
+
288
+ ```bash
289
+ # Run tests
290
+ npm test
291
+
292
+ # Run tests in watch mode
293
+ npm run test:watch
294
+ ```
295
+
296
+ ## Publishing
297
+
298
+ To publish this package to npm:
299
+
300
+ ```bash
301
+ # Build the package (runs automatically before publish)
302
+ npm run build
303
+
304
+ # Publish to npm
305
+ npm publish
306
+ ```
307
+
308
+ The `prepublishOnly` script ensures a clean build before publishing. Only the `dist` folder, `README.md`, and `LICENSE` are included in the published package.
309
+
310
+ ## License
311
+
312
+ [Do No Harm](https://github.com/raisely/NoHarm)
313
+
314
+ ## Related
315
+
316
+ - [Prisma Documentation](https://www.prisma.io/docs)
317
+ - [PostgreSQL Row-Level Locking](https://www.postgresql.org/docs/current/explicit-locking.html)
318
+
@@ -0,0 +1,17 @@
1
+ import { Prisma } from '@prisma/client';
2
+ import type { FindFirstForUpdateArgs, FindManyForUpdateArgs, FindUniqueForUpdateArgs } from './types.js';
3
+ /**
4
+ * Create the Prisma extension with forUpdate methods
5
+ */
6
+ export declare function withForUpdate(): (client: any) => {
7
+ $extends: {
8
+ extArgs: import(".prisma/client/runtime/client").InternalArgs<unknown, {
9
+ $allModels: {
10
+ findUniqueForUpdate<T, Args extends FindUniqueForUpdateArgs<T>>(this: T, args: Args): Promise<Prisma.Result<T, Args, "findUnique"> | null>;
11
+ findFirstForUpdate<T, Args extends FindFirstForUpdateArgs<T>>(this: T, args?: Args): Promise<Prisma.Result<T, Args, "findFirst"> | null>;
12
+ findManyForUpdate<T, Args extends FindManyForUpdateArgs<T>>(this: T, args?: Args): Promise<Prisma.Result<T, Args, "findMany">>;
13
+ };
14
+ }, {}, unknown>;
15
+ };
16
+ };
17
+ //# sourceMappingURL=extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC,OAAO,KAAK,EAEV,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EAGxB,MAAM,YAAY,CAAA;AAqGnB;;GAEG;AACH,wBAAgB,aAAa;;;;oCAKK,CAAC,EAAE,IAAI,SAAS,uBAAuB,CAAC,CAAC,CAAC,QAC5D,CAAC,QACD,IAAI;mCAuBa,CAAC,EAAE,IAAI,SAAS,sBAAsB,CAAC,CAAC,CAAC,QAC1D,CAAC,SACD,IAAI;kCAwBY,CAAC,EAAE,IAAI,SAAS,qBAAqB,CAAC,CAAC,CAAC,QACxD,CAAC,SACD,IAAI;;;;EAyBnB"}
@@ -0,0 +1,128 @@
1
+ import { Prisma } from '@prisma/client';
2
+ import { buildSelectForUpdate } from './sql-builder.js';
3
+ /**
4
+ * Get model metadata from DMMF
5
+ */
6
+ function getModelMeta(modelName) {
7
+ const dmmf = Prisma.dmmf;
8
+ const model = dmmf.datamodel.models.find((m) => m.name.toLowerCase() === modelName.toLowerCase());
9
+ if (!model) {
10
+ throw new Error(`Model "${modelName}" not found in Prisma schema`);
11
+ }
12
+ return {
13
+ name: model.name,
14
+ dbName: model.dbName ?? null,
15
+ fields: model.fields.map((f) => ({
16
+ name: f.name,
17
+ kind: f.kind,
18
+ type: f.type,
19
+ dbName: f.dbName ?? null,
20
+ isId: f.isId ?? false,
21
+ isUnique: f.isUnique ?? false,
22
+ })),
23
+ };
24
+ }
25
+ /**
26
+ * Transform raw query result to match Prisma's camelCase field naming
27
+ */
28
+ function transformResult(result, model) {
29
+ const transformed = {};
30
+ for (const [key, value] of Object.entries(result)) {
31
+ // Find field by dbName or name (case-insensitive for PostgreSQL)
32
+ const field = model.fields.find((f) => (f.dbName ?? f.name).toLowerCase() === key.toLowerCase() ||
33
+ f.name.toLowerCase() === key.toLowerCase());
34
+ if (field) {
35
+ transformed[field.name] = value;
36
+ }
37
+ else {
38
+ transformed[key] = value;
39
+ }
40
+ }
41
+ return transformed;
42
+ }
43
+ /**
44
+ * Check if we're in a transaction context
45
+ * Transaction clients do NOT have $transaction method (can't nest transactions)
46
+ */
47
+ function isTransactionClient(client) {
48
+ // Transaction clients don't have $transaction method
49
+ // Base client has $transaction, transaction client doesn't
50
+ return !(client?.$transaction);
51
+ }
52
+ /**
53
+ * Get and validate execution context from Prisma extension
54
+ * Extracts model name, validates transaction context, and returns model metadata
55
+ */
56
+ function getExecutionContext(thisArg) {
57
+ const context = Prisma.getExtensionContext(thisArg);
58
+ const modelName = context.$name;
59
+ if (!modelName) {
60
+ throw new Error('Could not determine model name from context');
61
+ }
62
+ const client = context.$parent;
63
+ // Check if we're in a transaction
64
+ if (!isTransactionClient(client)) {
65
+ throw new Error('forUpdate methods must be called within a transaction. ' +
66
+ 'Use prisma.$transaction(async (tx) => { await tx.model.findUniqueForUpdate(...) })');
67
+ }
68
+ if (!client?.$queryRawUnsafe) {
69
+ throw new Error('$queryRawUnsafe not available on client');
70
+ }
71
+ const model = getModelMeta(modelName);
72
+ return { client, model, modelName };
73
+ }
74
+ /**
75
+ * Create the Prisma extension with forUpdate methods
76
+ */
77
+ export function withForUpdate() {
78
+ return Prisma.defineExtension({
79
+ name: 'prisma-lock-for-update',
80
+ model: {
81
+ $allModels: {
82
+ async findUniqueForUpdate(args) {
83
+ const { client, model } = getExecutionContext(this);
84
+ const { sql, params } = buildSelectForUpdate(model, {
85
+ where: args.where,
86
+ select: args.select,
87
+ lock: args.lock,
88
+ take: 1, // findUnique should return at most 1 row
89
+ });
90
+ const results = await client.$queryRawUnsafe(sql, ...params);
91
+ if (results.length === 0) {
92
+ return null;
93
+ }
94
+ return transformResult(results[0], model);
95
+ },
96
+ async findFirstForUpdate(args = {}) {
97
+ const { client, model } = getExecutionContext(this);
98
+ const { sql, params } = buildSelectForUpdate(model, {
99
+ where: args.where,
100
+ select: args.select,
101
+ orderBy: args.orderBy,
102
+ lock: args.lock,
103
+ take: 1, // findFirst returns at most 1 row
104
+ });
105
+ const results = await client.$queryRawUnsafe(sql, ...params);
106
+ if (results.length === 0) {
107
+ return null;
108
+ }
109
+ return transformResult(results[0], model);
110
+ },
111
+ async findManyForUpdate(args = {}) {
112
+ const { client, model } = getExecutionContext(this);
113
+ const { sql, params } = buildSelectForUpdate(model, {
114
+ where: args.where,
115
+ select: args.select,
116
+ orderBy: args.orderBy,
117
+ take: args.take,
118
+ skip: args.skip,
119
+ lock: args.lock,
120
+ });
121
+ const results = await client.$queryRawUnsafe(sql, ...params);
122
+ return results.map((r) => transformResult(r, model));
123
+ },
124
+ },
125
+ },
126
+ });
127
+ }
128
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { withForUpdate } from './extension.js';
2
+ export type { LockMode, LockOptions, FindUniqueForUpdateArgs, FindFirstForUpdateArgs, FindManyForUpdateArgs, } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,YAAY,EACV,QAAQ,EACR,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { withForUpdate } from './extension.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,41 @@
1
+ import type { LockOptions, ModelMeta, SqlQuery } from './types.js';
2
+ /**
3
+ * Quote a PostgreSQL identifier (table/column name)
4
+ * Implementation matches pg.escapeIdentifier (ported from PostgreSQL source)
5
+ * Escapes double quotes by doubling them: " becomes ""
6
+ */
7
+ export declare function quoteIdentifier(name: string): string;
8
+ /**
9
+ * Escape LIKE pattern wildcards to treat them as literal characters
10
+ * Escapes %, _, and \ characters to match Prisma's behavior
11
+ */
12
+ export declare function escapeLikePattern(value: string): string;
13
+ export declare function buildLockClause(lock?: LockOptions): string;
14
+ /**
15
+ * Build SELECT clause from select option or return *
16
+ */
17
+ export declare function buildSelectClause(model: ModelMeta, select?: Record<string, boolean>): string;
18
+ /**
19
+ * Build ORDER BY clause from orderBy option
20
+ */
21
+ export declare function buildOrderByClause(model: ModelMeta, orderBy?: Record<string, 'asc' | 'desc'> | Array<Record<string, 'asc' | 'desc'>>): string;
22
+ /**
23
+ * Convert a Prisma where clause to SQL WHERE clause with parameters
24
+ * Supports common operators: equals, not, in, notIn, lt, lte, gt, gte, contains, startsWith, endsWith
25
+ */
26
+ export declare function buildWhereClause(model: ModelMeta, where?: Record<string, unknown>, startParamIndex?: number): {
27
+ sql: string;
28
+ params: unknown[];
29
+ };
30
+ /**
31
+ * Build a complete SELECT ... FOR UPDATE query
32
+ */
33
+ export declare function buildSelectForUpdate(model: ModelMeta, options: {
34
+ where?: Record<string, unknown>;
35
+ select?: Record<string, boolean>;
36
+ orderBy?: Record<string, 'asc' | 'desc'> | Array<Record<string, 'asc' | 'desc'>>;
37
+ take?: number;
38
+ skip?: number;
39
+ lock?: LockOptions;
40
+ }): SqlQuery;
41
+ //# sourceMappingURL=sql-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-builder.d.ts","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE5E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAqBD,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAW1D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAiBR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,GAC/E,MAAM,CAsBR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,eAAe,SAAI,GAClB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAoMpC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAA;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,GACA,QAAQ,CAgCV"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Quote a PostgreSQL identifier (table/column name)
3
+ * Implementation matches pg.escapeIdentifier (ported from PostgreSQL source)
4
+ * Escapes double quotes by doubling them: " becomes ""
5
+ */
6
+ export function quoteIdentifier(name) {
7
+ return `"${name.replace(/"/g, '""')}"`;
8
+ }
9
+ /**
10
+ * Escape LIKE pattern wildcards to treat them as literal characters
11
+ * Escapes %, _, and \ characters to match Prisma's behavior
12
+ */
13
+ export function escapeLikePattern(value) {
14
+ return value.replace(/[%_\\]/g, '\\$&');
15
+ }
16
+ /**
17
+ * Get database field name for a model field
18
+ * Returns dbName if available, otherwise returns the field name
19
+ */
20
+ function getDbFieldName(model, fieldName) {
21
+ const field = model.fields.find((f) => f.name === fieldName);
22
+ return field?.dbName ?? fieldName;
23
+ }
24
+ /**
25
+ * Get the SQL lock clause based on lock options
26
+ */
27
+ const LOCK_MODE_SQL = {
28
+ ForUpdate: 'FOR UPDATE',
29
+ ForNoKeyUpdate: 'FOR NO KEY UPDATE',
30
+ ForShare: 'FOR SHARE',
31
+ ForKeyShare: 'FOR KEY SHARE',
32
+ };
33
+ export function buildLockClause(lock) {
34
+ const mode = lock?.mode ?? 'ForNoKeyUpdate';
35
+ let lockSql = LOCK_MODE_SQL[mode];
36
+ if (lock?.noWait) {
37
+ lockSql += ' NOWAIT';
38
+ }
39
+ else if (lock?.skipLocked) {
40
+ lockSql += ' SKIP LOCKED';
41
+ }
42
+ return lockSql;
43
+ }
44
+ /**
45
+ * Build SELECT clause from select option or return *
46
+ */
47
+ export function buildSelectClause(model, select) {
48
+ if (!select) {
49
+ return '*';
50
+ }
51
+ const selectedFields = Object.entries(select)
52
+ .filter(([_, include]) => include)
53
+ .map(([fieldName]) => {
54
+ const dbName = getDbFieldName(model, fieldName);
55
+ return quoteIdentifier(dbName);
56
+ });
57
+ if (selectedFields.length === 0) {
58
+ return '*';
59
+ }
60
+ return selectedFields.join(', ');
61
+ }
62
+ /**
63
+ * Build ORDER BY clause from orderBy option
64
+ */
65
+ export function buildOrderByClause(model, orderBy) {
66
+ if (!orderBy) {
67
+ return '';
68
+ }
69
+ const orderByList = Array.isArray(orderBy) ? orderBy : [orderBy];
70
+ if (orderByList.length === 0) {
71
+ return '';
72
+ }
73
+ const clauses = orderByList.flatMap((item) => Object.entries(item).map(([fieldName, direction]) => {
74
+ const dbName = getDbFieldName(model, fieldName);
75
+ return `${quoteIdentifier(dbName)} ${direction.toUpperCase()}`;
76
+ }));
77
+ if (clauses.length === 0) {
78
+ return '';
79
+ }
80
+ return `ORDER BY ${clauses.join(', ')}`;
81
+ }
82
+ /**
83
+ * Convert a Prisma where clause to SQL WHERE clause with parameters
84
+ * Supports common operators: equals, not, in, notIn, lt, lte, gt, gte, contains, startsWith, endsWith
85
+ */
86
+ export function buildWhereClause(model, where, startParamIndex = 1) {
87
+ if (!where || Object.keys(where).length === 0) {
88
+ return { sql: '', params: [] };
89
+ }
90
+ const conditions = [];
91
+ const params = [];
92
+ let paramIndex = startParamIndex;
93
+ function processCondition(key, value) {
94
+ // Handle logical operators
95
+ if (key === 'AND' && Array.isArray(value)) {
96
+ const subConditions = [];
97
+ for (const v of value) {
98
+ const sub = buildWhereClause(model, v, paramIndex);
99
+ params.push(...sub.params);
100
+ paramIndex += sub.params.length;
101
+ subConditions.push(`(${sub.sql})`);
102
+ }
103
+ if (subConditions.length > 0) {
104
+ conditions.push(`(${subConditions.join(' AND ')})`);
105
+ }
106
+ return;
107
+ }
108
+ if (key === 'OR' && Array.isArray(value)) {
109
+ const subConditions = [];
110
+ for (const v of value) {
111
+ const sub = buildWhereClause(model, v, paramIndex);
112
+ params.push(...sub.params);
113
+ paramIndex += sub.params.length;
114
+ subConditions.push(`(${sub.sql})`);
115
+ }
116
+ if (subConditions.length > 0) {
117
+ conditions.push(`(${subConditions.join(' OR ')})`);
118
+ }
119
+ return;
120
+ }
121
+ if (key === 'NOT') {
122
+ const sub = buildWhereClause(model, value, paramIndex);
123
+ params.push(...sub.params);
124
+ paramIndex += sub.params.length;
125
+ conditions.push(`NOT (${sub.sql})`);
126
+ return;
127
+ }
128
+ // Regular field conditions
129
+ const field = model.fields.find((f) => f.name === key);
130
+ if (!field || field.kind !== 'scalar') {
131
+ return; // Skip non-scalar fields (relations)
132
+ }
133
+ const dbName = getDbFieldName(model, key);
134
+ const quotedName = quoteIdentifier(dbName);
135
+ if (value === null) {
136
+ conditions.push(`${quotedName} IS NULL`);
137
+ return;
138
+ }
139
+ if (typeof value !== 'object' || value instanceof Date) {
140
+ // Simple equality
141
+ conditions.push(`${quotedName} = $${paramIndex++}`);
142
+ params.push(value);
143
+ return;
144
+ }
145
+ // Object with operators
146
+ const ops = value;
147
+ for (const [op, opValue] of Object.entries(ops)) {
148
+ switch (op) {
149
+ case 'equals':
150
+ if (opValue === null) {
151
+ conditions.push(`${quotedName} IS NULL`);
152
+ }
153
+ else {
154
+ conditions.push(`${quotedName} = $${paramIndex++}`);
155
+ params.push(opValue);
156
+ }
157
+ break;
158
+ case 'not':
159
+ if (opValue === null) {
160
+ conditions.push(`${quotedName} IS NOT NULL`);
161
+ }
162
+ else if (typeof opValue === 'object' && opValue !== null) {
163
+ // Nested not with operators
164
+ for (const [nestedOp, nestedVal] of Object.entries(opValue)) {
165
+ switch (nestedOp) {
166
+ case 'equals':
167
+ conditions.push(`${quotedName} != $${paramIndex++}`);
168
+ params.push(nestedVal);
169
+ break;
170
+ case 'in':
171
+ if (Array.isArray(nestedVal)) {
172
+ if (nestedVal.length === 0) {
173
+ // Empty array in NOT IN means match all rows
174
+ // Skip condition - no restriction
175
+ }
176
+ else {
177
+ const placeholders = nestedVal.map(() => `$${paramIndex++}`).join(', ');
178
+ conditions.push(`${quotedName} NOT IN (${placeholders})`);
179
+ params.push(...nestedVal);
180
+ }
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ }
186
+ else {
187
+ conditions.push(`${quotedName} != $${paramIndex++}`);
188
+ params.push(opValue);
189
+ }
190
+ break;
191
+ case 'in':
192
+ if (Array.isArray(opValue)) {
193
+ if (opValue.length === 0) {
194
+ // Empty array matches nothing (matches Prisma behavior)
195
+ conditions.push('FALSE');
196
+ }
197
+ else {
198
+ const placeholders = opValue.map(() => `$${paramIndex++}`).join(', ');
199
+ conditions.push(`${quotedName} IN (${placeholders})`);
200
+ params.push(...opValue);
201
+ }
202
+ }
203
+ break;
204
+ case 'notIn':
205
+ if (Array.isArray(opValue)) {
206
+ if (opValue.length === 0) {
207
+ // Empty notIn array matches all rows (matches Prisma behavior)
208
+ // Skip condition - no restriction
209
+ }
210
+ else {
211
+ const placeholders = opValue.map(() => `$${paramIndex++}`).join(', ');
212
+ conditions.push(`${quotedName} NOT IN (${placeholders})`);
213
+ params.push(...opValue);
214
+ }
215
+ }
216
+ break;
217
+ case 'lt':
218
+ conditions.push(`${quotedName} < $${paramIndex++}`);
219
+ params.push(opValue);
220
+ break;
221
+ case 'lte':
222
+ conditions.push(`${quotedName} <= $${paramIndex++}`);
223
+ params.push(opValue);
224
+ break;
225
+ case 'gt':
226
+ conditions.push(`${quotedName} > $${paramIndex++}`);
227
+ params.push(opValue);
228
+ break;
229
+ case 'gte':
230
+ conditions.push(`${quotedName} >= $${paramIndex++}`);
231
+ params.push(opValue);
232
+ break;
233
+ case 'contains':
234
+ if (typeof opValue !== 'string') {
235
+ break;
236
+ }
237
+ conditions.push(`${quotedName} LIKE $${paramIndex++}`);
238
+ params.push(`%${escapeLikePattern(opValue)}%`);
239
+ break;
240
+ case 'startsWith':
241
+ if (typeof opValue !== 'string') {
242
+ break;
243
+ }
244
+ conditions.push(`${quotedName} LIKE $${paramIndex++}`);
245
+ params.push(`${escapeLikePattern(opValue)}%`);
246
+ break;
247
+ case 'endsWith':
248
+ if (typeof opValue !== 'string') {
249
+ break;
250
+ }
251
+ conditions.push(`${quotedName} LIKE $${paramIndex++}`);
252
+ params.push(`%${escapeLikePattern(opValue)}`);
253
+ break;
254
+ case 'mode':
255
+ if (opValue === 'insensitive') {
256
+ throw new Error('Case-insensitive mode is not supported. ' +
257
+ 'Use $queryRaw with ILIKE for case-insensitive matching, or use Prisma\'s standard query API.');
258
+ }
259
+ // Other mode values are ignored (default is case-sensitive)
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ for (const [key, value] of Object.entries(where)) {
265
+ processCondition(key, value);
266
+ }
267
+ if (conditions.length === 0) {
268
+ return { sql: '', params: [] };
269
+ }
270
+ return {
271
+ sql: conditions.join(' AND '),
272
+ params,
273
+ };
274
+ }
275
+ /**
276
+ * Build a complete SELECT ... FOR UPDATE query
277
+ */
278
+ export function buildSelectForUpdate(model, options) {
279
+ const tableName = model.dbName ?? model.name;
280
+ const selectClause = buildSelectClause(model, options.select);
281
+ const { sql: whereSql, params } = buildWhereClause(model, options.where);
282
+ const orderByClause = buildOrderByClause(model, options.orderBy);
283
+ const lockClause = buildLockClause(options.lock);
284
+ let sql = `SELECT ${selectClause} FROM ${quoteIdentifier(tableName)}`;
285
+ if (whereSql) {
286
+ sql += ` WHERE ${whereSql}`;
287
+ }
288
+ if (orderByClause) {
289
+ sql += ` ${orderByClause}`;
290
+ }
291
+ // Handle LIMIT and OFFSET
292
+ let paramIndex = params.length + 1;
293
+ if (options.take !== undefined) {
294
+ sql += ` LIMIT $${paramIndex++}`;
295
+ params.push(options.take);
296
+ }
297
+ if (options.skip !== undefined) {
298
+ sql += ` OFFSET $${paramIndex++}`;
299
+ params.push(options.skip);
300
+ }
301
+ sql += ` ${lockClause}`;
302
+ return { sql, params };
303
+ }
304
+ //# sourceMappingURL=sql-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-builder.js","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type { Prisma } from '@prisma/client';
2
+ /**
3
+ * Lock modes supported by PostgreSQL
4
+ */
5
+ export type LockMode = 'ForUpdate' | 'ForNoKeyUpdate' | 'ForShare' | 'ForKeyShare';
6
+ /**
7
+ * Lock options for FOR UPDATE queries
8
+ */
9
+ export interface LockOptions {
10
+ /** Lock mode - defaults to ForNoKeyUpdate */
11
+ mode?: LockMode;
12
+ /** Fail immediately if row is locked (NOWAIT) */
13
+ noWait?: boolean;
14
+ /** Skip locked rows instead of waiting (SKIP LOCKED) */
15
+ skipLocked?: boolean;
16
+ }
17
+ /**
18
+ * Arguments for findUniqueForUpdate
19
+ */
20
+ export interface FindUniqueForUpdateArgs<T> {
21
+ where: Prisma.Args<T, 'findUnique'>['where'];
22
+ select?: Prisma.Args<T, 'findUnique'>['select'];
23
+ lock?: LockOptions;
24
+ }
25
+ /**
26
+ * Arguments for findFirstForUpdate
27
+ */
28
+ export interface FindFirstForUpdateArgs<T> {
29
+ where?: Prisma.Args<T, 'findFirst'>['where'];
30
+ select?: Prisma.Args<T, 'findFirst'>['select'];
31
+ orderBy?: Prisma.Args<T, 'findFirst'>['orderBy'];
32
+ lock?: LockOptions;
33
+ }
34
+ /**
35
+ * Arguments for findManyForUpdate
36
+ */
37
+ export interface FindManyForUpdateArgs<T> {
38
+ where?: Prisma.Args<T, 'findMany'>['where'];
39
+ select?: Prisma.Args<T, 'findMany'>['select'];
40
+ orderBy?: Prisma.Args<T, 'findMany'>['orderBy'];
41
+ take?: number;
42
+ skip?: number;
43
+ lock?: LockOptions;
44
+ }
45
+ /**
46
+ * SQL query result with parameterized values
47
+ */
48
+ export interface SqlQuery {
49
+ sql: string;
50
+ params: unknown[];
51
+ }
52
+ /**
53
+ * Model metadata from DMMF
54
+ */
55
+ export interface ModelField {
56
+ name: string;
57
+ kind: 'scalar' | 'object' | 'enum' | 'unsupported';
58
+ type: string;
59
+ dbName?: string | null;
60
+ isId?: boolean;
61
+ isUnique?: boolean;
62
+ }
63
+ export interface ModelMeta {
64
+ name: string;
65
+ dbName: string | null;
66
+ fields: ModelField[];
67
+ }
68
+ /**
69
+ * Transaction client interface for Prisma extensions
70
+ * Transaction clients don't have $transaction method (can't nest transactions)
71
+ */
72
+ export interface TransactionClient {
73
+ $queryRawUnsafe: <R>(sql: string, ...params: unknown[]) => Promise<R[]>;
74
+ }
75
+ /**
76
+ * Extension context from Prisma.getExtensionContext
77
+ */
78
+ export interface ExtensionContext {
79
+ $name: string;
80
+ $parent: TransactionClient;
81
+ }
82
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAE5C;;GAEG;AACH,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,gBAAgB,GAChB,UAAU,GACV,aAAa,CAAA;AAEjB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC/C,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,OAAO,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,SAAS,CAAC,CAAA;IAChD,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,SAAS,CAAC,CAAA;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAA;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;CACxE;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,iBAAiB,CAAA;CAC3B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@mosaic-code/prisma-select-for-update",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Prisma v7 extension for SELECT ... FOR UPDATE row locking",
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "clean": "rm -rf dist",
19
+ "build": "tsc",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "npm run clean && npm run build",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "db:up": "docker compose up -d",
25
+ "db:down": "docker compose down",
26
+ "db:push": "prisma db push",
27
+ "db:generate": "prisma generate",
28
+ "db:setup": "npm run db:up && sleep 2 && npm run db:push",
29
+ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
30
+ "release:patch": "npm run changelog && git add CHANGELOG.md && git commit -m \"chore: update changelog\" && npm version patch && git push --follow-tags && npm run build && npm publish",
31
+ "release:minor": "npm run changelog && git add CHANGELOG.md && git commit -m \"chore: update changelog\" && npm version minor && git push --follow-tags && npm run build && npm publish",
32
+ "release:major": "npm run changelog && git add CHANGELOG.md && git commit -m \"chore: update changelog\" && npm version major && git push --follow-tags && npm run build && npm publish",
33
+ "release:dry": "npm run changelog -- --dry-run"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.18.0"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "peerDependencies": {
44
+ "@prisma/adapter-pg": "^7.0.0",
45
+ "@prisma/client": "^7.0.0",
46
+ "pg": "^8.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@prisma/adapter-pg": "^7.2.0",
50
+ "@prisma/client": "^7.0.0",
51
+ "@types/node": "^25.0.3",
52
+ "@types/pg": "^8.16.0",
53
+ "conventional-changelog-cli": "^5.0.0",
54
+ "dotenv": "^17.2.3",
55
+ "pg": "^8.16.3",
56
+ "prisma": "^7.0.0",
57
+ "typescript": "^5.7.0",
58
+ "vitest": "^2.1.0"
59
+ },
60
+ "keywords": [
61
+ "prisma",
62
+ "for-update",
63
+ "row-locking",
64
+ "select-for-update",
65
+ "postgresql"
66
+ ],
67
+ "license": "NoHarm",
68
+ "packageManager": "npm@11.6.2",
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/mosaic-code-coop/prisma-select-for-update.git"
72
+ },
73
+ "homepage": "https://github.com/mosaic-code-coop/prisma-select-for-update#readme",
74
+ "bugs": {
75
+ "url": "https://github.com/mosaic-code-coop/prisma-select-for-update/issues"
76
+ },
77
+ "author": "Chris Jensen <2920476+chrisjensen@users.noreply.github.com> (https://github.com/chrisjensen)"
78
+ }