postgres-memory-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/cli.js +390 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +504 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aman Agarwal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# postgres-memory-server
|
|
2
|
+
|
|
3
|
+
Spin up a disposable **real** PostgreSQL or ParadeDB instance in tests with a tiny API inspired by `mongodb-memory-server` and `redisjson-memory-server`.
|
|
4
|
+
|
|
5
|
+
This package does **not** emulate Postgres. It starts an actual database container and gives you a normal Postgres connection string.
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
For Postgres extension-heavy workloads, especially ParadeDB + `pgvector`, a real database is usually what you want in tests:
|
|
10
|
+
|
|
11
|
+
- no shared local database contamination
|
|
12
|
+
- deterministic integration tests
|
|
13
|
+
- realistic extension behavior
|
|
14
|
+
- straightforward CI/CD setup
|
|
15
|
+
- one API for plain Postgres and ParadeDB
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- Start a disposable Postgres container with `create()`
|
|
20
|
+
- Get a ready-to-use connection string with `getUri()`
|
|
21
|
+
- Use the same API for plain Postgres or ParadeDB presets
|
|
22
|
+
- Run SQL strings, SQL files, or a migrations directory
|
|
23
|
+
- Snapshot and restore database state between tests
|
|
24
|
+
- CLI for local scripts and debugging
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js 20+
|
|
29
|
+
- Docker available to the current user
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -D postgres-memory-server pg
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { PostgresMemoryServer } from "postgres-memory-server";
|
|
41
|
+
import { Client } from "pg";
|
|
42
|
+
|
|
43
|
+
const db = await PostgresMemoryServer.create();
|
|
44
|
+
|
|
45
|
+
const client = new Client({ connectionString: db.getUri() });
|
|
46
|
+
await client.connect();
|
|
47
|
+
await client.query("select 1");
|
|
48
|
+
await client.end();
|
|
49
|
+
|
|
50
|
+
await db.stop();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## ParadeDB quick start
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { PostgresMemoryServer } from "postgres-memory-server";
|
|
57
|
+
|
|
58
|
+
const db = await PostgresMemoryServer.createParadeDb();
|
|
59
|
+
|
|
60
|
+
await db.runSql(`
|
|
61
|
+
CREATE TABLE documents (
|
|
62
|
+
id bigserial primary key,
|
|
63
|
+
title text not null,
|
|
64
|
+
content text not null,
|
|
65
|
+
embedding vector(3)
|
|
66
|
+
);
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
await db.stop();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
By default, the ParadeDB preset uses the official ParadeDB image and runs:
|
|
73
|
+
|
|
74
|
+
```sql
|
|
75
|
+
CREATE EXTENSION IF NOT EXISTS pg_search;
|
|
76
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The default image is pinned to `paradedb/paradedb:0.22.3-pg17` so local runs and CI stay reproducible.
|
|
80
|
+
|
|
81
|
+
If you want to test against a different Postgres or ParadeDB version, pass `version`. The package resolves the correct image repository for the selected preset.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const postgres16 = await PostgresMemoryServer.createPostgres({
|
|
85
|
+
version: "16",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const paradeDbPg16 = await PostgresMemoryServer.createParadeDb({
|
|
89
|
+
version: "0.22.3-pg16",
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use `image` when you want an exact override, such as a private registry, a custom build, or a nonstandard tag. When both `version` and `image` are provided, `image` wins.
|
|
94
|
+
|
|
95
|
+
## API
|
|
96
|
+
|
|
97
|
+
### `PostgresMemoryServer.create(options?)`
|
|
98
|
+
|
|
99
|
+
Create a disposable Postgres instance.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const db = await PostgresMemoryServer.create({
|
|
103
|
+
version: "17",
|
|
104
|
+
database: "testdb",
|
|
105
|
+
username: "testuser",
|
|
106
|
+
password: "testpassword",
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `PostgresMemoryServer.createPostgres(options?)`
|
|
111
|
+
|
|
112
|
+
Convenience alias for the plain Postgres preset.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const db = await PostgresMemoryServer.createPostgres({
|
|
116
|
+
version: "15",
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `PostgresMemoryServer.createParadeDb(options?)`
|
|
121
|
+
|
|
122
|
+
Starts a ParadeDB container and creates the default extensions.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const db = await PostgresMemoryServer.createParadeDb({
|
|
126
|
+
version: "0.22.3-pg17",
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The preset still controls the default extensions. Overriding `version` or `image` only changes which container tag is started.
|
|
131
|
+
|
|
132
|
+
### `createJestGlobalSetup(options?)`
|
|
133
|
+
|
|
134
|
+
Starts one disposable database process for a Jest run and injects its URI into `DATABASE_URL` by default.
|
|
135
|
+
|
|
136
|
+
### `createJestGlobalTeardown(options?)`
|
|
137
|
+
|
|
138
|
+
Stops the process started by `createJestGlobalSetup()`.
|
|
139
|
+
|
|
140
|
+
### Instance methods
|
|
141
|
+
|
|
142
|
+
- `getUri()`
|
|
143
|
+
- `getHost()`
|
|
144
|
+
- `getPort()`
|
|
145
|
+
- `getDatabase()`
|
|
146
|
+
- `getUsername()`
|
|
147
|
+
- `getPassword()`
|
|
148
|
+
- `getConnectionOptions()`
|
|
149
|
+
- `query(text, params?)`
|
|
150
|
+
- `runSql(sql)`
|
|
151
|
+
- `runSqlFile(filePath)`
|
|
152
|
+
- `runMigrationsDir(dirPath)`
|
|
153
|
+
- `snapshot()`
|
|
154
|
+
- `restore()`
|
|
155
|
+
- `stop()`
|
|
156
|
+
|
|
157
|
+
## Snapshots
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const db = await PostgresMemoryServer.create();
|
|
161
|
+
|
|
162
|
+
await db.runSql([
|
|
163
|
+
"create table users (id serial primary key, email text not null)",
|
|
164
|
+
"insert into users (email) values ('first@example.com')",
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
await db.snapshot();
|
|
168
|
+
|
|
169
|
+
await db.runSql("insert into users (email) values ('second@example.com')");
|
|
170
|
+
|
|
171
|
+
await db.restore();
|
|
172
|
+
|
|
173
|
+
const result = await db.query<{ count: string }>(
|
|
174
|
+
"select count(*)::text as count from users",
|
|
175
|
+
);
|
|
176
|
+
console.log(result.rows[0]?.count); // 1
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use a non-system database name when you plan to use snapshots. The package defaults to `testdb` for that reason.
|
|
180
|
+
|
|
181
|
+
## Running SQL files or migrations
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
await db.runSqlFile("./sql/001_init.sql");
|
|
185
|
+
await db.runMigrationsDir("./sql/migrations");
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Migration files are run in lexicographic order.
|
|
189
|
+
|
|
190
|
+
## CLI
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npx postgres-memory-server --preset paradedb
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
To test a specific version from the CLI, pass `--version`:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
npx postgres-memory-server --preset postgres --version 16
|
|
200
|
+
npx postgres-memory-server --preset paradedb --version 0.22.3-pg16
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
If you need an exact image reference instead, `--image` still works and takes precedence over `--version`.
|
|
204
|
+
|
|
205
|
+
Example output:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
POSTGRES_MEMORY_SERVER_URI=postgres://testuser:testpassword@127.0.0.1:54329/testdb
|
|
209
|
+
POSTGRES_MEMORY_SERVER_HOST=127.0.0.1
|
|
210
|
+
POSTGRES_MEMORY_SERVER_PORT=54329
|
|
211
|
+
POSTGRES_MEMORY_SERVER_DATABASE=testdb
|
|
212
|
+
POSTGRES_MEMORY_SERVER_USERNAME=testuser
|
|
213
|
+
POSTGRES_MEMORY_SERVER_PASSWORD=testpassword
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The CLI keeps the container alive until you exit with `Ctrl+C`.
|
|
217
|
+
|
|
218
|
+
### CLI flags
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
--preset postgres|paradedb
|
|
222
|
+
--version <tag>
|
|
223
|
+
--image <image>
|
|
224
|
+
--database <name>
|
|
225
|
+
--username <name>
|
|
226
|
+
--password <password>
|
|
227
|
+
--extension <name> # repeatable
|
|
228
|
+
--init-file <path> # repeatable
|
|
229
|
+
--json
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Test scripts
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run test:postgres
|
|
236
|
+
npm run test:paradedb
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
These scripts are useful locally and are also what the GitHub Actions workflow uses.
|
|
240
|
+
|
|
241
|
+
## Jest global setup
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
// jest.global-setup.ts
|
|
245
|
+
import { createJestGlobalSetup } from "postgres-memory-server";
|
|
246
|
+
|
|
247
|
+
export default createJestGlobalSetup({
|
|
248
|
+
preset: "paradedb",
|
|
249
|
+
version: "0.22.3-pg16",
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
// jest.global-teardown.ts
|
|
255
|
+
import { createJestGlobalTeardown } from "postgres-memory-server";
|
|
256
|
+
|
|
257
|
+
export default createJestGlobalTeardown();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
// jest.config.ts
|
|
262
|
+
import type { Config } from "jest";
|
|
263
|
+
|
|
264
|
+
const config: Config = {
|
|
265
|
+
globalSetup: "./jest.global-setup.ts",
|
|
266
|
+
globalTeardown: "./jest.global-teardown.ts",
|
|
267
|
+
testEnvironment: "node",
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default config;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
After setup runs, your tests can connect through `process.env.DATABASE_URL`.
|
|
274
|
+
|
|
275
|
+
## Vitest example
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
279
|
+
import { PostgresMemoryServer } from "postgres-memory-server";
|
|
280
|
+
|
|
281
|
+
describe("db", () => {
|
|
282
|
+
let db: PostgresMemoryServer;
|
|
283
|
+
|
|
284
|
+
beforeAll(async () => {
|
|
285
|
+
db = await PostgresMemoryServer.create();
|
|
286
|
+
await db.runSql(`
|
|
287
|
+
create table notes (
|
|
288
|
+
id serial primary key,
|
|
289
|
+
body text not null
|
|
290
|
+
);
|
|
291
|
+
`);
|
|
292
|
+
await db.snapshot();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
beforeEach(async () => {
|
|
296
|
+
await db.restore();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
afterAll(async () => {
|
|
300
|
+
await db.stop();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("starts from a clean snapshot", async () => {
|
|
304
|
+
await db.runSql("insert into notes (body) values ('hello')");
|
|
305
|
+
const result = await db.query<{ count: string }>(
|
|
306
|
+
"select count(*)::text as count from notes",
|
|
307
|
+
);
|
|
308
|
+
expect(result.rows[0]?.count).toBe("1");
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Caveats
|
|
314
|
+
|
|
315
|
+
- This package depends on Docker. It is intentionally built around a real database, not an emulator.
|
|
316
|
+
- Snapshot and restore require no active client connections during those operations.
|
|
317
|
+
- The ParadeDB preset creates extensions in the target database, but you are still responsible for your schema, indexes, and test data.
|
|
318
|
+
- The package is ESM-only in this starter repo. If you need CJS, add a second build target.
|
|
319
|
+
|
|
320
|
+
## Publishing checklist
|
|
321
|
+
|
|
322
|
+
Before publishing:
|
|
323
|
+
|
|
324
|
+
1. update the package name in `package.json` if you plan to publish under a scope
|
|
325
|
+
2. update repository URLs in `package.json`
|
|
326
|
+
3. run `npm install`
|
|
327
|
+
4. run `npm test`
|
|
328
|
+
5. publish with `npm publish --access public`
|
|
329
|
+
|
|
330
|
+
## Roadmap
|
|
331
|
+
|
|
332
|
+
- reusable container mode
|
|
333
|
+
- worker-isolated databases
|
|
334
|
+
- Docker Compose / Podman engine adapters
|
|
335
|
+
- optional non-Docker backend
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT
|