drizzle-orm-test 0.2.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 +23 -0
- package/README.md +455 -0
- package/esm/index.js +74 -0
- package/esm/proxy-client.js +75 -0
- package/index.d.ts +65 -0
- package/index.js +80 -0
- package/package.json +60 -0
- package/proxy-client.d.ts +24 -0
- package/proxy-client.js +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Hyperweb <developers@hyperweb.io>
|
|
5
|
+
Copyright (c) 2020-present, Interweb, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# drizzle-orm-test
|
|
2
|
+
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://raw.githubusercontent.com/launchql/launchql/refs/heads/main/assets/outline-logo.svg" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/launchql/launchql/actions/workflows/run-tests.yaml">
|
|
9
|
+
<img height="20" src="https://github.com/launchql/launchql/actions/workflows/run-tests.yaml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/launchql/launchql/blob/main/LICENSE">
|
|
12
|
+
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/drizzle-orm-test">
|
|
15
|
+
<img height="20" src="https://img.shields.io/github/package-json/v/launchql/launchql?filename=packages%2Fdrizzle-orm-test%2Fpackage.json"/>
|
|
16
|
+
</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
Drop-in replacement for [`pgsql-test`](https://www.npmjs.com/package/pgsql-test) that enables Drizzle ORM to work seamlessly with context management and RLS (Row-Level Security) testing.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install drizzle-orm-test drizzle-orm pg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
* ๐ฏ **Drizzle ORM integration** โ automatic context management for Drizzle queries with RLS support
|
|
30
|
+
* โก **Instant test DBs** โ each one seeded, isolated, and UUID-named
|
|
31
|
+
* ๐ **Per-test rollback** โ every test runs in its own transaction or savepoint
|
|
32
|
+
* ๐ก๏ธ **RLS-friendly** โ test with role-based auth via `.setContext()`
|
|
33
|
+
* ๐ฑ **Flexible seeding** โ run `.sql` files, programmatic seeds, or even load fixtures
|
|
34
|
+
* ๐งช **Compatible with any async runner** โ works with `Jest`, `Mocha`, etc.
|
|
35
|
+
* ๐งน **Auto teardown** โ no residue, no reboots, just clean exits
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Basic Example
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
43
|
+
import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
44
|
+
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
|
|
45
|
+
|
|
46
|
+
// Define your schema
|
|
47
|
+
const users = pgTable('users', {
|
|
48
|
+
id: serial('id').primaryKey(),
|
|
49
|
+
name: text('name').notNull(),
|
|
50
|
+
userId: text('user_id')
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let db: PgTestClient;
|
|
54
|
+
let pg: PgTestClient;
|
|
55
|
+
let teardown: () => Promise<void>;
|
|
56
|
+
|
|
57
|
+
beforeAll(async () => {
|
|
58
|
+
({ db, pg, teardown } = await getConnections());
|
|
59
|
+
|
|
60
|
+
// Setup schema using pg (superuser)
|
|
61
|
+
await pg.query(`
|
|
62
|
+
CREATE TABLE users (
|
|
63
|
+
id SERIAL PRIMARY KEY,
|
|
64
|
+
name TEXT NOT NULL,
|
|
65
|
+
user_id TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
GRANT ALL ON TABLE users TO authenticated;
|
|
69
|
+
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO authenticated;
|
|
70
|
+
|
|
71
|
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
72
|
+
|
|
73
|
+
CREATE POLICY users_policy ON users
|
|
74
|
+
FOR ALL TO authenticated
|
|
75
|
+
USING (user_id = current_setting('jwt.claims.user_id', true));
|
|
76
|
+
`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterAll(async () => {
|
|
80
|
+
await teardown();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
await db.beforeEach();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(async () => {
|
|
88
|
+
await db.afterEach();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Drizzle with RLS', () => {
|
|
92
|
+
it('should respect authentication context', async () => {
|
|
93
|
+
// Set context on db (standard pgsql-test API)
|
|
94
|
+
db.setContext({
|
|
95
|
+
role: 'authenticated',
|
|
96
|
+
'jwt.claims.user_id': '123'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Use standard Drizzle pattern - no wrapper needed!
|
|
100
|
+
const drizzleDb = drizzle(db.client);
|
|
101
|
+
|
|
102
|
+
// All queries will include the context (SET LOCAL statements)
|
|
103
|
+
const result = await drizzleDb.select().from(users);
|
|
104
|
+
expect(result).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Using with Schema
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
113
|
+
import { getConnections } from 'drizzle-orm-test';
|
|
114
|
+
import * as schema from './schema';
|
|
115
|
+
|
|
116
|
+
const { db, teardown } = await getConnections();
|
|
117
|
+
|
|
118
|
+
// Set context on db
|
|
119
|
+
db.setContext({
|
|
120
|
+
role: 'authenticated',
|
|
121
|
+
'jwt.claims.user_id': '123'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Use standard Drizzle pattern with schema
|
|
125
|
+
const drizzleDb = drizzle(db.client, { schema });
|
|
126
|
+
|
|
127
|
+
// Now you can use relational queries
|
|
128
|
+
const usersWithPosts = await drizzleDb.query.users.findMany({
|
|
129
|
+
with: {
|
|
130
|
+
posts: true
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## API
|
|
136
|
+
|
|
137
|
+
> **Note:** For seeding documentation, see the [pgsql-test README](https://www.npmjs.com/package/pgsql-test) directly. The seeding API is identical since `drizzle-orm-test` uses the same `getConnections()` function and seed adapters.
|
|
138
|
+
|
|
139
|
+
### `getConnections(cn?, seedAdapters?)`
|
|
140
|
+
|
|
141
|
+
Drop-in replacement for `pgsql-test`'s `getConnections` that patches `db.client.query()` to automatically apply context before each query.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
- `cn?: GetConnectionOpts` - Connection options (same as pgsql-test)
|
|
145
|
+
- `seedAdapters?: SeedAdapter[]` - Optional seed adapters for test data
|
|
146
|
+
|
|
147
|
+
**Returns:** `Promise<{ pg, db, admin, teardown, manager }>`
|
|
148
|
+
- `pg: PgTestClient` - Superuser client for schema setup (bypasses RLS)
|
|
149
|
+
- `db: PgTestClient` - App-level client with patched `client.query()` for RLS testing
|
|
150
|
+
- `admin: DbAdmin` - CLI operations (createdb, dropdb, etc.)
|
|
151
|
+
- `teardown: () => Promise<void>` - Cleanup function
|
|
152
|
+
- `manager: PgTestConnector` - Connection pool manager
|
|
153
|
+
|
|
154
|
+
### `PgTestClient`
|
|
155
|
+
|
|
156
|
+
Re-exported from `pgsql-test` for convenience. See [pgsql-test documentation](https://github.com/launchql/launchql/tree/main/packages/pgsql-test) for full API.
|
|
157
|
+
|
|
158
|
+
**Key Methods:**
|
|
159
|
+
- `setContext(ctx: Record<string, string>)` - Set context variables (role, JWT claims, etc.)
|
|
160
|
+
- `auth(options?: AuthOptions)` - Set authentication context (shorthand for common JWT claims)
|
|
161
|
+
- `clearContext()` - Clear all context
|
|
162
|
+
- `beforeEach()` - Start transaction for test isolation
|
|
163
|
+
- `afterEach()` - Rollback transaction
|
|
164
|
+
- `publish()` - Commit data to make visible to other connections
|
|
165
|
+
- `query<T>(sql, values?)` - Execute query with context applied
|
|
166
|
+
|
|
167
|
+
## Advanced Usage
|
|
168
|
+
|
|
169
|
+
### Testing Row-Level Security
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
173
|
+
import { getConnections } from 'drizzle-orm-test';
|
|
174
|
+
import { eq } from 'drizzle-orm';
|
|
175
|
+
|
|
176
|
+
describe('RLS Policies', () => {
|
|
177
|
+
let db, pg, teardown;
|
|
178
|
+
|
|
179
|
+
beforeAll(async () => {
|
|
180
|
+
({ db, pg, teardown } = await getConnections());
|
|
181
|
+
|
|
182
|
+
// Setup schema with RLS using pg (superuser)
|
|
183
|
+
await pg.query(`
|
|
184
|
+
CREATE TABLE posts (
|
|
185
|
+
id SERIAL PRIMARY KEY,
|
|
186
|
+
user_id TEXT NOT NULL,
|
|
187
|
+
content TEXT NOT NULL
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
GRANT ALL ON TABLE posts TO authenticated;
|
|
191
|
+
GRANT USAGE, SELECT ON SEQUENCE posts_id_seq TO authenticated;
|
|
192
|
+
|
|
193
|
+
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
|
194
|
+
|
|
195
|
+
CREATE POLICY user_posts ON posts
|
|
196
|
+
FOR ALL TO authenticated
|
|
197
|
+
USING (user_id = current_setting('jwt.claims.user_id', true));
|
|
198
|
+
|
|
199
|
+
-- Seed test data using pg (bypasses RLS)
|
|
200
|
+
INSERT INTO posts (user_id, content)
|
|
201
|
+
VALUES ('1', 'User 1 Post'), ('2', 'User 2 Post');
|
|
202
|
+
`);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
afterAll(() => teardown());
|
|
206
|
+
|
|
207
|
+
describe('as user 1', () => {
|
|
208
|
+
beforeEach(async () => {
|
|
209
|
+
await db.beforeEach();
|
|
210
|
+
db.setContext({
|
|
211
|
+
role: 'authenticated',
|
|
212
|
+
'jwt.claims.user_id': '1'
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
afterEach(() => db.afterEach());
|
|
217
|
+
|
|
218
|
+
it('can only see own posts', async () => {
|
|
219
|
+
const drizzleDb = drizzle(db.client);
|
|
220
|
+
|
|
221
|
+
// Query with Drizzle - should only see user 1's posts
|
|
222
|
+
const posts = await drizzleDb.select().from(postsTable);
|
|
223
|
+
expect(posts).toHaveLength(1);
|
|
224
|
+
expect(posts[0].content).toBe('User 1 Post');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('can insert own posts', async () => {
|
|
228
|
+
const drizzleDb = drizzle(db.client);
|
|
229
|
+
|
|
230
|
+
await drizzleDb.insert(postsTable).values({
|
|
231
|
+
userId: '1',
|
|
232
|
+
content: 'New Post'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const posts = await drizzleDb.select().from(postsTable);
|
|
236
|
+
expect(posts).toHaveLength(2);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('as user 2', () => {
|
|
241
|
+
beforeEach(async () => {
|
|
242
|
+
await db.beforeEach();
|
|
243
|
+
db.setContext({
|
|
244
|
+
role: 'authenticated',
|
|
245
|
+
'jwt.claims.user_id': '2'
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
afterEach(() => db.afterEach());
|
|
250
|
+
|
|
251
|
+
it('can only see own posts', async () => {
|
|
252
|
+
const drizzleDb = drizzle(db.client);
|
|
253
|
+
|
|
254
|
+
const posts = await drizzleDb.select().from(postsTable);
|
|
255
|
+
expect(posts).toHaveLength(1);
|
|
256
|
+
expect(posts[0].content).toBe('User 2 Post');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Multi-Connection Testing
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
266
|
+
import { getConnections } from 'drizzle-orm-test';
|
|
267
|
+
|
|
268
|
+
let db1, db2, teardown;
|
|
269
|
+
|
|
270
|
+
beforeAll(async () => {
|
|
271
|
+
const conn1 = await getConnections();
|
|
272
|
+
const conn2 = await getConnections();
|
|
273
|
+
|
|
274
|
+
db1 = conn1.db;
|
|
275
|
+
db2 = conn2.db;
|
|
276
|
+
|
|
277
|
+
teardown = async () => {
|
|
278
|
+
await conn1.teardown();
|
|
279
|
+
await conn2.teardown();
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
afterAll(() => teardown());
|
|
284
|
+
|
|
285
|
+
it('can test cross-connection visibility', async () => {
|
|
286
|
+
const drizzle1 = drizzle(db1.client);
|
|
287
|
+
const drizzle2 = drizzle(db2.client);
|
|
288
|
+
|
|
289
|
+
// Set context for both connections
|
|
290
|
+
db1.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
291
|
+
db2.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
292
|
+
|
|
293
|
+
// Insert in first connection
|
|
294
|
+
await drizzle1.insert(users).values({ name: 'Alice', userId: '1' });
|
|
295
|
+
|
|
296
|
+
// Not visible to second connection yet (transaction isolation)
|
|
297
|
+
let result = await drizzle2.select().from(users);
|
|
298
|
+
expect(result).toHaveLength(0);
|
|
299
|
+
|
|
300
|
+
// Publish to make visible
|
|
301
|
+
await db1.publish();
|
|
302
|
+
|
|
303
|
+
// Now visible to second connection
|
|
304
|
+
result = await drizzle2.select().from(users);
|
|
305
|
+
expect(result).toHaveLength(1);
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Context Switching
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
it('can switch context between queries', async () => {
|
|
313
|
+
const drizzleDb = drizzle(db.client);
|
|
314
|
+
|
|
315
|
+
// Query as user 1
|
|
316
|
+
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
317
|
+
let posts = await drizzleDb.select().from(postsTable);
|
|
318
|
+
expect(posts.every(p => p.userId === '1')).toBe(true);
|
|
319
|
+
|
|
320
|
+
// Switch to user 2
|
|
321
|
+
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '2' });
|
|
322
|
+
posts = await drizzleDb.select().from(postsTable);
|
|
323
|
+
expect(posts.every(p => p.userId === '2')).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## The Problem
|
|
328
|
+
|
|
329
|
+
When using Drizzle ORM directly with `pgsql-test`, the context management (SET LOCAL statements for role and JWT claims) doesn't work because Drizzle calls the underlying `pg.Client.query()` directly, bypassing `PgTestClient`'s `ctxQuery()` method that applies the context.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// This doesn't work with plain pgsql-test - context is ignored!
|
|
333
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
334
|
+
import { getConnections } from 'pgsql-test';
|
|
335
|
+
|
|
336
|
+
const { db } = await getConnections();
|
|
337
|
+
db.setContext({ userId: '123' }); // This context is never applied!
|
|
338
|
+
|
|
339
|
+
const drizzleDb = drizzle(db.client); // Drizzle bypasses PgTestClient
|
|
340
|
+
const result = await drizzleDb.select().from(users); // No RLS context applied โ
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## The Solution
|
|
344
|
+
|
|
345
|
+
`drizzle-orm-test` is a drop-in replacement for `pgsql-test` that patches `db.client.query()` to automatically apply context before each query. This allows you to use the standard Drizzle pattern while maintaining full RLS support.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
349
|
+
import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
350
|
+
|
|
351
|
+
const { db } = await getConnections();
|
|
352
|
+
db.setContext({ userId: '123' }); // Context is applied!
|
|
353
|
+
|
|
354
|
+
const drizzleDb = drizzle(db.client); // Standard Drizzle pattern
|
|
355
|
+
const result = await drizzleDb.select().from(users); // RLS context applied โ
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## How It Works
|
|
359
|
+
|
|
360
|
+
`drizzle-orm-test` patches `db.client.query()` to automatically execute SET LOCAL statements before each query. This ensures that:
|
|
361
|
+
|
|
362
|
+
1. **Context is applied**: Role and JWT claims are set via `SET LOCAL` before every Drizzle query
|
|
363
|
+
2. **RLS policies work**: PostgreSQL Row-Level Security policies can check `current_setting('jwt.claims.*')`
|
|
364
|
+
3. **Transaction isolation**: Changes are isolated between tests via `beforeEach`/`afterEach`
|
|
365
|
+
4. **Standard Drizzle pattern**: You can use `drizzle(db.client)` directly without wrappers
|
|
366
|
+
|
|
367
|
+
The implementation avoids infinite recursion by directly executing the context statements using the original query method, rather than calling `db.ctxQuery()` which would create a circular call.
|
|
368
|
+
|
|
369
|
+
## Migration from pgsql-test
|
|
370
|
+
|
|
371
|
+
If you're already using `pgsql-test`, migration is simple:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Before (pgsql-test)
|
|
375
|
+
import { getConnections, PgTestClient } from 'pgsql-test';
|
|
376
|
+
|
|
377
|
+
// After (drizzle-orm-test)
|
|
378
|
+
import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
379
|
+
|
|
380
|
+
// Everything else stays the same!
|
|
381
|
+
// Now you can also use Drizzle ORM with full RLS support
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Education and Tutorials
|
|
385
|
+
|
|
386
|
+
1. ๐ [Quickstart: Getting Up and Running](https://launchql.com/learn/quickstart)
|
|
387
|
+
Get started with modular databases in minutes. Install prerequisites and deploy your first module.
|
|
388
|
+
|
|
389
|
+
2. ๐ฆ [Modular PostgreSQL Development with Database Packages](https://launchql.com/learn/modular-postgres)
|
|
390
|
+
Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
|
|
391
|
+
|
|
392
|
+
3. โ๏ธ [Authoring Database Changes](https://launchql.com/learn/authoring-database-changes)
|
|
393
|
+
Master the workflow for adding, organizing, and managing database changes with pgpm.
|
|
394
|
+
|
|
395
|
+
4. ๐งช [End-to-End PostgreSQL Testing with TypeScript](https://launchql.com/learn/e2e-postgres-testing)
|
|
396
|
+
Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
|
|
397
|
+
|
|
398
|
+
5. โก [Supabase Testing](https://launchql.com/learn/supabase)
|
|
399
|
+
TypeScript-native testing for Supabase with modern workflows.
|
|
400
|
+
|
|
401
|
+
6. ๐ง [Troubleshooting](https://launchql.com/learn/troubleshooting)
|
|
402
|
+
Common issues and solutions for pgpm, PostgreSQL, and testing.
|
|
403
|
+
|
|
404
|
+
## Related LaunchQL Tooling
|
|
405
|
+
|
|
406
|
+
### ๐งช Testing
|
|
407
|
+
|
|
408
|
+
* [launchql/pgsql-test](https://github.com/launchql/launchql/tree/main/packages/pgsql-test): **๐ Isolated testing environments** with per-test transaction rollbacksโideal for integration tests, complex migrations, and RLS simulation.
|
|
409
|
+
* [launchql/supabase-test](https://github.com/launchql/launchql/tree/main/packages/supabase-test): **๐งช Supabase-native test harness** preconfigured for the local Supabase stackโper-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
410
|
+
* [launchql/graphile-test](https://github.com/launchql/launchql/tree/main/packages/graphile-test): **๐ Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
411
|
+
* [launchql/pg-query-context](https://github.com/launchql/launchql/tree/main/packages/pg-query-context): **๐ Session context injection** to add session-local context (e.g., `SET LOCAL`) into queriesโideal for setting `role`, `jwt.claims`, and other session settings.
|
|
412
|
+
|
|
413
|
+
### ๐ง Parsing & AST
|
|
414
|
+
|
|
415
|
+
* [launchql/pgsql-parser](https://github.com/launchql/pgsql-parser): **๐ SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
416
|
+
* [launchql/libpg-query-node](https://github.com/launchql/libpg-query-node): **๐ Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
417
|
+
* [launchql/pg-proto-parser](https://github.com/launchql/pg-proto-parser): **๐ฆ Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
418
|
+
* [@pgsql/enums](https://github.com/launchql/pgsql-parser/tree/main/packages/enums): **๐ท๏ธ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
|
|
419
|
+
* [@pgsql/types](https://github.com/launchql/pgsql-parser/tree/main/packages/types): **๐ Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
420
|
+
* [@pgsql/utils](https://github.com/launchql/pgsql-parser/tree/main/packages/utils): **๐ ๏ธ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
421
|
+
* [launchql/pg-ast](https://github.com/launchql/launchql/tree/main/packages/pg-ast): **๐ Low-level AST tools** and transformations for Postgres query structures.
|
|
422
|
+
|
|
423
|
+
### ๐ API & Dev Tools
|
|
424
|
+
|
|
425
|
+
* [launchql/server](https://github.com/launchql/launchql/tree/main/packages/server): **โก Express-based API server** powered by PostGraphile to expose a secure, scalable GraphQL API over your Postgres database.
|
|
426
|
+
* [launchql/explorer](https://github.com/launchql/launchql/tree/main/packages/explorer): **๐ Visual API explorer** with GraphiQL for browsing across all databases and schemasโuseful for debugging, documentation, and API prototyping.
|
|
427
|
+
|
|
428
|
+
### ๐ Streaming & Uploads
|
|
429
|
+
|
|
430
|
+
* [launchql/s3-streamer](https://github.com/launchql/launchql/tree/main/packages/s3-streamer): **๐ค Direct S3 streaming** for large files with support for metadata injection and content validation.
|
|
431
|
+
* [launchql/etag-hash](https://github.com/launchql/launchql/tree/main/packages/etag-hash): **๐ท๏ธ S3-compatible ETags** created by streaming and hashing file uploads in chunks.
|
|
432
|
+
* [launchql/etag-stream](https://github.com/launchql/launchql/tree/main/packages/etag-stream): **๐ ETag computation** via Node stream transformer during upload or transfer.
|
|
433
|
+
* [launchql/uuid-hash](https://github.com/launchql/launchql/tree/main/packages/uuid-hash): **๐ Deterministic UUIDs** generated from hashed content, great for deduplication and asset referencing.
|
|
434
|
+
* [launchql/uuid-stream](https://github.com/launchql/launchql/tree/main/packages/uuid-stream): **๐ Streaming UUID generation** based on piped file contentโideal for upload pipelines.
|
|
435
|
+
* [launchql/upload-names](https://github.com/launchql/launchql/tree/main/packages/upload-names): **๐ Collision-resistant filenames** utility for structured and unique file names for uploads.
|
|
436
|
+
|
|
437
|
+
### ๐งฐ CLI & Codegen
|
|
438
|
+
|
|
439
|
+
* [pgpm](https://github.com/launchql/launchql/tree/main/packages/pgpm): **๐ฅ๏ธ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
440
|
+
* [@launchql/cli](https://github.com/launchql/launchql/tree/main/packages/cli): **๐ฅ๏ธ Command-line toolkit** for managing LaunchQL projectsโsupports database scaffolding, migrations, seeding, code generation, and automation.
|
|
441
|
+
* [launchql/launchql-gen](https://github.com/launchql/launchql/tree/main/packages/launchql-gen): **โจ Auto-generated GraphQL** mutations and queries dynamically built from introspected schema data.
|
|
442
|
+
* [@launchql/query-builder](https://github.com/launchql/launchql/tree/main/packages/query-builder): **๐๏ธ SQL constructor** providing a robust TypeScript-based query builder for dynamic generation of `SELECT`, `INSERT`, `UPDATE`, `DELETE`, and stored procedure callsโsupports advanced SQL features like `JOIN`, `GROUP BY`, and schema-qualified queries.
|
|
443
|
+
* [@launchql/query](https://github.com/launchql/launchql/tree/main/packages/query): **๐งฉ Fluent GraphQL builder** for PostGraphile schemas. โก Schema-aware via introspection, ๐งฉ composable and ergonomic for building deeply nested queries.
|
|
444
|
+
|
|
445
|
+
## Credits
|
|
446
|
+
|
|
447
|
+
๐ Built by LaunchQL โย if you like our tools, please checkout and contribute to [our github โ๏ธ](https://github.com/launchql)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
## Disclaimer
|
|
451
|
+
|
|
452
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
453
|
+
|
|
454
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
|
455
|
+
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* drizzle-orm-test
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly
|
|
5
|
+
* with context management and RLS testing.
|
|
6
|
+
*
|
|
7
|
+
* This package patches db.client.query() to automatically apply context (SET LOCAL
|
|
8
|
+
* statements for role and JWT claims) before each query, allowing you to use the
|
|
9
|
+
* standard Drizzle pattern: drizzle(db.client) while maintaining full RLS support.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { drizzle } from 'drizzle-orm/node-postgres';
|
|
14
|
+
* import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
15
|
+
*
|
|
16
|
+
* let db: PgTestClient;
|
|
17
|
+
* let pg: PgTestClient;
|
|
18
|
+
* let teardown: () => Promise<void>;
|
|
19
|
+
*
|
|
20
|
+
* beforeAll(async () => {
|
|
21
|
+
* ({ pg, db, teardown } = await getConnections());
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* afterAll(async () => {
|
|
25
|
+
* await teardown();
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* beforeEach(async () => {
|
|
29
|
+
* await db.beforeEach();
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* afterEach(async () => {
|
|
33
|
+
* await db.afterEach();
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* describe('your tests', () => {
|
|
37
|
+
* it('should work with standard Drizzle pattern', async () => {
|
|
38
|
+
* // Set context on db (standard pgsql-test API)
|
|
39
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
40
|
+
*
|
|
41
|
+
* // Use standard Drizzle pattern - no wrapper needed!
|
|
42
|
+
* const drizzleDb = drizzle(db.client);
|
|
43
|
+
* const result = await drizzleDb.select().from(users);
|
|
44
|
+
*
|
|
45
|
+
* // Context is automatically applied before each query
|
|
46
|
+
* expect(result).toBeDefined();
|
|
47
|
+
* });
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
import { getConnections as originalGetConnections } from 'pgsql-test';
|
|
52
|
+
import { proxyClientQuery } from './proxy-client';
|
|
53
|
+
/**
|
|
54
|
+
* Drop-in replacement for pgsql-test's getConnections that patches db.client.query
|
|
55
|
+
* to automatically apply context before each query. This allows ORMs like Drizzle
|
|
56
|
+
* to work with the standard pattern: drizzle(db.client) while still respecting
|
|
57
|
+
* context management (setContext, auth).
|
|
58
|
+
*
|
|
59
|
+
* @param cn - Connection options
|
|
60
|
+
* @param seedAdapters - Optional seed adapters for test data
|
|
61
|
+
* @returns Connection objects with patched db.client
|
|
62
|
+
*/
|
|
63
|
+
export async function getConnections(cn, seedAdapters) {
|
|
64
|
+
// Get the original connections from pgsql-test
|
|
65
|
+
const connections = await originalGetConnections(cn, seedAdapters);
|
|
66
|
+
// Patch db.client.query to automatically call ctxQuery()
|
|
67
|
+
proxyClientQuery(connections.db);
|
|
68
|
+
// Note: We only patch db.client, not pg.client
|
|
69
|
+
// pg is for superuser/seeding operations and should bypass RLS
|
|
70
|
+
return connections;
|
|
71
|
+
}
|
|
72
|
+
export { PgTestClient } from 'pgsql-test';
|
|
73
|
+
// Re-export the proxy function for advanced use cases
|
|
74
|
+
export { proxyClientQuery } from './proxy-client';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patches the db.client.query method to automatically apply context before each query.
|
|
3
|
+
* This allows ORMs like Drizzle to work with the standard pattern: drizzle(db.client)
|
|
4
|
+
* while still respecting PgTestClient's context management (setContext, auth).
|
|
5
|
+
*
|
|
6
|
+
* Note: This implementation intentionally does NOT call db.ctxQuery() to avoid infinite
|
|
7
|
+
* recursion (since ctxQuery() itself calls client.query()). Instead, we directly execute
|
|
8
|
+
* the ctxStmts using the original query method, which achieves the same result.
|
|
9
|
+
*
|
|
10
|
+
* @param db - The PgTestClient instance to patch
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const { db, pg, teardown } = await getConnections();
|
|
15
|
+
* proxyClientQuery(db);
|
|
16
|
+
*
|
|
17
|
+
* // Now you can use standard Drizzle pattern
|
|
18
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
19
|
+
* const drizzleDb = drizzle(db.client);
|
|
20
|
+
* await drizzleDb.select().from(users); // Context is automatically applied
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function proxyClientQuery(db) {
|
|
24
|
+
const clientAny = db.client;
|
|
25
|
+
// Capture the original query method with proper binding
|
|
26
|
+
const originalQuery = clientAny.query.bind(clientAny);
|
|
27
|
+
// Replace with a version that applies context before each query
|
|
28
|
+
// Note: This is NOT async to preserve synchronous stream return for pg-copy-streams
|
|
29
|
+
clientAny.query = (...args) => {
|
|
30
|
+
const [first] = args;
|
|
31
|
+
// Detect pg-copy-streams usage (COPY FROM/TO operations)
|
|
32
|
+
// These need to return a stream synchronously, not a Promise
|
|
33
|
+
const isCopyStream = first &&
|
|
34
|
+
typeof first === 'object' &&
|
|
35
|
+
typeof first.submit === 'function' &&
|
|
36
|
+
// Check for stream internals (pg-copy-streams objects have these)
|
|
37
|
+
(first._readableState || first._writableState);
|
|
38
|
+
if (isCopyStream) {
|
|
39
|
+
// For COPY operations, return the stream synchronously without touching context
|
|
40
|
+
// This is necessary for pg-copy-streams to work with pipeline()
|
|
41
|
+
return originalQuery(...args);
|
|
42
|
+
}
|
|
43
|
+
// Detect transaction control commands (BEGIN, COMMIT, ROLLBACK, SAVEPOINT)
|
|
44
|
+
// These should not have context injected to avoid "current transaction is aborted" errors
|
|
45
|
+
if (typeof first === 'string') {
|
|
46
|
+
const sql = first.trim().toUpperCase();
|
|
47
|
+
const isTxCommand = sql === 'BEGIN' ||
|
|
48
|
+
sql === 'BEGIN;' ||
|
|
49
|
+
sql === 'COMMIT' ||
|
|
50
|
+
sql === 'COMMIT;' ||
|
|
51
|
+
sql === 'ROLLBACK' ||
|
|
52
|
+
sql === 'ROLLBACK;' ||
|
|
53
|
+
sql.startsWith('ROLLBACK TO SAVEPOINT') ||
|
|
54
|
+
sql.startsWith('SAVEPOINT') ||
|
|
55
|
+
sql.startsWith('RELEASE SAVEPOINT');
|
|
56
|
+
if (isTxCommand) {
|
|
57
|
+
// For transaction control, execute without context injection
|
|
58
|
+
return originalQuery(...args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// For normal queries (Drizzle ORM), apply context before executing
|
|
62
|
+
const ctxStmts = db.ctxStmts;
|
|
63
|
+
if (!ctxStmts) {
|
|
64
|
+
// No context to apply, just delegate
|
|
65
|
+
return originalQuery(...args);
|
|
66
|
+
}
|
|
67
|
+
// Execute SET LOCAL statements first, then the actual query
|
|
68
|
+
// Return a Promise for Drizzle's async query pattern
|
|
69
|
+
const runWithContext = async () => {
|
|
70
|
+
await originalQuery(ctxStmts);
|
|
71
|
+
return originalQuery(...args);
|
|
72
|
+
};
|
|
73
|
+
return runWithContext();
|
|
74
|
+
};
|
|
75
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* drizzle-orm-test
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly
|
|
5
|
+
* with context management and RLS testing.
|
|
6
|
+
*
|
|
7
|
+
* This package patches db.client.query() to automatically apply context (SET LOCAL
|
|
8
|
+
* statements for role and JWT claims) before each query, allowing you to use the
|
|
9
|
+
* standard Drizzle pattern: drizzle(db.client) while maintaining full RLS support.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { drizzle } from 'drizzle-orm/node-postgres';
|
|
14
|
+
* import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
15
|
+
*
|
|
16
|
+
* let db: PgTestClient;
|
|
17
|
+
* let pg: PgTestClient;
|
|
18
|
+
* let teardown: () => Promise<void>;
|
|
19
|
+
*
|
|
20
|
+
* beforeAll(async () => {
|
|
21
|
+
* ({ pg, db, teardown } = await getConnections());
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* afterAll(async () => {
|
|
25
|
+
* await teardown();
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* beforeEach(async () => {
|
|
29
|
+
* await db.beforeEach();
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* afterEach(async () => {
|
|
33
|
+
* await db.afterEach();
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* describe('your tests', () => {
|
|
37
|
+
* it('should work with standard Drizzle pattern', async () => {
|
|
38
|
+
* // Set context on db (standard pgsql-test API)
|
|
39
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
40
|
+
*
|
|
41
|
+
* // Use standard Drizzle pattern - no wrapper needed!
|
|
42
|
+
* const drizzleDb = drizzle(db.client);
|
|
43
|
+
* const result = await drizzleDb.select().from(users);
|
|
44
|
+
*
|
|
45
|
+
* // Context is automatically applied before each query
|
|
46
|
+
* expect(result).toBeDefined();
|
|
47
|
+
* });
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
import { GetConnectionOpts, SeedAdapter } from 'pgsql-test';
|
|
52
|
+
/**
|
|
53
|
+
* Drop-in replacement for pgsql-test's getConnections that patches db.client.query
|
|
54
|
+
* to automatically apply context before each query. This allows ORMs like Drizzle
|
|
55
|
+
* to work with the standard pattern: drizzle(db.client) while still respecting
|
|
56
|
+
* context management (setContext, auth).
|
|
57
|
+
*
|
|
58
|
+
* @param cn - Connection options
|
|
59
|
+
* @param seedAdapters - Optional seed adapters for test data
|
|
60
|
+
* @returns Connection objects with patched db.client
|
|
61
|
+
*/
|
|
62
|
+
export declare function getConnections(cn?: GetConnectionOpts, seedAdapters?: SeedAdapter[]): Promise<import("pgsql-test").GetConnectionResult>;
|
|
63
|
+
export type { GetConnectionOpts, SeedAdapter } from 'pgsql-test';
|
|
64
|
+
export { PgTestClient } from 'pgsql-test';
|
|
65
|
+
export { proxyClientQuery } from './proxy-client';
|
package/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* drizzle-orm-test
|
|
4
|
+
*
|
|
5
|
+
* Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly
|
|
6
|
+
* with context management and RLS testing.
|
|
7
|
+
*
|
|
8
|
+
* This package patches db.client.query() to automatically apply context (SET LOCAL
|
|
9
|
+
* statements for role and JWT claims) before each query, allowing you to use the
|
|
10
|
+
* standard Drizzle pattern: drizzle(db.client) while maintaining full RLS support.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { drizzle } from 'drizzle-orm/node-postgres';
|
|
15
|
+
* import { getConnections, PgTestClient } from 'drizzle-orm-test';
|
|
16
|
+
*
|
|
17
|
+
* let db: PgTestClient;
|
|
18
|
+
* let pg: PgTestClient;
|
|
19
|
+
* let teardown: () => Promise<void>;
|
|
20
|
+
*
|
|
21
|
+
* beforeAll(async () => {
|
|
22
|
+
* ({ pg, db, teardown } = await getConnections());
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* afterAll(async () => {
|
|
26
|
+
* await teardown();
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* beforeEach(async () => {
|
|
30
|
+
* await db.beforeEach();
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* afterEach(async () => {
|
|
34
|
+
* await db.afterEach();
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* describe('your tests', () => {
|
|
38
|
+
* it('should work with standard Drizzle pattern', async () => {
|
|
39
|
+
* // Set context on db (standard pgsql-test API)
|
|
40
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
41
|
+
*
|
|
42
|
+
* // Use standard Drizzle pattern - no wrapper needed!
|
|
43
|
+
* const drizzleDb = drizzle(db.client);
|
|
44
|
+
* const result = await drizzleDb.select().from(users);
|
|
45
|
+
*
|
|
46
|
+
* // Context is automatically applied before each query
|
|
47
|
+
* expect(result).toBeDefined();
|
|
48
|
+
* });
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.proxyClientQuery = exports.PgTestClient = void 0;
|
|
54
|
+
exports.getConnections = getConnections;
|
|
55
|
+
const pgsql_test_1 = require("pgsql-test");
|
|
56
|
+
const proxy_client_1 = require("./proxy-client");
|
|
57
|
+
/**
|
|
58
|
+
* Drop-in replacement for pgsql-test's getConnections that patches db.client.query
|
|
59
|
+
* to automatically apply context before each query. This allows ORMs like Drizzle
|
|
60
|
+
* to work with the standard pattern: drizzle(db.client) while still respecting
|
|
61
|
+
* context management (setContext, auth).
|
|
62
|
+
*
|
|
63
|
+
* @param cn - Connection options
|
|
64
|
+
* @param seedAdapters - Optional seed adapters for test data
|
|
65
|
+
* @returns Connection objects with patched db.client
|
|
66
|
+
*/
|
|
67
|
+
async function getConnections(cn, seedAdapters) {
|
|
68
|
+
// Get the original connections from pgsql-test
|
|
69
|
+
const connections = await (0, pgsql_test_1.getConnections)(cn, seedAdapters);
|
|
70
|
+
// Patch db.client.query to automatically call ctxQuery()
|
|
71
|
+
(0, proxy_client_1.proxyClientQuery)(connections.db);
|
|
72
|
+
// Note: We only patch db.client, not pg.client
|
|
73
|
+
// pg is for superuser/seeding operations and should bypass RLS
|
|
74
|
+
return connections;
|
|
75
|
+
}
|
|
76
|
+
var pgsql_test_2 = require("pgsql-test");
|
|
77
|
+
Object.defineProperty(exports, "PgTestClient", { enumerable: true, get: function () { return pgsql_test_2.PgTestClient; } });
|
|
78
|
+
// Re-export the proxy function for advanced use cases
|
|
79
|
+
var proxy_client_2 = require("./proxy-client");
|
|
80
|
+
Object.defineProperty(exports, "proxyClientQuery", { enumerable: true, get: function () { return proxy_client_2.proxyClientQuery; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "drizzle-orm-test",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
|
+
"description": "Drop-in replacement for pgsql-test that enables Drizzle ORM to work seamlessly with context management and RLS testing",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/launchql/launchql",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"directory": "dist"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/launchql/launchql"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/launchql/launchql/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"postgres",
|
|
24
|
+
"postgresql",
|
|
25
|
+
"drizzle",
|
|
26
|
+
"drizzle-orm",
|
|
27
|
+
"testing",
|
|
28
|
+
"integration-tests",
|
|
29
|
+
"database-testing",
|
|
30
|
+
"pg",
|
|
31
|
+
"rls",
|
|
32
|
+
"role-based-access",
|
|
33
|
+
"test-database",
|
|
34
|
+
"pgsql-test",
|
|
35
|
+
"jest",
|
|
36
|
+
"mocha"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"copy": "copyfiles -f ../../LICENSE README.md package.json dist",
|
|
40
|
+
"clean": "rimraf dist/**",
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"build": "npm run clean; tsc -p tsconfig.json; tsc -p tsconfig.esm.json; npm run copy",
|
|
43
|
+
"build:dev": "npm run clean; tsc -p tsconfig.json --declarationMap; tsc -p tsconfig.esm.json; npm run copy",
|
|
44
|
+
"lint": "eslint . --fix",
|
|
45
|
+
"test": "jest",
|
|
46
|
+
"test:watch": "jest --watch"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/pg": "^8.15.2",
|
|
50
|
+
"drizzle-orm": "^0.44.7"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"pgsql-test": "^2.14.4"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"drizzle-orm": ">=0.30.0",
|
|
57
|
+
"pg": "^8.16.0"
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "3d7064e53131f085372143d1bb0749aa22988f59"
|
|
60
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PgTestClient } from 'pgsql-test';
|
|
2
|
+
/**
|
|
3
|
+
* Patches the db.client.query method to automatically apply context before each query.
|
|
4
|
+
* This allows ORMs like Drizzle to work with the standard pattern: drizzle(db.client)
|
|
5
|
+
* while still respecting PgTestClient's context management (setContext, auth).
|
|
6
|
+
*
|
|
7
|
+
* Note: This implementation intentionally does NOT call db.ctxQuery() to avoid infinite
|
|
8
|
+
* recursion (since ctxQuery() itself calls client.query()). Instead, we directly execute
|
|
9
|
+
* the ctxStmts using the original query method, which achieves the same result.
|
|
10
|
+
*
|
|
11
|
+
* @param db - The PgTestClient instance to patch
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const { db, pg, teardown } = await getConnections();
|
|
16
|
+
* proxyClientQuery(db);
|
|
17
|
+
*
|
|
18
|
+
* // Now you can use standard Drizzle pattern
|
|
19
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
20
|
+
* const drizzleDb = drizzle(db.client);
|
|
21
|
+
* await drizzleDb.select().from(users); // Context is automatically applied
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function proxyClientQuery(db: PgTestClient): void;
|
package/proxy-client.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proxyClientQuery = proxyClientQuery;
|
|
4
|
+
/**
|
|
5
|
+
* Patches the db.client.query method to automatically apply context before each query.
|
|
6
|
+
* This allows ORMs like Drizzle to work with the standard pattern: drizzle(db.client)
|
|
7
|
+
* while still respecting PgTestClient's context management (setContext, auth).
|
|
8
|
+
*
|
|
9
|
+
* Note: This implementation intentionally does NOT call db.ctxQuery() to avoid infinite
|
|
10
|
+
* recursion (since ctxQuery() itself calls client.query()). Instead, we directly execute
|
|
11
|
+
* the ctxStmts using the original query method, which achieves the same result.
|
|
12
|
+
*
|
|
13
|
+
* @param db - The PgTestClient instance to patch
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const { db, pg, teardown } = await getConnections();
|
|
18
|
+
* proxyClientQuery(db);
|
|
19
|
+
*
|
|
20
|
+
* // Now you can use standard Drizzle pattern
|
|
21
|
+
* db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '1' });
|
|
22
|
+
* const drizzleDb = drizzle(db.client);
|
|
23
|
+
* await drizzleDb.select().from(users); // Context is automatically applied
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function proxyClientQuery(db) {
|
|
27
|
+
const clientAny = db.client;
|
|
28
|
+
// Capture the original query method with proper binding
|
|
29
|
+
const originalQuery = clientAny.query.bind(clientAny);
|
|
30
|
+
// Replace with a version that applies context before each query
|
|
31
|
+
// Note: This is NOT async to preserve synchronous stream return for pg-copy-streams
|
|
32
|
+
clientAny.query = (...args) => {
|
|
33
|
+
const [first] = args;
|
|
34
|
+
// Detect pg-copy-streams usage (COPY FROM/TO operations)
|
|
35
|
+
// These need to return a stream synchronously, not a Promise
|
|
36
|
+
const isCopyStream = first &&
|
|
37
|
+
typeof first === 'object' &&
|
|
38
|
+
typeof first.submit === 'function' &&
|
|
39
|
+
// Check for stream internals (pg-copy-streams objects have these)
|
|
40
|
+
(first._readableState || first._writableState);
|
|
41
|
+
if (isCopyStream) {
|
|
42
|
+
// For COPY operations, return the stream synchronously without touching context
|
|
43
|
+
// This is necessary for pg-copy-streams to work with pipeline()
|
|
44
|
+
return originalQuery(...args);
|
|
45
|
+
}
|
|
46
|
+
// Detect transaction control commands (BEGIN, COMMIT, ROLLBACK, SAVEPOINT)
|
|
47
|
+
// These should not have context injected to avoid "current transaction is aborted" errors
|
|
48
|
+
if (typeof first === 'string') {
|
|
49
|
+
const sql = first.trim().toUpperCase();
|
|
50
|
+
const isTxCommand = sql === 'BEGIN' ||
|
|
51
|
+
sql === 'BEGIN;' ||
|
|
52
|
+
sql === 'COMMIT' ||
|
|
53
|
+
sql === 'COMMIT;' ||
|
|
54
|
+
sql === 'ROLLBACK' ||
|
|
55
|
+
sql === 'ROLLBACK;' ||
|
|
56
|
+
sql.startsWith('ROLLBACK TO SAVEPOINT') ||
|
|
57
|
+
sql.startsWith('SAVEPOINT') ||
|
|
58
|
+
sql.startsWith('RELEASE SAVEPOINT');
|
|
59
|
+
if (isTxCommand) {
|
|
60
|
+
// For transaction control, execute without context injection
|
|
61
|
+
return originalQuery(...args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// For normal queries (Drizzle ORM), apply context before executing
|
|
65
|
+
const ctxStmts = db.ctxStmts;
|
|
66
|
+
if (!ctxStmts) {
|
|
67
|
+
// No context to apply, just delegate
|
|
68
|
+
return originalQuery(...args);
|
|
69
|
+
}
|
|
70
|
+
// Execute SET LOCAL statements first, then the actual query
|
|
71
|
+
// Return a Promise for Drizzle's async query pattern
|
|
72
|
+
const runWithContext = async () => {
|
|
73
|
+
await originalQuery(ctxStmts);
|
|
74
|
+
return originalQuery(...args);
|
|
75
|
+
};
|
|
76
|
+
return runWithContext();
|
|
77
|
+
};
|
|
78
|
+
}
|