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 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