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 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;
@@ -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
+ }