safegres 0.0.1 → 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 +1 -1
- package/README.md +66 -512
- package/ast/helpers.d.ts +16 -0
- package/ast/helpers.js +40 -0
- package/ast/parse.d.ts +31 -0
- package/ast/parse.js +87 -0
- package/ast/walk.d.ts +27 -0
- package/ast/walk.js +79 -0
- package/checks/anti-patterns.d.ts +47 -0
- package/checks/anti-patterns.js +224 -0
- package/checks/coverage.d.ts +18 -0
- package/checks/coverage.js +108 -0
- package/checks/rls-flags.d.ts +25 -0
- package/checks/rls-flags.js +82 -0
- package/cli/audit.d.ts +3 -0
- package/cli/audit.js +110 -0
- package/cli/commands.d.ts +2 -0
- package/cli/commands.js +44 -0
- package/cli.d.ts +2 -0
- package/cli.js +35 -0
- package/commands/audit.d.ts +19 -0
- package/commands/audit.js +115 -0
- package/esm/ast/helpers.d.ts +16 -0
- package/esm/ast/helpers.js +36 -0
- package/esm/ast/parse.d.ts +31 -0
- package/esm/ast/parse.js +82 -0
- package/esm/ast/walk.d.ts +27 -0
- package/esm/ast/walk.js +72 -0
- package/esm/checks/anti-patterns.d.ts +47 -0
- package/esm/checks/anti-patterns.js +217 -0
- package/esm/checks/coverage.d.ts +18 -0
- package/esm/checks/coverage.js +104 -0
- package/esm/checks/rls-flags.d.ts +25 -0
- package/esm/checks/rls-flags.js +77 -0
- package/esm/cli/audit.d.ts +3 -0
- package/esm/cli/audit.js +108 -0
- package/esm/cli/commands.d.ts +2 -0
- package/esm/cli/commands.js +37 -0
- package/esm/cli.d.ts +2 -0
- package/esm/cli.js +33 -0
- package/esm/commands/audit.d.ts +19 -0
- package/esm/commands/audit.js +112 -0
- package/esm/index.d.ts +15 -0
- package/esm/index.js +14 -6
- package/esm/pg/introspect.d.ts +81 -0
- package/esm/pg/introspect.js +190 -0
- package/esm/pg/proc.d.ts +21 -0
- package/esm/pg/proc.js +53 -0
- package/esm/pg/roles.d.ts +28 -0
- package/esm/pg/roles.js +50 -0
- package/esm/report/json.d.ts +4 -0
- package/esm/report/json.js +3 -0
- package/esm/report/pretty.d.ts +4 -0
- package/esm/report/pretty.js +46 -0
- package/esm/types.d.ts +44 -0
- package/esm/types.js +26 -0
- package/esm/version.d.ts +1 -0
- package/esm/version.js +2 -0
- package/index.d.ts +15 -6
- package/index.js +23 -6
- package/package.json +39 -49
- package/pg/introspect.d.ts +81 -0
- package/pg/introspect.js +195 -0
- package/pg/proc.d.ts +21 -0
- package/pg/proc.js +56 -0
- package/pg/roles.d.ts +28 -0
- package/pg/roles.js +54 -0
- package/report/json.d.ts +4 -0
- package/report/json.js +6 -0
- package/report/pretty.d.ts +4 -0
- package/report/pretty.js +52 -0
- package/types.d.ts +44 -0
- package/types.js +32 -0
- package/version.d.ts +1 -0
- package/version.js +5 -0
- package/admin.d.ts +0 -26
- package/admin.js +0 -182
- package/connect.d.ts +0 -19
- package/connect.js +0 -83
- package/dist/README.md +0 -557
- package/dist/package.json +0 -72
- package/esm/admin.js +0 -178
- package/esm/connect.js +0 -78
- package/esm/manager.js +0 -136
- package/esm/roles.js +0 -32
- package/esm/seed/adapters.js +0 -23
- package/esm/seed/csv.js +0 -44
- package/esm/seed/index.js +0 -16
- package/esm/seed/json.js +0 -18
- package/esm/seed/launchql.js +0 -19
- package/esm/seed/sqitch.js +0 -17
- package/esm/seed/types.js +0 -1
- package/esm/stream.js +0 -43
- package/esm/test-client.js +0 -150
- package/manager.d.ts +0 -25
- package/manager.js +0 -140
- package/roles.d.ts +0 -17
- package/roles.js +0 -38
- package/seed/adapters.d.ts +0 -4
- package/seed/adapters.js +0 -28
- package/seed/csv.d.ts +0 -9
- package/seed/csv.js +0 -49
- package/seed/index.d.ts +0 -16
- package/seed/index.js +0 -33
- package/seed/json.d.ts +0 -6
- package/seed/json.js +0 -21
- package/seed/launchql.d.ts +0 -2
- package/seed/launchql.js +0 -22
- package/seed/sqitch.d.ts +0 -2
- package/seed/sqitch.js +0 -20
- package/seed/types.d.ts +0 -13
- package/seed/types.js +0 -2
- package/stream.d.ts +0 -2
- package/stream.js +0 -46
- package/test-client.d.ts +0 -49
- package/test-client.js +0 -154
package/LICENSE
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
-
Copyright (c) 2025
|
|
4
|
+
Copyright (c) 2025 Constructive <developers@constructive.io>
|
|
5
5
|
Copyright (c) 2020-present, Interweb, Inc.
|
|
6
6
|
|
|
7
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package/README.md
CHANGED
|
@@ -1,557 +1,111 @@
|
|
|
1
|
-
|
|
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/pgsql-test">
|
|
15
|
-
<img height="20" src="https://img.shields.io/github/package-json/v/launchql/launchql?filename=packages%2Fpgsql-test%2Fpackage.json"/>
|
|
16
|
-
</a>
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Safegres/brand/refs/heads/main/safegres.svg" alt="safegres" width="120" />
|
|
17
3
|
</p>
|
|
18
4
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Install
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
npm install pgsql-test
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Features
|
|
28
|
-
|
|
29
|
-
* ⚡ **Instant test DBs** — each one seeded, isolated, and UUID-named
|
|
30
|
-
* 🔄 **Per-test rollback** — every test runs in its own transaction or savepoint
|
|
31
|
-
* 🛡️ **RLS-friendly** — test with role-based auth via `.setContext()`
|
|
32
|
-
* 🌱 **Flexible seeding** — run `.sql` files, programmatic seeds, or even load fixtures
|
|
33
|
-
* 🧪 **Compatible with any async runner** — works with `Jest`, `Mocha`, etc.
|
|
34
|
-
* 🧹 **Auto teardown** — no residue, no reboots, just clean exits
|
|
35
|
-
|
|
36
|
-
### LaunchQL migrations
|
|
37
|
-
|
|
38
|
-
Part of the [LaunchQL](https://github.com/launchql) ecosystem, `pgsql-test` is built to pair seamlessly with our TypeScript-based [Sqitch](https://sqitch.org/) engine rewrite:
|
|
39
|
-
|
|
40
|
-
* 🚀 **Lightning-fast migrations** — powered by LaunchQL’s native deployer (10x faster than legacy Sqitch)
|
|
41
|
-
* 🔧 **Composable test scaffolds** — integrate with full LaunchQL stacks or use standalone
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Table of Contents
|
|
45
|
-
|
|
46
|
-
1. [Install](#install)
|
|
47
|
-
2. [Features](#features)
|
|
48
|
-
3. [Quick Start](#-quick-start)
|
|
49
|
-
4. [`getConnections()` Overview](#getconnections-overview)
|
|
50
|
-
5. [PgTestClient API Overview](#pgtestclient-api-overview)
|
|
51
|
-
6. [Usage Examples](#usage-examples)
|
|
52
|
-
* [Basic Setup](#-basic-setup)
|
|
53
|
-
* [Role-Based Context](#-role-based-context)
|
|
54
|
-
* [Seeding System](#-seeding-system)
|
|
55
|
-
* [SQL File Seeding](#-sql-file-seeding)
|
|
56
|
-
* [Programmatic Seeding](#-programmatic-seeding)
|
|
57
|
-
* [CSV Seeding](#️-csv-seeding)
|
|
58
|
-
* [JSON Seeding](#️-json-seeding)
|
|
59
|
-
* [Sqitch Seeding](#️-sqitch-seeding)
|
|
60
|
-
* [LaunchQL Seeding](#-launchql-seeding)
|
|
61
|
-
7. [`getConnections() Options` ](#getconnections-options)
|
|
62
|
-
8. [Disclaimer](#disclaimer)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## ✨ Quick Start
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { getConnections } from 'pgsql-test';
|
|
69
|
-
|
|
70
|
-
let db, teardown;
|
|
71
|
-
|
|
72
|
-
beforeAll(async () => {
|
|
73
|
-
({ db, teardown } = await getConnections());
|
|
74
|
-
await db.query(`SELECT 1`); // ✅ Ready to run queries
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
afterAll(() => teardown());
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## `getConnections()` Overview
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
import { getConnections } from 'pgsql-test';
|
|
84
|
-
|
|
85
|
-
// Complete object destructuring
|
|
86
|
-
const { pg, db, admin, teardown, manager } = await getConnections();
|
|
87
|
-
|
|
88
|
-
// Most common pattern
|
|
89
|
-
const { db, teardown } = await getConnections();
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
The `getConnections()` helper sets up a fresh PostgreSQL test database and returns a structured object with:
|
|
93
|
-
|
|
94
|
-
* `pg`: a `PgTestClient` connected as the root or superuser — useful for administrative setup or introspection
|
|
95
|
-
* `db`: a `PgTestClient` connected as the app-level user — used for running tests with RLS and granted permissions
|
|
96
|
-
* `admin`: a `DbAdmin` utility for managing database state, extensions, roles, and templates
|
|
97
|
-
* `teardown()`: a function that shuts down the test environment and database pool
|
|
98
|
-
* `manager`: a shared connection pool manager (`PgTestConnector`) behind both clients
|
|
99
|
-
|
|
100
|
-
Together, these allow fast, isolated, role-aware test environments with per-test rollback and full control over setup and teardown.
|
|
101
|
-
|
|
102
|
-
The `PgTestClient` returned by `getConnections()` is a fully-featured wrapper around `pg.Pool`. It provides:
|
|
103
|
-
|
|
104
|
-
* Automatic transaction and savepoint management for test isolation
|
|
105
|
-
* Easy switching of role-based contexts for RLS testing
|
|
106
|
-
* A clean, high-level API for integration testing PostgreSQL systems
|
|
107
|
-
|
|
108
|
-
## `PgTestClient` API Overview
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
let pg: PgTestClient;
|
|
112
|
-
let teardown: () => Promise<void>;
|
|
113
|
-
|
|
114
|
-
beforeAll(async () => {
|
|
115
|
-
({ pg, teardown } = await getConnections());
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
beforeEach(() => pg.beforeEach());
|
|
119
|
-
afterEach(() => pg.afterEach());
|
|
120
|
-
afterAll(() => teardown());
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The `PgTestClient` returned by `getConnections()` wraps a `pg.Client` and provides convenient helpers for query execution, test isolation, and context switching.
|
|
5
|
+
# safegres
|
|
124
6
|
|
|
125
|
-
|
|
7
|
+
Pure-Postgres Row-Level Security auditor. No app framework required. Drop it on any PostgreSQL database and get a structured report on grants, RLS enforcement, policy coverage, and risky SQL policy patterns.
|
|
126
8
|
|
|
127
|
-
|
|
128
|
-
* `beforeEach()` – Begins a transaction and sets a savepoint (called at the start of each test)
|
|
129
|
-
* `afterEach()` – Rolls back to the savepoint and commits the outer transaction (cleans up test state)
|
|
130
|
-
* `setContext({ key: value })` – Sets PostgreSQL config variables (like `role`) to simulate RLS contexts
|
|
131
|
-
* `any`, `one`, `oneOrNone`, `many`, `manyOrNone`, `none`, `result` – Typed query helpers for specific result expectations
|
|
9
|
+
safegres audits Row-Level Security from inside Postgres. It checks whether tables with grants are protected by RLS, whether policies actually cover the granted operations, and whether policy bodies contain risky patterns like permissive `true` checks, volatile functions, or role/session-based bypass logic.
|
|
132
10
|
|
|
133
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g safegres
|
|
134
13
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* Easy switching of role-based contexts for RLS testing
|
|
139
|
-
* A clean, high-level API for integration testing PostgreSQL systems
|
|
140
|
-
|
|
141
|
-
## Usage Examples
|
|
142
|
-
|
|
143
|
-
### ⚡ Basic Setup
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
import { getConnections } from 'pgsql-test';
|
|
147
|
-
|
|
148
|
-
let db; // A fully wrapped PgTestClient using pg.Pool with savepoint-based rollback per test
|
|
149
|
-
let teardown;
|
|
150
|
-
|
|
151
|
-
beforeAll(async () => {
|
|
152
|
-
({ db, teardown } = await getConnections());
|
|
153
|
-
|
|
154
|
-
await db.query(`
|
|
155
|
-
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
|
|
156
|
-
CREATE TABLE posts (id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id), content TEXT);
|
|
157
|
-
|
|
158
|
-
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
|
|
159
|
-
INSERT INTO posts (user_id, content) VALUES (1, 'Hello world!'), (2, 'Graphile is cool!');
|
|
160
|
-
`);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
afterAll(() => teardown());
|
|
164
|
-
|
|
165
|
-
beforeEach(() => db.beforeEach());
|
|
166
|
-
afterEach(() => db.afterEach());
|
|
167
|
-
|
|
168
|
-
test('user count starts at 2', async () => {
|
|
169
|
-
const res = await db.query('SELECT COUNT(*) FROM users');
|
|
170
|
-
expect(res.rows[0].count).toBe('2');
|
|
171
|
-
});
|
|
14
|
+
# Standard libpq env vars (PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE)
|
|
15
|
+
export PGHOST=localhost PGUSER=postgres PGPASSWORD=password PGDATABASE=mydb
|
|
16
|
+
safegres audit
|
|
172
17
|
```
|
|
173
18
|
|
|
174
|
-
|
|
19
|
+
Per-field overrides (`--host`, `--port`, `--user`, `--password`, `--database`) and a full `--connection <url>` flag are also supported. See `safegres audit --help`.
|
|
175
20
|
|
|
21
|
+
## What it checks
|
|
176
22
|
|
|
177
|
-
|
|
23
|
+
| Code | Severity | Category | Check |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| A1 | critical | flags | RLS enabled but **0 policies** (effectively deny-all) |
|
|
26
|
+
| A2 | high | flags | Grants exist on a table with **RLS disabled** |
|
|
27
|
+
| A3 | medium | flags | RLS enabled but **`FORCE ROW LEVEL SECURITY` not set** (table owner bypass) |
|
|
28
|
+
| A4 | high | coverage | INSERT / UPDATE / DELETE grant with **no covering policy** for that verb |
|
|
29
|
+
| A5 | medium | coverage | SELECT grant with **no policy** (silent empty result) |
|
|
30
|
+
| A6 | info | coverage | UPDATE has `USING` but **no `WITH CHECK`** (row-smuggling surface) |
|
|
31
|
+
| A7 | high | anti-pattern | Trivially-permissive policy (`USING (true)` / `WITH CHECK (true)`) |
|
|
32
|
+
| P1 | high | anti-pattern | Policy body calls a **VOLATILE function** (per-row evaluation) |
|
|
33
|
+
| P5 | high | anti-pattern | Policy body references **`session_user`** / `current_user` / `pg_has_role(...)` |
|
|
178
34
|
|
|
179
|
-
|
|
35
|
+
Coverage is aggregated `(table, role) → { hasUsing, hasWithCheck }` across every applicable permissive policy (FOR ALL + PUBLIC-role policies considered). Roles with `BYPASSRLS` are suppressed.
|
|
180
36
|
|
|
181
|
-
|
|
37
|
+
## Library use
|
|
182
38
|
|
|
183
39
|
```ts
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'jwt.claims.org_id': 'acme'
|
|
188
|
-
});
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
This applies the settings using `SET LOCAL` statements, ensuring they persist only for the current transaction and maintain proper isolation between tests.
|
|
40
|
+
import { Client } from 'pg';
|
|
41
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
42
|
+
import { audit, renderPretty } from 'safegres';
|
|
192
43
|
|
|
193
|
-
|
|
44
|
+
const client = new Client(getPgEnvOptions());
|
|
45
|
+
await client.connect();
|
|
194
46
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
beforeEach(async () => {
|
|
198
|
-
db.setContext({ role: 'authenticated' });
|
|
199
|
-
await db.beforeEach();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
afterEach(() => db.afterEach());
|
|
203
|
-
|
|
204
|
-
it('runs as authenticated', async () => {
|
|
205
|
-
const res = await db.query(`SELECT current_setting('role', true) AS role`);
|
|
206
|
-
expect(res.rows[0].role).toBe('authenticated');
|
|
207
|
-
});
|
|
47
|
+
const report = await audit(client, {
|
|
48
|
+
excludeSchemas: ['my_private_schema']
|
|
208
49
|
});
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
#### Database Connection Options
|
|
212
|
-
|
|
213
|
-
For non-superuser testing, use the connection options described in the [options](#getconnections-options) section. The `db.connection` property allows you to customize the non-privileged user account for your tests.
|
|
214
|
-
|
|
215
|
-
Use `setContext()` to simulate Role-Based Access Control (RBAC) during tests. This is useful when testing Row-Level Security (RLS) policies. Your actual server should manage role/user claims via secure tokens (e.g., setting `current_setting('jwt.claims.user_id')`), but this interface helps emulate those behaviors in test environments.
|
|
216
|
-
|
|
217
|
-
#### Common Testing Scenarios
|
|
218
50
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- Per-user data filtering
|
|
222
|
-
- Admin privilege bypass behavior
|
|
223
|
-
- Custom claim-based restrictions (organization membership, admin status)
|
|
224
|
-
|
|
225
|
-
> **Note:** While this interface helps simulate RBAC for testing, your production server should manage user/role claims via secure authentication tokens, typically by setting values like `current_setting('jwt.claims.user_id')` through proper authentication middleware.
|
|
226
|
-
|
|
227
|
-
### 🌱 Seeding System
|
|
228
|
-
|
|
229
|
-
The second argument to `getConnections()` is an optional array of `SeedAdapter` objects:
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
const { db, teardown } = await getConnections(getConnectionOptions, seedAdapters);
|
|
51
|
+
console.log(renderPretty(report));
|
|
52
|
+
console.log(`${report.findings.length} findings`);
|
|
233
53
|
```
|
|
234
54
|
|
|
235
|
-
|
|
55
|
+
---
|
|
236
56
|
|
|
237
|
-
|
|
238
|
-
* [`seed.fn()`](#-programmatic-seeding) – Run JavaScript/TypeScript logic to programmatically insert data
|
|
239
|
-
* [`seed.csv()`](#️-csv-seeding) – Load tabular data from CSV files
|
|
240
|
-
* [`seed.json()`](#️-json-seeding) – Use in-memory objects as seed data
|
|
241
|
-
* [`seed.sqitch()`](#️-sqitch-seeding) – Deploy a Sqitch-compatible migration project
|
|
242
|
-
* [`seed.launchql()`](#-launchql-seeding) – Apply a LaunchQL module using `deployFast()` (compatible with sqitch)
|
|
57
|
+
## Education and Tutorials
|
|
243
58
|
|
|
244
|
-
|
|
59
|
+
1. 🚀 [Quickstart: Getting Up and Running](https://constructive.io/learn/quickstart)
|
|
60
|
+
Get started with modular databases in minutes. Install prerequisites and deploy your first module.
|
|
245
61
|
|
|
246
|
-
|
|
62
|
+
2. 📦 [Modular PostgreSQL Development with Database Packages](https://constructive.io/learn/modular-postgres)
|
|
63
|
+
Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
|
|
247
64
|
|
|
248
|
-
|
|
65
|
+
3. ✏️ [Authoring Database Changes](https://constructive.io/learn/authoring-database-changes)
|
|
66
|
+
Master the workflow for adding, organizing, and managing database changes with pgpm.
|
|
249
67
|
|
|
250
|
-
|
|
68
|
+
4. 🧪 [End-to-End PostgreSQL Testing with TypeScript](https://constructive.io/learn/e2e-postgres-testing)
|
|
69
|
+
Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
|
|
251
70
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
71
|
+
5. ⚡ [Supabase Testing](https://constructive.io/learn/supabase)
|
|
72
|
+
Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
|
|
255
73
|
|
|
256
|
-
|
|
74
|
+
6. 💧 [Drizzle ORM Testing](https://constructive.io/learn/drizzle-testing)
|
|
75
|
+
Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
|
|
257
76
|
|
|
258
|
-
|
|
259
|
-
|
|
77
|
+
7. 🔧 [Troubleshooting](https://constructive.io/learn/troubleshooting)
|
|
78
|
+
Common issues and solutions for pgpm, PostgreSQL, and testing.
|
|
260
79
|
|
|
261
|
-
|
|
262
|
-
({ db, teardown } = await getConnections({}, [
|
|
263
|
-
seed.sqlfile([
|
|
264
|
-
sql('schema.sql'),
|
|
265
|
-
sql('fixtures.sql')
|
|
266
|
-
])
|
|
267
|
-
]));
|
|
268
|
-
});
|
|
80
|
+
## Related Constructive Tooling
|
|
269
81
|
|
|
270
|
-
|
|
271
|
-
await teardown();
|
|
272
|
-
});
|
|
273
|
-
```
|
|
82
|
+
### 📦 Package Management
|
|
274
83
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Use JavaScript functions to insert seed data:
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
281
|
-
|
|
282
|
-
let db;
|
|
283
|
-
let teardown;
|
|
284
|
-
|
|
285
|
-
beforeAll(async () => {
|
|
286
|
-
({ db, teardown } = await getConnections({}, [
|
|
287
|
-
seed.fn(async ({ pg }) => {
|
|
288
|
-
await pg.query(`
|
|
289
|
-
INSERT INTO users (name) VALUES ('Seeded User');
|
|
290
|
-
`);
|
|
291
|
-
})
|
|
292
|
-
]));
|
|
293
|
-
});
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## 🗃️ CSV Seeding
|
|
297
|
-
|
|
298
|
-
You can load tables from CSV files using `seed.csv({ ... })`. CSV headers must match the table column names exactly. This is useful for loading stable fixture data for integration tests or CI environments.
|
|
299
|
-
|
|
300
|
-
```ts
|
|
301
|
-
import path from 'path';
|
|
302
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
303
|
-
|
|
304
|
-
const csv = (file: string) => path.resolve(__dirname, '../csv', file);
|
|
305
|
-
|
|
306
|
-
let db;
|
|
307
|
-
let teardown;
|
|
308
|
-
|
|
309
|
-
beforeAll(async () => {
|
|
310
|
-
({ db, teardown } = await getConnections({}, [
|
|
311
|
-
// Create schema
|
|
312
|
-
seed.fn(async ({ pg }) => {
|
|
313
|
-
await pg.query(`
|
|
314
|
-
CREATE TABLE users (
|
|
315
|
-
id SERIAL PRIMARY KEY,
|
|
316
|
-
name TEXT NOT NULL
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
CREATE TABLE posts (
|
|
320
|
-
id SERIAL PRIMARY KEY,
|
|
321
|
-
user_id INT REFERENCES users(id),
|
|
322
|
-
content TEXT NOT NULL
|
|
323
|
-
);
|
|
324
|
-
`);
|
|
325
|
-
}),
|
|
326
|
-
// Load from CSV
|
|
327
|
-
seed.csv({
|
|
328
|
-
users: csv('users.csv'),
|
|
329
|
-
posts: csv('posts.csv')
|
|
330
|
-
}),
|
|
331
|
-
// Adjust SERIAL sequences to avoid conflicts
|
|
332
|
-
seed.fn(async ({ pg }) => {
|
|
333
|
-
await pg.query(`SELECT setval(pg_get_serial_sequence('users', 'id'), (SELECT MAX(id) FROM users));`);
|
|
334
|
-
await pg.query(`SELECT setval(pg_get_serial_sequence('posts', 'id'), (SELECT MAX(id) FROM posts));`);
|
|
335
|
-
})
|
|
336
|
-
]));
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
afterAll(() => teardown());
|
|
340
|
-
|
|
341
|
-
it('has loaded rows', async () => {
|
|
342
|
-
const res = await db.query('SELECT COUNT(*) FROM users');
|
|
343
|
-
expect(+res.rows[0].count).toBeGreaterThan(0);
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
## 🗃️ JSON Seeding
|
|
348
|
-
|
|
349
|
-
You can seed tables using in-memory JSON objects. This is useful when you want fast, inline fixtures without managing external files.
|
|
350
|
-
|
|
351
|
-
```ts
|
|
352
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
353
|
-
|
|
354
|
-
let db;
|
|
355
|
-
let teardown;
|
|
356
|
-
|
|
357
|
-
beforeAll(async () => {
|
|
358
|
-
({ db, teardown } = await getConnections({}, [
|
|
359
|
-
// Create schema
|
|
360
|
-
seed.fn(async ({ pg }) => {
|
|
361
|
-
await pg.query(`
|
|
362
|
-
CREATE SCHEMA custom;
|
|
363
|
-
CREATE TABLE custom.users (
|
|
364
|
-
id SERIAL PRIMARY KEY,
|
|
365
|
-
name TEXT NOT NULL
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
CREATE TABLE custom.posts (
|
|
369
|
-
id SERIAL PRIMARY KEY,
|
|
370
|
-
user_id INT REFERENCES custom.users(id),
|
|
371
|
-
content TEXT NOT NULL
|
|
372
|
-
);
|
|
373
|
-
`);
|
|
374
|
-
}),
|
|
375
|
-
// Seed with in-memory JSON
|
|
376
|
-
seed.json({
|
|
377
|
-
'custom.users': [
|
|
378
|
-
{ id: 1, name: 'Alice' },
|
|
379
|
-
{ id: 2, name: 'Bob' }
|
|
380
|
-
],
|
|
381
|
-
'custom.posts': [
|
|
382
|
-
{ id: 1, user_id: 1, content: 'Hello world!' },
|
|
383
|
-
{ id: 2, user_id: 2, content: 'Graphile is cool!' }
|
|
384
|
-
]
|
|
385
|
-
}),
|
|
386
|
-
// Fix SERIAL sequences
|
|
387
|
-
seed.fn(async ({ pg }) => {
|
|
388
|
-
await pg.query(`SELECT setval(pg_get_serial_sequence('custom.users', 'id'), (SELECT MAX(id) FROM custom.users));`);
|
|
389
|
-
await pg.query(`SELECT setval(pg_get_serial_sequence('custom.posts', 'id'), (SELECT MAX(id) FROM custom.posts));`);
|
|
390
|
-
})
|
|
391
|
-
]));
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
afterAll(() => teardown());
|
|
395
|
-
|
|
396
|
-
it('has loaded rows', async () => {
|
|
397
|
-
const res = await db.query('SELECT COUNT(*) FROM custom.users');
|
|
398
|
-
expect(+res.rows[0].count).toBeGreaterThan(0);
|
|
399
|
-
});
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
## 🏗️ Sqitch Seeding
|
|
403
|
-
|
|
404
|
-
*Note: While compatible with Sqitch syntax, LaunchQL uses its own high-performance [TypeScript-based deploy engine.](#-launchql-seeding) that we encourage using for sqitch projects*
|
|
405
|
-
|
|
406
|
-
You can seed your test database using a Sqitch project but with significantly improved performance by leveraging LaunchQL's TypeScript deployment engine:
|
|
407
|
-
|
|
408
|
-
```ts
|
|
409
|
-
import path from 'path';
|
|
410
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
411
|
-
|
|
412
|
-
const cwd = path.resolve(__dirname, '../path/to/sqitch');
|
|
413
|
-
|
|
414
|
-
beforeAll(async () => {
|
|
415
|
-
({ db, teardown } = await getConnections({}, [
|
|
416
|
-
seed.sqitch(cwd)
|
|
417
|
-
]));
|
|
418
|
-
});
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
This works for any Sqitch-compatible module, now accelerated by LaunchQL's deployment tooling.
|
|
422
|
-
|
|
423
|
-
## 🚀 LaunchQL Seeding
|
|
424
|
-
|
|
425
|
-
If your project uses LaunchQL modules with a precompiled `launchql.plan`, you can use `pgsql-test` with **zero configuration**. Just call `getConnections()` — and it *just works*:
|
|
426
|
-
|
|
427
|
-
```ts
|
|
428
|
-
import { getConnections } from 'pgsql-test';
|
|
429
|
-
|
|
430
|
-
let db, teardown;
|
|
431
|
-
|
|
432
|
-
beforeAll(async () => {
|
|
433
|
-
({ db, teardown } = await getConnections()); // 🚀 LaunchQL deployFast() is used automatically - up to 10x faster than traditional Sqitch!
|
|
434
|
-
});
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
This works out of the box because `pgsql-test` uses the high-speed `deployFast()` function by default, applying any compiled LaunchQL schema located in the current working directory (`process.cwd()`).
|
|
438
|
-
|
|
439
|
-
If you want to specify a custom path to your LaunchQL module, use `seed.launchql()` explicitly:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
```ts
|
|
443
|
-
import path from 'path';
|
|
444
|
-
import { getConnections, seed } from 'pgsql-test';
|
|
445
|
-
|
|
446
|
-
const cwd = path.resolve(__dirname, '../path/to/launchql');
|
|
447
|
-
|
|
448
|
-
beforeAll(async () => {
|
|
449
|
-
({ db, teardown } = await getConnections({}, [
|
|
450
|
-
seed.launchql(cwd) // uses deployFast() - up to 10x faster than traditional Sqitch!
|
|
451
|
-
]));
|
|
452
|
-
});
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
## Why LaunchQL's Approach?
|
|
456
|
-
|
|
457
|
-
LaunchQL provides the best of both worlds:
|
|
458
|
-
|
|
459
|
-
1. **Sqitch Compatibility**: Keep your familiar Sqitch syntax and migration approach
|
|
460
|
-
2. **TypeScript Performance**: Our TS-rewritten deployment engine delivers up to 10x faster schema deployments
|
|
461
|
-
3. **Developer Experience**: Tight feedback loops with near-instant schema setup for tests
|
|
462
|
-
4. **CI Optimization**: Dramatically reduced test suite run times with optimized deployment
|
|
463
|
-
|
|
464
|
-
By maintaining Sqitch compatibility while supercharging performance, LaunchQL enables you to keep your existing migration patterns while enjoying the speed benefits of our TypeScript engine.
|
|
465
|
-
|
|
466
|
-
## `getConnections` Options
|
|
467
|
-
|
|
468
|
-
This table documents the available options for the `getConnections` function. The options are passed as a combination of `pg` and `db` configuration objects.
|
|
469
|
-
|
|
470
|
-
### `db` Options (PgTestConnectionOptions)
|
|
471
|
-
|
|
472
|
-
| Option | Type | Default | Description |
|
|
473
|
-
| ------------------------ | ---------- | ---------------- | --------------------------------------------------------------------------- |
|
|
474
|
-
| `db.extensions` | `string[]` | `[]` | Array of PostgreSQL extensions to include in the test database |
|
|
475
|
-
| `db.cwd` | `string` | `process.cwd()` | Working directory used for LaunchQL/Sqitch projects |
|
|
476
|
-
| `db.connection.user` | `string` | `'app_user'` | User for simulating RLS via `setContext()` |
|
|
477
|
-
| `db.connection.password` | `string` | `'app_password'` | Password for RLS test user |
|
|
478
|
-
| `db.connection.role` | `string` | `'anonymous'` | Default role used during `setContext()` |
|
|
479
|
-
| `db.template` | `string` | `undefined` | Template database used for faster test DB creation |
|
|
480
|
-
| `db.rootDb` | `string` | `'postgres'` | Root database used for administrative operations (e.g., creating databases) |
|
|
481
|
-
| `db.prefix` | `string` | `'db-'` | Prefix used when generating test database names |
|
|
482
|
-
|
|
483
|
-
### `pg` Options (PgConfig)
|
|
484
|
-
|
|
485
|
-
Environment variables will override these options when available:
|
|
486
|
-
|
|
487
|
-
* `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`
|
|
488
|
-
|
|
489
|
-
| Option | Type | Default | Description |
|
|
490
|
-
| ------------- | -------- | ------------- | ----------------------------------------------- |
|
|
491
|
-
| `pg.user` | `string` | `'postgres'` | Superuser for PostgreSQL |
|
|
492
|
-
| `pg.password` | `string` | `'password'` | Password for the PostgreSQL superuser |
|
|
493
|
-
| `pg.host` | `string` | `'localhost'` | Hostname for PostgreSQL |
|
|
494
|
-
| `pg.port` | `number` | `5423` | Port for PostgreSQL |
|
|
495
|
-
| `pg.database` | `string` | `'postgres'` | Default database used when connecting initially |
|
|
496
|
-
|
|
497
|
-
### Usage
|
|
498
|
-
|
|
499
|
-
```ts
|
|
500
|
-
const { conn, db, teardown } = await getConnections({
|
|
501
|
-
pg: { user: 'postgres', password: 'secret' },
|
|
502
|
-
db: {
|
|
503
|
-
extensions: ['uuid-ossp'],
|
|
504
|
-
cwd: '/path/to/project',
|
|
505
|
-
connection: { user: 'test_user', password: 'secret', role: 'authenticated' },
|
|
506
|
-
template: 'test_template',
|
|
507
|
-
prefix: 'test_',
|
|
508
|
-
rootDb: 'postgres'
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
## Related LaunchQL Tooling
|
|
84
|
+
* [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
514
85
|
|
|
515
86
|
### 🧪 Testing
|
|
516
87
|
|
|
517
|
-
* [
|
|
518
|
-
* [
|
|
519
|
-
* [
|
|
88
|
+
* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
|
|
89
|
+
* [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
|
|
90
|
+
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
91
|
+
* [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
92
|
+
* [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/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.
|
|
520
93
|
|
|
521
94
|
### 🧠 Parsing & AST
|
|
522
95
|
|
|
523
|
-
* [
|
|
524
|
-
* [
|
|
525
|
-
* [
|
|
526
|
-
* [@pgsql/enums](https://
|
|
527
|
-
* [@pgsql/types](https://
|
|
528
|
-
* [@pgsql/utils](https://
|
|
529
|
-
* [launchql/pg-ast](https://github.com/launchql/launchql/tree/main/packages/pg-ast): **🔍 Low-level AST tools** and transformations for Postgres query structures.
|
|
530
|
-
|
|
531
|
-
### 🚀 API & Dev Tools
|
|
96
|
+
* [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
97
|
+
* [libpg-query-node](https://www.npmjs.com/package/libpg-query): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
98
|
+
* [pg-proto-parser](https://www.npmjs.com/package/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
99
|
+
* [@pgsql/enums](https://www.npmjs.com/package/@pgsql/enums): **🏷️ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
|
|
100
|
+
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
101
|
+
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
532
102
|
|
|
533
|
-
|
|
534
|
-
* [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.
|
|
103
|
+
## Credits
|
|
535
104
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
* [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.
|
|
539
|
-
* [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.
|
|
540
|
-
* [launchql/etag-stream](https://github.com/launchql/launchql/tree/main/packages/etag-stream): **🔄 ETag computation** via Node stream transformer during upload or transfer.
|
|
541
|
-
* [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.
|
|
542
|
-
* [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.
|
|
543
|
-
* [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.
|
|
544
|
-
|
|
545
|
-
### 🧰 CLI & Codegen
|
|
546
|
-
|
|
547
|
-
* [@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.
|
|
548
|
-
* [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.
|
|
549
|
-
* [@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.
|
|
550
|
-
* [@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.
|
|
105
|
+
**🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
|
|
551
106
|
|
|
552
107
|
## Disclaimer
|
|
553
108
|
|
|
554
109
|
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
555
110
|
|
|
556
111
|
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.
|
|
557
|
-
|
package/ast/helpers.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small typed helpers for inspecting `pgsql-parser` AST nodes.
|
|
3
|
+
*
|
|
4
|
+
* Operates on the raw shape returned by `parse()` rather than the typed
|
|
5
|
+
* `@pgsql/types` view, so the runtime cost is just object lookups.
|
|
6
|
+
*/
|
|
7
|
+
import type { PgAstNode } from './parse';
|
|
8
|
+
type Obj = Record<string, unknown>;
|
|
9
|
+
/** Extract the single wrapping key (e.g. `A_Expr`, `BoolExpr`) and its payload. */
|
|
10
|
+
export declare function unwrap(node: PgAstNode): {
|
|
11
|
+
kind: string;
|
|
12
|
+
body: Obj;
|
|
13
|
+
} | null;
|
|
14
|
+
/** Returns the boolean literal value, or null if not an `A_Const(bool)`. */
|
|
15
|
+
export declare function boolConst(node: PgAstNode): boolean | null;
|
|
16
|
+
export {};
|