@geekmidas/db 0.0.3 → 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/README.md ADDED
@@ -0,0 +1,495 @@
1
+ # @geekmidas/db
2
+
3
+ Database utilities for Kysely with flexible transaction management. Provides helpers for working with database connections and transactions in a type-safe way.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Flexible Transaction Handling**: Works with Kysely, Transaction, and ControlledTransaction
8
+ - ✅ **Automatic Transaction Detection**: Reuses existing transactions when nested
9
+ - ✅ **Type-Safe**: Full TypeScript support with generic database schemas
10
+ - ✅ **Connection Abstraction**: Single helper for all database connection types
11
+ - ✅ **Zero Dependencies**: Only peer dependency on Kysely
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @geekmidas/db
17
+ ```
18
+
19
+ ### Peer Dependencies
20
+
21
+ ```bash
22
+ pnpm add kysely pg
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { withTransaction } from '@geekmidas/db/kysely';
29
+ import type { DatabaseConnection } from '@geekmidas/db/kysely';
30
+ import { Kysely } from 'kysely';
31
+
32
+ interface Database {
33
+ users: {
34
+ id: string;
35
+ email: string;
36
+ name: string;
37
+ };
38
+ posts: {
39
+ id: string;
40
+ userId: string;
41
+ title: string;
42
+ };
43
+ }
44
+
45
+ async function createUserWithPost(
46
+ db: DatabaseConnection<Database>,
47
+ userData: { email: string; name: string },
48
+ postData: { title: string }
49
+ ) {
50
+ return withTransaction(db, async (trx) => {
51
+ // Create user
52
+ const user = await trx
53
+ .insertInto('users')
54
+ .values(userData)
55
+ .returningAll()
56
+ .executeTakeFirstOrThrow();
57
+
58
+ // Create post for user
59
+ const post = await trx
60
+ .insertInto('posts')
61
+ .values({
62
+ userId: user.id,
63
+ title: postData.title
64
+ })
65
+ .returningAll()
66
+ .executeTakeFirstOrThrow();
67
+
68
+ return { user, post };
69
+ });
70
+ }
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### `withTransaction`
76
+
77
+ Execute a callback within a transaction. If the connection is already a transaction, it reuses it. Otherwise, it creates a new transaction.
78
+
79
+ ```typescript
80
+ function withTransaction<DB, T>(
81
+ db: DatabaseConnection<DB>,
82
+ cb: (trx: Transaction<DB>) => Promise<T>
83
+ ): Promise<T>
84
+ ```
85
+
86
+ **Parameters:**
87
+ - `db` - A database connection (Kysely, Transaction, or ControlledTransaction)
88
+ - `cb` - Callback function that receives the transaction
89
+
90
+ **Returns:**
91
+ - Promise resolving to the callback's return value
92
+
93
+ ### `DatabaseConnection<T>`
94
+
95
+ Type union for all supported database connection types:
96
+
97
+ ```typescript
98
+ type DatabaseConnection<T> =
99
+ | Kysely<T>
100
+ | Transaction<T>
101
+ | ControlledTransaction<T>;
102
+ ```
103
+
104
+ ## Usage Examples
105
+
106
+ ### Basic Transaction
107
+
108
+ ```typescript
109
+ import { withTransaction } from '@geekmidas/db/kysely';
110
+ import { Kysely } from 'kysely';
111
+
112
+ const db = new Kysely<Database>({ /* config */ });
113
+
114
+ async function transferFunds(fromId: string, toId: string, amount: number) {
115
+ return withTransaction(db, async (trx) => {
116
+ // Deduct from sender
117
+ await trx
118
+ .updateTable('accounts')
119
+ .set({ balance: sql`balance - ${amount}` })
120
+ .where('id', '=', fromId)
121
+ .execute();
122
+
123
+ // Add to receiver
124
+ await trx
125
+ .updateTable('accounts')
126
+ .set({ balance: sql`balance + ${amount}` })
127
+ .where('id', '=', toId)
128
+ .execute();
129
+
130
+ return { success: true };
131
+ });
132
+ }
133
+ ```
134
+
135
+ ### Nested Transactions
136
+
137
+ The helper automatically detects existing transactions and reuses them:
138
+
139
+ ```typescript
140
+ async function createUser(
141
+ db: DatabaseConnection<Database>,
142
+ email: string
143
+ ) {
144
+ return withTransaction(db, async (trx) => {
145
+ const user = await trx
146
+ .insertInto('users')
147
+ .values({ email })
148
+ .returningAll()
149
+ .executeTakeFirstOrThrow();
150
+
151
+ // This will reuse the same transaction
152
+ await createAuditLog(trx, 'user_created', user.id);
153
+
154
+ return user;
155
+ });
156
+ }
157
+
158
+ async function createAuditLog(
159
+ db: DatabaseConnection<Database>,
160
+ action: string,
161
+ userId: string
162
+ ) {
163
+ // If db is already a transaction, it's reused
164
+ return withTransaction(db, async (trx) => {
165
+ await trx
166
+ .insertInto('audit_logs')
167
+ .values({ action, userId, timestamp: new Date() })
168
+ .execute();
169
+ });
170
+ }
171
+ ```
172
+
173
+ ### Repository Pattern
174
+
175
+ Use `DatabaseConnection` type in repositories for flexibility:
176
+
177
+ ```typescript
178
+ import type { DatabaseConnection } from '@geekmidas/db/kysely';
179
+ import { withTransaction } from '@geekmidas/db/kysely';
180
+
181
+ class UserRepository {
182
+ constructor(private db: DatabaseConnection<Database>) {}
183
+
184
+ async create(data: NewUser): Promise<User> {
185
+ return withTransaction(this.db, async (trx) => {
186
+ return trx
187
+ .insertInto('users')
188
+ .values(data)
189
+ .returningAll()
190
+ .executeTakeFirstOrThrow();
191
+ });
192
+ }
193
+
194
+ async update(id: string, data: UserUpdate): Promise<User> {
195
+ return withTransaction(this.db, async (trx) => {
196
+ return trx
197
+ .updateTable('users')
198
+ .set(data)
199
+ .where('id', '=', id)
200
+ .returningAll()
201
+ .executeTakeFirstOrThrow();
202
+ });
203
+ }
204
+ }
205
+
206
+ // Can be used with any connection type
207
+ const db = new Kysely<Database>({ /* config */ });
208
+ const repo = new UserRepository(db);
209
+
210
+ // Or within a transaction
211
+ await withTransaction(db, async (trx) => {
212
+ const repo = new UserRepository(trx);
213
+ await repo.create({ email: 'user@example.com' });
214
+ });
215
+ ```
216
+
217
+ ### Service Pattern with Transactions
218
+
219
+ ```typescript
220
+ import type { DatabaseConnection } from '@geekmidas/db/kysely';
221
+ import { withTransaction } from '@geekmidas/db/kysely';
222
+
223
+ class OrderService {
224
+ constructor(
225
+ private db: DatabaseConnection<Database>,
226
+ private inventoryService: InventoryService,
227
+ private paymentService: PaymentService
228
+ ) {}
229
+
230
+ async createOrder(
231
+ userId: string,
232
+ items: OrderItem[]
233
+ ): Promise<Order> {
234
+ return withTransaction(this.db, async (trx) => {
235
+ // All operations share the same transaction
236
+ const order = await trx
237
+ .insertInto('orders')
238
+ .values({ userId, status: 'pending' })
239
+ .returningAll()
240
+ .executeTakeFirstOrThrow();
241
+
242
+ // These services can accept the transaction
243
+ await this.inventoryService.reserveItems(trx, items);
244
+ await this.paymentService.processPayment(trx, order.id);
245
+
246
+ // Update order status
247
+ return trx
248
+ .updateTable('orders')
249
+ .set({ status: 'completed' })
250
+ .where('id', '=', order.id)
251
+ .returningAll()
252
+ .executeTakeFirstOrThrow();
253
+ });
254
+ }
255
+ }
256
+
257
+ class InventoryService {
258
+ async reserveItems(
259
+ db: DatabaseConnection<Database>,
260
+ items: OrderItem[]
261
+ ) {
262
+ return withTransaction(db, async (trx) => {
263
+ for (const item of items) {
264
+ await trx
265
+ .updateTable('inventory')
266
+ .set({ reserved: sql`reserved + ${item.quantity}` })
267
+ .where('productId', '=', item.productId)
268
+ .execute();
269
+ }
270
+ });
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### Error Handling
276
+
277
+ Transactions automatically roll back on errors:
278
+
279
+ ```typescript
280
+ import { withTransaction } from '@geekmidas/db/kysely';
281
+
282
+ async function processOrder(db: DatabaseConnection<Database>, orderId: string) {
283
+ try {
284
+ return await withTransaction(db, async (trx) => {
285
+ const order = await trx
286
+ .selectFrom('orders')
287
+ .where('id', '=', orderId)
288
+ .selectAll()
289
+ .executeTakeFirstOrThrow();
290
+
291
+ if (order.status !== 'pending') {
292
+ throw new Error('Order already processed');
293
+ }
294
+
295
+ // Update order
296
+ await trx
297
+ .updateTable('orders')
298
+ .set({ status: 'processing' })
299
+ .where('id', '=', orderId)
300
+ .execute();
301
+
302
+ // If this throws, the entire transaction rolls back
303
+ await processPayment(trx, orderId);
304
+
305
+ return order;
306
+ });
307
+ } catch (error) {
308
+ console.error('Transaction failed:', error);
309
+ // Transaction has been rolled back
310
+ throw error;
311
+ }
312
+ }
313
+ ```
314
+
315
+ ### Testing with Transactions
316
+
317
+ Use transactions for test isolation:
318
+
319
+ ```typescript
320
+ import { describe, it, beforeEach, afterEach } from 'vitest';
321
+ import type { Transaction } from 'kysely';
322
+
323
+ describe('UserRepository', () => {
324
+ let trx: Transaction<Database>;
325
+
326
+ beforeEach(async () => {
327
+ trx = await db.transaction().execute(async (t) => t);
328
+ });
329
+
330
+ afterEach(async () => {
331
+ await trx.rollback();
332
+ });
333
+
334
+ it('should create user', async () => {
335
+ const repo = new UserRepository(trx);
336
+ const user = await repo.create({ email: 'test@example.com' });
337
+
338
+ expect(user.email).toBe('test@example.com');
339
+ // Transaction will be rolled back after test
340
+ });
341
+ });
342
+ ```
343
+
344
+ ## Type Safety
345
+
346
+ The package provides full type safety for database operations:
347
+
348
+ ```typescript
349
+ import type { DatabaseConnection } from '@geekmidas/db/kysely';
350
+ import { withTransaction } from '@geekmidas/db/kysely';
351
+
352
+ interface Database {
353
+ users: {
354
+ id: Generated<string>;
355
+ email: string;
356
+ name: string | null;
357
+ };
358
+ }
359
+
360
+ function updateUser(
361
+ db: DatabaseConnection<Database>,
362
+ id: string,
363
+ data: { name: string }
364
+ ) {
365
+ return withTransaction(db, async (trx) => {
366
+ // Full autocomplete and type checking
367
+ return trx
368
+ .updateTable('users')
369
+ .set(data) // Type-checked against users table
370
+ .where('id', '=', id)
371
+ .returningAll()
372
+ .executeTakeFirstOrThrow();
373
+ });
374
+ }
375
+ ```
376
+
377
+ ## Advanced Patterns
378
+
379
+ ### Unit of Work Pattern
380
+
381
+ ```typescript
382
+ class UnitOfWork {
383
+ private transaction: Transaction<Database> | null = null;
384
+
385
+ constructor(private db: Kysely<Database>) {}
386
+
387
+ async begin() {
388
+ this.transaction = await this.db.transaction().execute(async (t) => t);
389
+ }
390
+
391
+ async commit() {
392
+ if (this.transaction) {
393
+ await this.transaction.commit();
394
+ this.transaction = null;
395
+ }
396
+ }
397
+
398
+ async rollback() {
399
+ if (this.transaction) {
400
+ await this.transaction.rollback();
401
+ this.transaction = null;
402
+ }
403
+ }
404
+
405
+ getConnection(): DatabaseConnection<Database> {
406
+ return this.transaction || this.db;
407
+ }
408
+ }
409
+
410
+ // Usage
411
+ const uow = new UnitOfWork(db);
412
+ await uow.begin();
413
+
414
+ try {
415
+ const userRepo = new UserRepository(uow.getConnection());
416
+ const orderRepo = new OrderRepository(uow.getConnection());
417
+
418
+ await userRepo.create({ email: 'user@example.com' });
419
+ await orderRepo.create({ userId: '123' });
420
+
421
+ await uow.commit();
422
+ } catch (error) {
423
+ await uow.rollback();
424
+ throw error;
425
+ }
426
+ ```
427
+
428
+ ### Connection Pooling
429
+
430
+ ```typescript
431
+ import { Pool } from 'pg';
432
+ import { Kysely, PostgresDialect } from 'kysely';
433
+
434
+ const pool = new Pool({
435
+ host: 'localhost',
436
+ database: 'mydb',
437
+ max: 10
438
+ });
439
+
440
+ const db = new Kysely<Database>({
441
+ dialect: new PostgresDialect({ pool })
442
+ });
443
+
444
+ // Use with withTransaction
445
+ async function processData(data: unknown[]) {
446
+ return withTransaction(db, async (trx) => {
447
+ // Each transaction gets a connection from the pool
448
+ for (const item of data) {
449
+ await trx.insertInto('items').values(item).execute();
450
+ }
451
+ });
452
+ }
453
+ ```
454
+
455
+ ## Best Practices
456
+
457
+ 1. **Use DatabaseConnection Type**: Accept `DatabaseConnection` in functions that work with transactions
458
+ ```typescript
459
+ async function myFunction(db: DatabaseConnection<Database>) { }
460
+ ```
461
+
462
+ 2. **Let withTransaction Handle Reuse**: Don't manually check for transaction type
463
+ ```typescript
464
+ // Good
465
+ await withTransaction(db, async (trx) => { });
466
+
467
+ // Avoid
468
+ if (db.isTransaction) { /* ... */ } else { /* ... */ }
469
+ ```
470
+
471
+ 3. **Keep Transactions Short**: Execute quickly to avoid blocking
472
+ ```typescript
473
+ // Good
474
+ await withTransaction(db, async (trx) => {
475
+ await trx.insertInto('users').values(data).execute();
476
+ });
477
+
478
+ // Avoid long-running operations
479
+ await withTransaction(db, async (trx) => {
480
+ await fetch('https://api.example.com'); // Bad!
481
+ });
482
+ ```
483
+
484
+ 4. **Error Handling**: Let transactions roll back automatically on errors
485
+
486
+ 5. **Testing**: Use transactions for test isolation with automatic rollback
487
+
488
+ ## Related Packages
489
+
490
+ - [Kysely](https://github.com/kysely-org/kysely) - Type-safe SQL query builder
491
+ - [@geekmidas/testkit](../testkit) - Testing utilities with database factories
492
+
493
+ ## License
494
+
495
+ MIT
@@ -0,0 +1,11 @@
1
+ import { ControlledTransaction, IsolationLevel, Kysely, Transaction } from "kysely";
2
+
3
+ //#region src/kysely.d.ts
4
+ interface TransactionSettings {
5
+ isolationLevel?: IsolationLevel;
6
+ }
7
+ declare function withTransaction<DB, T>(db: DatabaseConnection<DB>, cb: (trx: Transaction<DB>) => Promise<T>, settings?: TransactionSettings): Promise<T>;
8
+ type DatabaseConnection<T> = ControlledTransaction<T> | Kysely<T> | Transaction<T>;
9
+ //#endregion
10
+ export { DatabaseConnection, TransactionSettings, withTransaction };
11
+ //# sourceMappingURL=kysely-0FOi6ZdO.d.cts.map
@@ -0,0 +1,17 @@
1
+
2
+ //#region src/kysely.ts
3
+ function withTransaction(db, cb, settings) {
4
+ if (db.isTransaction) return cb(db);
5
+ const builder = db.transaction();
6
+ if (settings?.isolationLevel) return builder.setIsolationLevel(settings.isolationLevel).execute(cb);
7
+ return builder.execute(cb);
8
+ }
9
+
10
+ //#endregion
11
+ Object.defineProperty(exports, 'withTransaction', {
12
+ enumerable: true,
13
+ get: function () {
14
+ return withTransaction;
15
+ }
16
+ });
17
+ //# sourceMappingURL=kysely-8WPSKCZG.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-8WPSKCZG.cjs","names":["db: DatabaseConnection<DB>","cb: (trx: Transaction<DB>) => Promise<T>","settings?: TransactionSettings"],"sources":["../src/kysely.ts"],"sourcesContent":["import type {\n ControlledTransaction,\n IsolationLevel,\n Kysely,\n Transaction,\n} from 'kysely';\n\nexport interface TransactionSettings {\n isolationLevel?: IsolationLevel;\n}\n\nexport function withTransaction<DB, T>(\n db: DatabaseConnection<DB>,\n cb: (trx: Transaction<DB>) => Promise<T>,\n settings?: TransactionSettings,\n): Promise<T> {\n if (db.isTransaction) {\n return cb(db as Transaction<DB>);\n }\n\n const builder = db.transaction();\n\n if (settings?.isolationLevel) {\n return builder.setIsolationLevel(settings.isolationLevel).execute(cb);\n }\n\n return builder.execute(cb);\n}\n\nexport type DatabaseConnection<T> =\n | ControlledTransaction<T>\n | Kysely<T>\n | Transaction<T>;\n"],"mappings":";;AAWA,SAAgB,gBACdA,IACAC,IACAC,UACY;AACZ,KAAI,GAAG,cACL,QAAO,GAAG,GAAsB;CAGlC,MAAM,UAAU,GAAG,aAAa;AAEhC,KAAI,UAAU,eACZ,QAAO,QAAQ,kBAAkB,SAAS,eAAe,CAAC,QAAQ,GAAG;AAGvE,QAAO,QAAQ,QAAQ,GAAG;AAC3B"}
@@ -0,0 +1,11 @@
1
+ import { ControlledTransaction, IsolationLevel, Kysely, Transaction } from "kysely";
2
+
3
+ //#region src/kysely.d.ts
4
+ interface TransactionSettings {
5
+ isolationLevel?: IsolationLevel;
6
+ }
7
+ declare function withTransaction<DB, T>(db: DatabaseConnection<DB>, cb: (trx: Transaction<DB>) => Promise<T>, settings?: TransactionSettings): Promise<T>;
8
+ type DatabaseConnection<T> = ControlledTransaction<T> | Kysely<T> | Transaction<T>;
9
+ //#endregion
10
+ export { DatabaseConnection, TransactionSettings, withTransaction };
11
+ //# sourceMappingURL=kysely-Di1LVvL2.d.mts.map
@@ -0,0 +1,11 @@
1
+ //#region src/kysely.ts
2
+ function withTransaction(db, cb, settings) {
3
+ if (db.isTransaction) return cb(db);
4
+ const builder = db.transaction();
5
+ if (settings?.isolationLevel) return builder.setIsolationLevel(settings.isolationLevel).execute(cb);
6
+ return builder.execute(cb);
7
+ }
8
+
9
+ //#endregion
10
+ export { withTransaction };
11
+ //# sourceMappingURL=kysely-DmfA94RY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-DmfA94RY.mjs","names":["db: DatabaseConnection<DB>","cb: (trx: Transaction<DB>) => Promise<T>","settings?: TransactionSettings"],"sources":["../src/kysely.ts"],"sourcesContent":["import type {\n ControlledTransaction,\n IsolationLevel,\n Kysely,\n Transaction,\n} from 'kysely';\n\nexport interface TransactionSettings {\n isolationLevel?: IsolationLevel;\n}\n\nexport function withTransaction<DB, T>(\n db: DatabaseConnection<DB>,\n cb: (trx: Transaction<DB>) => Promise<T>,\n settings?: TransactionSettings,\n): Promise<T> {\n if (db.isTransaction) {\n return cb(db as Transaction<DB>);\n }\n\n const builder = db.transaction();\n\n if (settings?.isolationLevel) {\n return builder.setIsolationLevel(settings.isolationLevel).execute(cb);\n }\n\n return builder.execute(cb);\n}\n\nexport type DatabaseConnection<T> =\n | ControlledTransaction<T>\n | Kysely<T>\n | Transaction<T>;\n"],"mappings":";AAWA,SAAgB,gBACdA,IACAC,IACAC,UACY;AACZ,KAAI,GAAG,cACL,QAAO,GAAG,GAAsB;CAGlC,MAAM,UAAU,GAAG,aAAa;AAEhC,KAAI,UAAU,eACZ,QAAO,QAAQ,kBAAkB,SAAS,eAAe,CAAC,QAAQ,GAAG;AAGvE,QAAO,QAAQ,QAAQ,GAAG;AAC3B"}
package/dist/kysely.cjs CHANGED
@@ -1,9 +1,3 @@
1
+ const require_kysely = require('./kysely-8WPSKCZG.cjs');
1
2
 
2
- //#region src/kysely.ts
3
- function withTransaction(db, cb) {
4
- if (db.isTransaction) return cb(db);
5
- return db.transaction().execute(cb);
6
- }
7
-
8
- //#endregion
9
- exports.withTransaction = withTransaction;
3
+ exports.withTransaction = require_kysely.withTransaction;
package/dist/kysely.d.cts CHANGED
@@ -1,7 +1,2 @@
1
- import { ControlledTransaction, Kysely, Transaction } from "kysely";
2
-
3
- //#region src/kysely.d.ts
4
- declare function withTransaction<DB extends DatabaseConnection<any>, T>(db: DB, cb: (trx: Transaction<DB>) => Promise<T>): Promise<T>;
5
- type DatabaseConnection<T> = ControlledTransaction<T> | Kysely<T> | Transaction<T>;
6
- //#endregion
7
- export { DatabaseConnection, withTransaction };
1
+ import { DatabaseConnection, TransactionSettings, withTransaction } from "./kysely-0FOi6ZdO.cjs";
2
+ export { DatabaseConnection, TransactionSettings, withTransaction };
package/dist/kysely.d.mts CHANGED
@@ -1,7 +1,2 @@
1
- import { ControlledTransaction, Kysely, Transaction } from "kysely";
2
-
3
- //#region src/kysely.d.ts
4
- declare function withTransaction<DB extends DatabaseConnection<any>, T>(db: DB, cb: (trx: Transaction<DB>) => Promise<T>): Promise<T>;
5
- type DatabaseConnection<T> = ControlledTransaction<T> | Kysely<T> | Transaction<T>;
6
- //#endregion
7
- export { DatabaseConnection, withTransaction };
1
+ import { DatabaseConnection, TransactionSettings, withTransaction } from "./kysely-Di1LVvL2.mjs";
2
+ export { DatabaseConnection, TransactionSettings, withTransaction };
package/dist/kysely.mjs CHANGED
@@ -1,8 +1,3 @@
1
- //#region src/kysely.ts
2
- function withTransaction(db, cb) {
3
- if (db.isTransaction) return cb(db);
4
- return db.transaction().execute(cb);
5
- }
1
+ import { withTransaction } from "./kysely-DmfA94RY.mjs";
6
2
 
7
- //#endregion
8
3
  export { withTransaction };
package/dist/rls.cjs ADDED
@@ -0,0 +1,72 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const require_kysely = require('./kysely-8WPSKCZG.cjs');
25
+ const kysely = __toESM(require("kysely"));
26
+
27
+ //#region src/rls.ts
28
+ /**
29
+ * Execute a callback within a transaction with RLS context variables set.
30
+ *
31
+ * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the
32
+ * current transaction. Variables are automatically cleared when the transaction
33
+ * ends (commit or rollback).
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * await withRlsContext(
38
+ * db,
39
+ * { user_id: session.userId, tenant_id: session.tenantId },
40
+ * async (trx) => {
41
+ * // RLS policies can now use current_setting('app.user_id')
42
+ * return trx.selectFrom('orders').selectAll().execute();
43
+ * }
44
+ * );
45
+ * ```
46
+ *
47
+ * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)
48
+ * @param context - Key-value pairs to set as session variables
49
+ * @param callback - Function to execute within the RLS context
50
+ * @param options - Optional prefix and transaction settings
51
+ */
52
+ async function withRlsContext(db, context, callback, options) {
53
+ const prefix = options?.prefix ?? "app";
54
+ return require_kysely.withTransaction(db, async (trx) => {
55
+ for (const [key, value] of Object.entries(context)) {
56
+ if (value === null || value === void 0) continue;
57
+ const settingName = `${prefix}.${key}`;
58
+ const settingValue = String(value);
59
+ await kysely.sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(trx);
60
+ }
61
+ return callback(trx);
62
+ }, options?.settings);
63
+ }
64
+ /**
65
+ * Bypass marker symbol for explicitly skipping RLS context.
66
+ */
67
+ const RLS_BYPASS = Symbol.for("geekmidas.rls.bypass");
68
+
69
+ //#endregion
70
+ exports.RLS_BYPASS = RLS_BYPASS;
71
+ exports.withRlsContext = withRlsContext;
72
+ //# sourceMappingURL=rls.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls.cjs","names":["db: DatabaseConnection<DB>","context: RlsContext","callback: (trx: Transaction<DB>) => Promise<T>","options?: WithRlsContextOptions"],"sources":["../src/rls.ts"],"sourcesContent":["import type { Transaction } from 'kysely';\nimport { sql } from 'kysely';\nimport {\n type DatabaseConnection,\n type TransactionSettings,\n withTransaction,\n} from './kysely';\n\n/**\n * RLS context - key-value pairs to set as PostgreSQL session variables.\n * Keys become `prefix.key` (e.g., `app.user_id`).\n */\nexport interface RlsContext {\n [key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Options for withRlsContext function.\n */\nexport interface WithRlsContextOptions {\n /** Prefix for PostgreSQL session variables (default: 'app') */\n prefix?: string;\n /** Transaction settings (isolation level) */\n settings?: TransactionSettings;\n}\n\n/**\n * Execute a callback within a transaction with RLS context variables set.\n *\n * Sets PostgreSQL session variables using `SET LOCAL` which scopes them to the\n * current transaction. Variables are automatically cleared when the transaction\n * ends (commit or rollback).\n *\n * @example\n * ```ts\n * await withRlsContext(\n * db,\n * { user_id: session.userId, tenant_id: session.tenantId },\n * async (trx) => {\n * // RLS policies can now use current_setting('app.user_id')\n * return trx.selectFrom('orders').selectAll().execute();\n * }\n * );\n * ```\n *\n * @param db - Database connection (Kysely, Transaction, or ControlledTransaction)\n * @param context - Key-value pairs to set as session variables\n * @param callback - Function to execute within the RLS context\n * @param options - Optional prefix and transaction settings\n */\nexport async function withRlsContext<DB, T>(\n db: DatabaseConnection<DB>,\n context: RlsContext,\n callback: (trx: Transaction<DB>) => Promise<T>,\n options?: WithRlsContextOptions,\n): Promise<T> {\n const prefix = options?.prefix ?? 'app';\n\n return withTransaction(\n db,\n async (trx) => {\n // Set each context variable using SET LOCAL (scoped to transaction)\n for (const [key, value] of Object.entries(context)) {\n if (value === null || value === undefined) continue;\n\n const settingName = `${prefix}.${key}`;\n const settingValue = String(value);\n\n // Use raw SQL for SET LOCAL with proper escaping\n // The setting name is an identifier, value is a string literal\n await sql`SELECT set_config(${settingName}, ${settingValue}, true)`.execute(\n trx,\n );\n }\n\n return callback(trx);\n },\n options?.settings,\n );\n}\n\n/**\n * Bypass marker symbol for explicitly skipping RLS context.\n */\nexport const RLS_BYPASS = Symbol.for('geekmidas.rls.bypass');\n\n/**\n * Type for RLS bypass marker.\n */\nexport type RlsBypass = typeof RLS_BYPASS;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,eAAsB,eACpBA,IACAC,SACAC,UACAC,SACY;CACZ,MAAM,SAAS,SAAS,UAAU;AAElC,QAAO,+BACL,IACA,OAAO,QAAQ;AAEb,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,QAAQ,EAAE;AAClD,OAAI,UAAU,QAAQ,iBAAqB;GAE3C,MAAM,eAAe,EAAE,OAAO,GAAG,IAAI;GACrC,MAAM,eAAe,OAAO,MAAM;AAIlC,SAAM,WAAI,oBAAoB,YAAY,IAAI,aAAa,SAAS,QAClE,IACD;EACF;AAED,SAAO,SAAS,IAAI;CACrB,GACD,SAAS,SACV;AACF;;;;AAKD,MAAa,aAAa,OAAO,IAAI,uBAAuB"}