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/dist/index.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
// src/PostgresMemoryServer.ts
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
|
5
|
+
import { Client } from "pg";
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var PostgresMemoryServerError = class extends Error {
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = new.target.name;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var SnapshotUnsupportedError = class extends PostgresMemoryServerError {
|
|
15
|
+
constructor(database) {
|
|
16
|
+
super(
|
|
17
|
+
`Snapshots are not supported when the database name is "${database}". Use a non-system database name such as "testdb" before calling snapshot() or restore().`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var ServerStoppedError = class extends PostgresMemoryServerError {
|
|
22
|
+
constructor() {
|
|
23
|
+
super("The PostgresMemoryServer has already been stopped.");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/presets.ts
|
|
28
|
+
var POSTGRES_IMAGE_REPOSITORY = "postgres";
|
|
29
|
+
var PARADEDB_IMAGE_REPOSITORY = "paradedb/paradedb";
|
|
30
|
+
var DEFAULT_POSTGRES_VERSION = "17";
|
|
31
|
+
var DEFAULT_PARADEDB_VERSION = "0.22.3-pg17";
|
|
32
|
+
var DEFAULT_POSTGRES_IMAGE = getImageForVersion(
|
|
33
|
+
"postgres",
|
|
34
|
+
DEFAULT_POSTGRES_VERSION
|
|
35
|
+
);
|
|
36
|
+
var DEFAULT_PARADEDB_IMAGE = getImageForVersion(
|
|
37
|
+
"paradedb",
|
|
38
|
+
DEFAULT_PARADEDB_VERSION
|
|
39
|
+
);
|
|
40
|
+
var DEFAULT_DATABASE = "testdb";
|
|
41
|
+
var DEFAULT_USERNAME = "testuser";
|
|
42
|
+
var DEFAULT_PASSWORD = "testpassword";
|
|
43
|
+
var PARADEDB_DEFAULT_EXTENSIONS = ["pg_search", "vector"];
|
|
44
|
+
function normalizeOptions(options = {}) {
|
|
45
|
+
const preset = options.preset ?? "postgres";
|
|
46
|
+
const version = options.version;
|
|
47
|
+
const image = options.image ?? getImage(preset, version);
|
|
48
|
+
const database = options.database ?? DEFAULT_DATABASE;
|
|
49
|
+
const username = options.username ?? DEFAULT_USERNAME;
|
|
50
|
+
const password = options.password ?? DEFAULT_PASSWORD;
|
|
51
|
+
const extensions = options.extensions ?? getDefaultExtensions(preset);
|
|
52
|
+
const initSql = options.initSql ?? [];
|
|
53
|
+
return {
|
|
54
|
+
preset,
|
|
55
|
+
version,
|
|
56
|
+
image,
|
|
57
|
+
database,
|
|
58
|
+
username,
|
|
59
|
+
password,
|
|
60
|
+
extensions,
|
|
61
|
+
initSql
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function getImageForVersion(preset, version) {
|
|
65
|
+
const repository = preset === "paradedb" ? PARADEDB_IMAGE_REPOSITORY : POSTGRES_IMAGE_REPOSITORY;
|
|
66
|
+
return `${repository}:${version}`;
|
|
67
|
+
}
|
|
68
|
+
function getDefaultImage(preset) {
|
|
69
|
+
return preset === "paradedb" ? DEFAULT_PARADEDB_IMAGE : DEFAULT_POSTGRES_IMAGE;
|
|
70
|
+
}
|
|
71
|
+
function getImage(preset, version) {
|
|
72
|
+
return version ? getImageForVersion(preset, version) : getDefaultImage(preset);
|
|
73
|
+
}
|
|
74
|
+
function getDefaultExtensions(preset) {
|
|
75
|
+
return preset === "paradedb" ? [...PARADEDB_DEFAULT_EXTENSIONS] : [];
|
|
76
|
+
}
|
|
77
|
+
function buildInitStatements(options) {
|
|
78
|
+
const extensionStatements = options.extensions.map(
|
|
79
|
+
(extension) => `CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extension)};`
|
|
80
|
+
);
|
|
81
|
+
return [...extensionStatements, ...options.initSql];
|
|
82
|
+
}
|
|
83
|
+
function quoteIdentifier(name) {
|
|
84
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
85
|
+
return name;
|
|
86
|
+
}
|
|
87
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/PostgresMemoryServer.ts
|
|
91
|
+
var PostgresMemoryServer = class _PostgresMemoryServer {
|
|
92
|
+
constructor(container, options) {
|
|
93
|
+
this.container = container;
|
|
94
|
+
this.options = options;
|
|
95
|
+
this.snapshotSupported = options.database !== "postgres";
|
|
96
|
+
}
|
|
97
|
+
stopped = false;
|
|
98
|
+
snapshotSupported;
|
|
99
|
+
static async create(options = {}) {
|
|
100
|
+
const normalized = normalizeOptions(options);
|
|
101
|
+
const container = await new PostgreSqlContainer(normalized.image).withDatabase(normalized.database).withUsername(normalized.username).withPassword(normalized.password).start();
|
|
102
|
+
const server = new _PostgresMemoryServer(container, normalized);
|
|
103
|
+
const initStatements = buildInitStatements(normalized);
|
|
104
|
+
if (initStatements.length > 0) {
|
|
105
|
+
await server.runSql(initStatements);
|
|
106
|
+
}
|
|
107
|
+
return server;
|
|
108
|
+
}
|
|
109
|
+
static createPostgres(options = {}) {
|
|
110
|
+
return _PostgresMemoryServer.create({ ...options, preset: "postgres" });
|
|
111
|
+
}
|
|
112
|
+
static createParadeDb(options = {}) {
|
|
113
|
+
return _PostgresMemoryServer.create({ ...options, preset: "paradedb" });
|
|
114
|
+
}
|
|
115
|
+
getUri() {
|
|
116
|
+
this.ensureRunning();
|
|
117
|
+
return this.container.getConnectionUri();
|
|
118
|
+
}
|
|
119
|
+
getHost() {
|
|
120
|
+
this.ensureRunning();
|
|
121
|
+
return this.container.getHost();
|
|
122
|
+
}
|
|
123
|
+
getPort() {
|
|
124
|
+
this.ensureRunning();
|
|
125
|
+
return this.container.getPort();
|
|
126
|
+
}
|
|
127
|
+
getDatabase() {
|
|
128
|
+
return this.options.database;
|
|
129
|
+
}
|
|
130
|
+
getUsername() {
|
|
131
|
+
return this.options.username;
|
|
132
|
+
}
|
|
133
|
+
getPassword() {
|
|
134
|
+
return this.options.password;
|
|
135
|
+
}
|
|
136
|
+
getImage() {
|
|
137
|
+
return this.options.image;
|
|
138
|
+
}
|
|
139
|
+
getConnectionOptions() {
|
|
140
|
+
return {
|
|
141
|
+
host: this.getHost(),
|
|
142
|
+
port: this.getPort(),
|
|
143
|
+
database: this.getDatabase(),
|
|
144
|
+
user: this.getUsername(),
|
|
145
|
+
password: this.getPassword()
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async query(text, params) {
|
|
149
|
+
return this.withClient((client) => {
|
|
150
|
+
if (typeof text === "string") {
|
|
151
|
+
if (params === void 0) {
|
|
152
|
+
return client.query(text);
|
|
153
|
+
}
|
|
154
|
+
return client.query(text, params);
|
|
155
|
+
}
|
|
156
|
+
return client.query(text);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async withClient(callback) {
|
|
160
|
+
this.ensureRunning();
|
|
161
|
+
const client = new Client({
|
|
162
|
+
connectionString: this.getUri()
|
|
163
|
+
});
|
|
164
|
+
await client.connect();
|
|
165
|
+
try {
|
|
166
|
+
return await callback(client);
|
|
167
|
+
} finally {
|
|
168
|
+
await client.end();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async runSql(sql) {
|
|
172
|
+
const statements = Array.isArray(sql) ? sql : [sql];
|
|
173
|
+
await this.withClient(async (client) => {
|
|
174
|
+
for (const statement of statements) {
|
|
175
|
+
await client.query(statement);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async runSqlFile(filePath) {
|
|
180
|
+
const sql = await fs.readFile(filePath, "utf8");
|
|
181
|
+
await this.runSql(sql);
|
|
182
|
+
}
|
|
183
|
+
async runMigrationsDir(dirPath) {
|
|
184
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
185
|
+
const files = entries.filter(
|
|
186
|
+
(entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".sql")
|
|
187
|
+
).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
188
|
+
for (const file of files) {
|
|
189
|
+
await this.runSqlFile(path.join(dirPath, file));
|
|
190
|
+
}
|
|
191
|
+
return files;
|
|
192
|
+
}
|
|
193
|
+
async snapshot() {
|
|
194
|
+
this.ensureRunning();
|
|
195
|
+
this.ensureSnapshotSupported();
|
|
196
|
+
await this.container.snapshot();
|
|
197
|
+
}
|
|
198
|
+
async restore() {
|
|
199
|
+
this.ensureRunning();
|
|
200
|
+
this.ensureSnapshotSupported();
|
|
201
|
+
await this.container.restoreSnapshot();
|
|
202
|
+
}
|
|
203
|
+
async stop() {
|
|
204
|
+
if (this.stopped) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.stopped = true;
|
|
208
|
+
await this.container.stop();
|
|
209
|
+
}
|
|
210
|
+
ensureRunning() {
|
|
211
|
+
if (this.stopped) {
|
|
212
|
+
throw new ServerStoppedError();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
ensureSnapshotSupported() {
|
|
216
|
+
if (!this.snapshotSupported) {
|
|
217
|
+
throw new SnapshotUnsupportedError(this.options.database);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/jest.ts
|
|
223
|
+
import { promises as fs2 } from "fs";
|
|
224
|
+
import { spawn } from "child_process";
|
|
225
|
+
import { createHash } from "crypto";
|
|
226
|
+
import path2 from "path";
|
|
227
|
+
import { tmpdir } from "os";
|
|
228
|
+
import process from "process";
|
|
229
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
230
|
+
var CHILD_OPTIONS_ENV_VAR = "POSTGRES_MEMORY_SERVER_CHILD_OPTIONS_B64";
|
|
231
|
+
var CHILD_SETUP_TIMEOUT_MS = 12e4;
|
|
232
|
+
var CHILD_SHUTDOWN_TIMEOUT_MS = 3e4;
|
|
233
|
+
var POLL_INTERVAL_MS = 100;
|
|
234
|
+
var DEFAULT_JEST_ENV_VAR_NAME = "DATABASE_URL";
|
|
235
|
+
var DEFAULT_JEST_STATE_FILE = path2.join(
|
|
236
|
+
tmpdir(),
|
|
237
|
+
`postgres-memory-server-jest-${createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12)}.json`
|
|
238
|
+
);
|
|
239
|
+
function getChildScript(childModuleUrl) {
|
|
240
|
+
return `
|
|
241
|
+
import process from "node:process";
|
|
242
|
+
import { PostgresMemoryServer } from ${JSON.stringify(childModuleUrl)};
|
|
243
|
+
|
|
244
|
+
const encodedOptions = process.env.${CHILD_OPTIONS_ENV_VAR};
|
|
245
|
+
if (!encodedOptions) {
|
|
246
|
+
throw new Error("Missing child setup options");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const options = JSON.parse(Buffer.from(encodedOptions, "base64").toString("utf8"));
|
|
250
|
+
const server = await PostgresMemoryServer.create(options);
|
|
251
|
+
|
|
252
|
+
const payload = {
|
|
253
|
+
uri: server.getUri(),
|
|
254
|
+
host: server.getHost(),
|
|
255
|
+
port: server.getPort(),
|
|
256
|
+
database: server.getDatabase(),
|
|
257
|
+
username: server.getUsername(),
|
|
258
|
+
password: server.getPassword(),
|
|
259
|
+
image: server.getImage(),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
process.stdout.write(JSON.stringify(payload) + "\\n");
|
|
263
|
+
|
|
264
|
+
const stop = async () => {
|
|
265
|
+
await server.stop();
|
|
266
|
+
process.exit(0);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
process.on("SIGINT", () => {
|
|
270
|
+
void stop();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
process.on("SIGTERM", () => {
|
|
274
|
+
void stop();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await new Promise(() => {});
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
function createJestGlobalSetup(options = {}) {
|
|
281
|
+
return async () => {
|
|
282
|
+
const {
|
|
283
|
+
envVarName = DEFAULT_JEST_ENV_VAR_NAME,
|
|
284
|
+
stateFilePath,
|
|
285
|
+
...serverOptions
|
|
286
|
+
} = options;
|
|
287
|
+
const resolvedStateFilePath = resolveStateFilePath(stateFilePath);
|
|
288
|
+
await fs2.mkdir(path2.dirname(resolvedStateFilePath), { recursive: true });
|
|
289
|
+
const existingState = await readStateFile(resolvedStateFilePath);
|
|
290
|
+
if (existingState) {
|
|
291
|
+
await stopChildProcess(existingState.pid);
|
|
292
|
+
}
|
|
293
|
+
const { pid, payload } = await startChildProcess(serverOptions);
|
|
294
|
+
applyConnectionEnvironment(envVarName, payload);
|
|
295
|
+
const state = {
|
|
296
|
+
pid,
|
|
297
|
+
envVarName,
|
|
298
|
+
payload
|
|
299
|
+
};
|
|
300
|
+
await fs2.writeFile(
|
|
301
|
+
resolvedStateFilePath,
|
|
302
|
+
JSON.stringify(state, null, 2),
|
|
303
|
+
"utf8"
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function createJestGlobalTeardown(options = {}) {
|
|
308
|
+
return async () => {
|
|
309
|
+
const resolvedStateFilePath = resolveStateFilePath(options.stateFilePath);
|
|
310
|
+
const state = await readStateFile(resolvedStateFilePath);
|
|
311
|
+
if (!state) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
await stopChildProcess(state.pid);
|
|
315
|
+
await fs2.rm(resolvedStateFilePath, { force: true });
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function resolveStateFilePath(stateFilePath) {
|
|
319
|
+
return stateFilePath ? path2.resolve(stateFilePath) : DEFAULT_JEST_STATE_FILE;
|
|
320
|
+
}
|
|
321
|
+
async function readStateFile(filePath) {
|
|
322
|
+
try {
|
|
323
|
+
const content = await fs2.readFile(filePath, "utf8");
|
|
324
|
+
return JSON.parse(content);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (isMissingFileError(error)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function applyConnectionEnvironment(envVarName, payload) {
|
|
333
|
+
process.env[envVarName] = payload.uri;
|
|
334
|
+
process.env.POSTGRES_MEMORY_SERVER_URI = payload.uri;
|
|
335
|
+
process.env.POSTGRES_MEMORY_SERVER_HOST = payload.host;
|
|
336
|
+
process.env.POSTGRES_MEMORY_SERVER_PORT = String(payload.port);
|
|
337
|
+
process.env.POSTGRES_MEMORY_SERVER_DATABASE = payload.database;
|
|
338
|
+
process.env.POSTGRES_MEMORY_SERVER_USERNAME = payload.username;
|
|
339
|
+
process.env.POSTGRES_MEMORY_SERVER_PASSWORD = payload.password;
|
|
340
|
+
process.env.POSTGRES_MEMORY_SERVER_IMAGE = payload.image;
|
|
341
|
+
}
|
|
342
|
+
async function startChildProcess(options) {
|
|
343
|
+
const childModuleUrl = await resolveChildModuleUrl();
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
const child = spawn(
|
|
346
|
+
process.execPath,
|
|
347
|
+
["--input-type=module", "--eval", getChildScript(childModuleUrl)],
|
|
348
|
+
{
|
|
349
|
+
env: {
|
|
350
|
+
...process.env,
|
|
351
|
+
[CHILD_OPTIONS_ENV_VAR]: Buffer.from(
|
|
352
|
+
JSON.stringify(options),
|
|
353
|
+
"utf8"
|
|
354
|
+
).toString("base64")
|
|
355
|
+
},
|
|
356
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
if (child.pid === void 0) {
|
|
360
|
+
reject(new Error("Failed to start postgres-memory-server child process"));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const childPid = child.pid;
|
|
364
|
+
let settled = false;
|
|
365
|
+
let stdout = "";
|
|
366
|
+
let stderr = "";
|
|
367
|
+
const timeout = setTimeout(() => {
|
|
368
|
+
if (settled) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
settled = true;
|
|
372
|
+
void stopChildProcess(childPid).finally(() => {
|
|
373
|
+
reject(
|
|
374
|
+
new Error(
|
|
375
|
+
`Timed out waiting for postgres-memory-server child process to become ready. ${stderr.trim()}`.trim()
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
}, CHILD_SETUP_TIMEOUT_MS);
|
|
380
|
+
child.stdout.setEncoding("utf8");
|
|
381
|
+
child.stderr.setEncoding("utf8");
|
|
382
|
+
child.stdout.on("data", (chunk) => {
|
|
383
|
+
if (settled) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
stdout += chunk;
|
|
387
|
+
const newlineIndex = stdout.indexOf("\n");
|
|
388
|
+
if (newlineIndex === -1) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const firstLine = stdout.slice(0, newlineIndex).trim();
|
|
392
|
+
if (!firstLine) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const payload = JSON.parse(firstLine);
|
|
397
|
+
settled = true;
|
|
398
|
+
clearTimeout(timeout);
|
|
399
|
+
resolve({ pid: childPid, payload });
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
child.stderr.on("data", (chunk) => {
|
|
404
|
+
stderr += chunk;
|
|
405
|
+
});
|
|
406
|
+
child.on("error", (error) => {
|
|
407
|
+
if (settled) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
settled = true;
|
|
411
|
+
clearTimeout(timeout);
|
|
412
|
+
reject(error);
|
|
413
|
+
});
|
|
414
|
+
child.on("exit", (code, signal) => {
|
|
415
|
+
if (settled) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
settled = true;
|
|
419
|
+
clearTimeout(timeout);
|
|
420
|
+
reject(
|
|
421
|
+
new Error(
|
|
422
|
+
`postgres-memory-server child process exited before reporting readiness (code: ${code ?? "null"}, signal: ${signal ?? "null"}). ${stderr.trim()}`.trim()
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
async function resolveChildModuleUrl() {
|
|
429
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
430
|
+
const currentDirectoryPath = path2.dirname(currentFilePath);
|
|
431
|
+
const distEntryPath = path2.resolve(currentDirectoryPath, "../dist/index.js");
|
|
432
|
+
try {
|
|
433
|
+
await fs2.access(distEntryPath);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (isMissingFileError(error)) {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`Missing built package entry at ${distEntryPath}. Run npm run build before using the Jest global setup helpers from the repository source checkout.`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
return pathToFileURL(distEntryPath).href;
|
|
443
|
+
}
|
|
444
|
+
async function stopChildProcess(pid) {
|
|
445
|
+
try {
|
|
446
|
+
process.kill(pid, "SIGTERM");
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (isMissingProcessError(error)) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
const deadline = Date.now() + CHILD_SHUTDOWN_TIMEOUT_MS;
|
|
454
|
+
while (Date.now() < deadline) {
|
|
455
|
+
await sleep(POLL_INTERVAL_MS);
|
|
456
|
+
try {
|
|
457
|
+
process.kill(pid, 0);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (isMissingProcessError(error)) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Timed out waiting for postgres-memory-server child process ${pid} to stop`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
async function sleep(durationMs) {
|
|
470
|
+
await new Promise((resolve) => {
|
|
471
|
+
setTimeout(resolve, durationMs);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
function isMissingFileError(error) {
|
|
475
|
+
return isNodeErrorWithCode(error, "ENOENT");
|
|
476
|
+
}
|
|
477
|
+
function isMissingProcessError(error) {
|
|
478
|
+
return isNodeErrorWithCode(error, "ESRCH");
|
|
479
|
+
}
|
|
480
|
+
function isNodeErrorWithCode(error, code) {
|
|
481
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
482
|
+
}
|
|
483
|
+
export {
|
|
484
|
+
DEFAULT_JEST_ENV_VAR_NAME,
|
|
485
|
+
DEFAULT_JEST_STATE_FILE,
|
|
486
|
+
DEFAULT_PARADEDB_IMAGE,
|
|
487
|
+
DEFAULT_PARADEDB_VERSION,
|
|
488
|
+
DEFAULT_POSTGRES_IMAGE,
|
|
489
|
+
DEFAULT_POSTGRES_VERSION,
|
|
490
|
+
PARADEDB_IMAGE_REPOSITORY,
|
|
491
|
+
POSTGRES_IMAGE_REPOSITORY,
|
|
492
|
+
PostgresMemoryServer,
|
|
493
|
+
PostgresMemoryServerError,
|
|
494
|
+
ServerStoppedError,
|
|
495
|
+
SnapshotUnsupportedError,
|
|
496
|
+
buildInitStatements,
|
|
497
|
+
createJestGlobalSetup,
|
|
498
|
+
createJestGlobalTeardown,
|
|
499
|
+
getDefaultExtensions,
|
|
500
|
+
getDefaultImage,
|
|
501
|
+
getImageForVersion,
|
|
502
|
+
normalizeOptions
|
|
503
|
+
};
|
|
504
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/PostgresMemoryServer.ts","../src/errors.ts","../src/presets.ts","../src/jest.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { PostgreSqlContainer } from \"@testcontainers/postgresql\";\nimport { Client, type QueryResultRow } from \"pg\";\n\nimport { ServerStoppedError, SnapshotUnsupportedError } from \"./errors.js\";\nimport { buildInitStatements, normalizeOptions } from \"./presets.js\";\nimport type {\n PostgresConnectionOptions,\n PostgresMemoryServerOptions,\n QueryParams,\n QueryResponse,\n QueryText,\n} from \"./types.js\";\n\nexport type StartedPostgreSqlContainer = Awaited<\n ReturnType<PostgreSqlContainer[\"start\"]>\n>;\n\nexport class PostgresMemoryServer {\n private stopped = false;\n private readonly snapshotSupported: boolean;\n\n private constructor(\n private readonly container: StartedPostgreSqlContainer,\n private readonly options: ReturnType<typeof normalizeOptions>,\n ) {\n this.snapshotSupported = options.database !== \"postgres\";\n }\n\n static async create(\n options: PostgresMemoryServerOptions = {},\n ): Promise<PostgresMemoryServer> {\n const normalized = normalizeOptions(options);\n\n const container = await new PostgreSqlContainer(normalized.image)\n .withDatabase(normalized.database)\n .withUsername(normalized.username)\n .withPassword(normalized.password)\n .start();\n\n const server = new PostgresMemoryServer(container, normalized);\n\n const initStatements = buildInitStatements(normalized);\n if (initStatements.length > 0) {\n await server.runSql(initStatements);\n }\n\n return server;\n }\n\n static createPostgres(\n options: Omit<PostgresMemoryServerOptions, \"preset\"> = {},\n ): Promise<PostgresMemoryServer> {\n return PostgresMemoryServer.create({ ...options, preset: \"postgres\" });\n }\n\n static createParadeDb(\n options: Omit<PostgresMemoryServerOptions, \"preset\"> = {},\n ): Promise<PostgresMemoryServer> {\n return PostgresMemoryServer.create({ ...options, preset: \"paradedb\" });\n }\n\n getUri(): string {\n this.ensureRunning();\n return this.container.getConnectionUri();\n }\n\n getHost(): string {\n this.ensureRunning();\n return this.container.getHost();\n }\n\n getPort(): number {\n this.ensureRunning();\n return this.container.getPort();\n }\n\n getDatabase(): string {\n return this.options.database;\n }\n\n getUsername(): string {\n return this.options.username;\n }\n\n getPassword(): string {\n return this.options.password;\n }\n\n getImage(): string {\n return this.options.image;\n }\n\n getConnectionOptions(): PostgresConnectionOptions {\n return {\n host: this.getHost(),\n port: this.getPort(),\n database: this.getDatabase(),\n user: this.getUsername(),\n password: this.getPassword(),\n };\n }\n\n async query<Row extends QueryResultRow = QueryResultRow>(\n text: QueryText<Row>,\n params?: QueryParams,\n ): Promise<QueryResponse<Row>> {\n return this.withClient((client) => {\n if (typeof text === \"string\") {\n if (params === undefined) {\n return client.query<Row>(text);\n }\n\n return client.query<Row>(text, params);\n }\n\n return client.query<Row>(text);\n });\n }\n\n async withClient<T>(callback: (client: Client) => Promise<T>): Promise<T> {\n this.ensureRunning();\n\n const client = new Client({\n connectionString: this.getUri(),\n });\n\n await client.connect();\n try {\n return await callback(client);\n } finally {\n await client.end();\n }\n }\n\n async runSql(sql: string | string[]): Promise<void> {\n const statements = Array.isArray(sql) ? sql : [sql];\n\n await this.withClient(async (client) => {\n for (const statement of statements) {\n await client.query(statement);\n }\n });\n }\n\n async runSqlFile(filePath: string): Promise<void> {\n const sql = await fs.readFile(filePath, \"utf8\");\n await this.runSql(sql);\n }\n\n async runMigrationsDir(dirPath: string): Promise<string[]> {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const files = entries\n .filter(\n (entry) => entry.isFile() && entry.name.toLowerCase().endsWith(\".sql\"),\n )\n .map((entry) => entry.name)\n .sort((left, right) => left.localeCompare(right));\n\n for (const file of files) {\n await this.runSqlFile(path.join(dirPath, file));\n }\n\n return files;\n }\n\n async snapshot(): Promise<void> {\n this.ensureRunning();\n this.ensureSnapshotSupported();\n await this.container.snapshot();\n }\n\n async restore(): Promise<void> {\n this.ensureRunning();\n this.ensureSnapshotSupported();\n await this.container.restoreSnapshot();\n }\n\n async stop(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n await this.container.stop();\n }\n\n private ensureRunning(): void {\n if (this.stopped) {\n throw new ServerStoppedError();\n }\n }\n\n private ensureSnapshotSupported(): void {\n if (!this.snapshotSupported) {\n throw new SnapshotUnsupportedError(this.options.database);\n }\n }\n}\n","export class PostgresMemoryServerError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\nexport class SnapshotUnsupportedError extends PostgresMemoryServerError {\n constructor(database: string) {\n super(\n `Snapshots are not supported when the database name is \"${database}\". ` +\n `Use a non-system database name such as \"testdb\" before calling snapshot() or restore().`,\n );\n }\n}\n\nexport class ServerStoppedError extends PostgresMemoryServerError {\n constructor() {\n super(\"The PostgresMemoryServer has already been stopped.\");\n }\n}\n","import type {\n NormalizedPostgresMemoryServerOptions,\n PostgresMemoryServerOptions,\n PostgresMemoryServerPreset,\n} from \"./types.js\";\n\nexport const POSTGRES_IMAGE_REPOSITORY = \"postgres\";\nexport const PARADEDB_IMAGE_REPOSITORY = \"paradedb/paradedb\";\nexport const DEFAULT_POSTGRES_VERSION = \"17\";\nexport const DEFAULT_PARADEDB_VERSION = \"0.22.3-pg17\";\nexport const DEFAULT_POSTGRES_IMAGE = getImageForVersion(\n \"postgres\",\n DEFAULT_POSTGRES_VERSION,\n);\nexport const DEFAULT_PARADEDB_IMAGE = getImageForVersion(\n \"paradedb\",\n DEFAULT_PARADEDB_VERSION,\n);\n\nconst DEFAULT_DATABASE = \"testdb\";\nconst DEFAULT_USERNAME = \"testuser\";\nconst DEFAULT_PASSWORD = \"testpassword\";\n\nconst PARADEDB_DEFAULT_EXTENSIONS = [\"pg_search\", \"vector\"];\n\nexport function normalizeOptions(\n options: PostgresMemoryServerOptions = {},\n): NormalizedPostgresMemoryServerOptions {\n const preset = options.preset ?? \"postgres\";\n const version = options.version;\n const image = options.image ?? getImage(preset, version);\n const database = options.database ?? DEFAULT_DATABASE;\n const username = options.username ?? DEFAULT_USERNAME;\n const password = options.password ?? DEFAULT_PASSWORD;\n const extensions = options.extensions ?? getDefaultExtensions(preset);\n const initSql = options.initSql ?? [];\n\n return {\n preset,\n version,\n image,\n database,\n username,\n password,\n extensions,\n initSql,\n };\n}\n\nexport function getImageForVersion(\n preset: PostgresMemoryServerPreset,\n version: string,\n): string {\n const repository =\n preset === \"paradedb\"\n ? PARADEDB_IMAGE_REPOSITORY\n : POSTGRES_IMAGE_REPOSITORY;\n\n return `${repository}:${version}`;\n}\n\nexport function getDefaultImage(preset: PostgresMemoryServerPreset): string {\n return preset === \"paradedb\"\n ? DEFAULT_PARADEDB_IMAGE\n : DEFAULT_POSTGRES_IMAGE;\n}\n\nfunction getImage(\n preset: PostgresMemoryServerPreset,\n version?: string,\n): string {\n return version\n ? getImageForVersion(preset, version)\n : getDefaultImage(preset);\n}\n\nexport function getDefaultExtensions(\n preset: PostgresMemoryServerPreset,\n): string[] {\n return preset === \"paradedb\" ? [...PARADEDB_DEFAULT_EXTENSIONS] : [];\n}\n\nexport function buildInitStatements(\n options: NormalizedPostgresMemoryServerOptions,\n): string[] {\n const extensionStatements = options.extensions.map(\n (extension) =>\n `CREATE EXTENSION IF NOT EXISTS ${quoteIdentifier(extension)};`,\n );\n\n return [...extensionStatements, ...options.initSql];\n}\n\nfunction quoteIdentifier(name: string): string {\n if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n return name;\n }\n\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n","import { promises as fs } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport process from \"node:process\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nimport type { PostgresMemoryServerOptions } from \"./types.js\";\n\nconst CHILD_OPTIONS_ENV_VAR = \"POSTGRES_MEMORY_SERVER_CHILD_OPTIONS_B64\";\nconst CHILD_SETUP_TIMEOUT_MS = 120_000;\nconst CHILD_SHUTDOWN_TIMEOUT_MS = 30_000;\nconst POLL_INTERVAL_MS = 100;\n\nexport const DEFAULT_JEST_ENV_VAR_NAME = \"DATABASE_URL\";\nexport const DEFAULT_JEST_STATE_FILE = path.join(\n tmpdir(),\n `postgres-memory-server-jest-${createHash(\"sha256\")\n .update(process.cwd())\n .digest(\"hex\")\n .slice(0, 12)}.json`,\n);\n\ntype ChildPayload = {\n uri: string;\n host: string;\n port: number;\n database: string;\n username: string;\n password: string;\n image: string;\n};\n\ntype JestGlobalState = {\n pid: number;\n envVarName: string;\n payload: ChildPayload;\n};\n\nexport interface JestGlobalSetupOptions extends PostgresMemoryServerOptions {\n envVarName?: string;\n stateFilePath?: string;\n}\n\nexport interface JestGlobalTeardownOptions {\n stateFilePath?: string;\n}\n\nfunction getChildScript(childModuleUrl: string): string {\n return `\nimport process from \"node:process\";\nimport { PostgresMemoryServer } from ${JSON.stringify(childModuleUrl)};\n\nconst encodedOptions = process.env.${CHILD_OPTIONS_ENV_VAR};\nif (!encodedOptions) {\n throw new Error(\"Missing child setup options\");\n}\n\nconst options = JSON.parse(Buffer.from(encodedOptions, \"base64\").toString(\"utf8\"));\nconst server = await PostgresMemoryServer.create(options);\n\nconst payload = {\n uri: server.getUri(),\n host: server.getHost(),\n port: server.getPort(),\n database: server.getDatabase(),\n username: server.getUsername(),\n password: server.getPassword(),\n image: server.getImage(),\n};\n\nprocess.stdout.write(JSON.stringify(payload) + \"\\\\n\");\n\nconst stop = async () => {\n await server.stop();\n process.exit(0);\n};\n\nprocess.on(\"SIGINT\", () => {\n void stop();\n});\n\nprocess.on(\"SIGTERM\", () => {\n void stop();\n});\n\nawait new Promise(() => {});\n`;\n}\n\nexport function createJestGlobalSetup(\n options: JestGlobalSetupOptions = {},\n): () => Promise<void> {\n return async () => {\n const {\n envVarName = DEFAULT_JEST_ENV_VAR_NAME,\n stateFilePath,\n ...serverOptions\n } = options;\n const resolvedStateFilePath = resolveStateFilePath(stateFilePath);\n\n await fs.mkdir(path.dirname(resolvedStateFilePath), { recursive: true });\n\n const existingState = await readStateFile(resolvedStateFilePath);\n if (existingState) {\n await stopChildProcess(existingState.pid);\n }\n\n const { pid, payload } = await startChildProcess(serverOptions);\n\n applyConnectionEnvironment(envVarName, payload);\n\n const state: JestGlobalState = {\n pid,\n envVarName,\n payload,\n };\n\n await fs.writeFile(\n resolvedStateFilePath,\n JSON.stringify(state, null, 2),\n \"utf8\",\n );\n };\n}\n\nexport function createJestGlobalTeardown(\n options: JestGlobalTeardownOptions = {},\n): () => Promise<void> {\n return async () => {\n const resolvedStateFilePath = resolveStateFilePath(options.stateFilePath);\n const state = await readStateFile(resolvedStateFilePath);\n\n if (!state) {\n return;\n }\n\n await stopChildProcess(state.pid);\n await fs.rm(resolvedStateFilePath, { force: true });\n };\n}\n\nfunction resolveStateFilePath(stateFilePath?: string): string {\n return stateFilePath ? path.resolve(stateFilePath) : DEFAULT_JEST_STATE_FILE;\n}\n\nasync function readStateFile(\n filePath: string,\n): Promise<JestGlobalState | null> {\n try {\n const content = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(content) as JestGlobalState;\n } catch (error) {\n if (isMissingFileError(error)) {\n return null;\n }\n\n throw error;\n }\n}\n\nfunction applyConnectionEnvironment(\n envVarName: string,\n payload: ChildPayload,\n): void {\n process.env[envVarName] = payload.uri;\n process.env.POSTGRES_MEMORY_SERVER_URI = payload.uri;\n process.env.POSTGRES_MEMORY_SERVER_HOST = payload.host;\n process.env.POSTGRES_MEMORY_SERVER_PORT = String(payload.port);\n process.env.POSTGRES_MEMORY_SERVER_DATABASE = payload.database;\n process.env.POSTGRES_MEMORY_SERVER_USERNAME = payload.username;\n process.env.POSTGRES_MEMORY_SERVER_PASSWORD = payload.password;\n process.env.POSTGRES_MEMORY_SERVER_IMAGE = payload.image;\n}\n\nasync function startChildProcess(\n options: PostgresMemoryServerOptions,\n): Promise<{ pid: number; payload: ChildPayload }> {\n const childModuleUrl = await resolveChildModuleUrl();\n\n return new Promise((resolve, reject) => {\n const child = spawn(\n process.execPath,\n [\"--input-type=module\", \"--eval\", getChildScript(childModuleUrl)],\n {\n env: {\n ...process.env,\n [CHILD_OPTIONS_ENV_VAR]: Buffer.from(\n JSON.stringify(options),\n \"utf8\",\n ).toString(\"base64\"),\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n\n if (child.pid === undefined) {\n reject(new Error(\"Failed to start postgres-memory-server child process\"));\n return;\n }\n\n const childPid = child.pid;\n\n let settled = false;\n let stdout = \"\";\n let stderr = \"\";\n\n const timeout = setTimeout(() => {\n if (settled) {\n return;\n }\n\n settled = true;\n void stopChildProcess(childPid).finally(() => {\n reject(\n new Error(\n `Timed out waiting for postgres-memory-server child process to become ready. ${stderr.trim()}`.trim(),\n ),\n );\n });\n }, CHILD_SETUP_TIMEOUT_MS);\n\n child.stdout.setEncoding(\"utf8\");\n child.stderr.setEncoding(\"utf8\");\n\n child.stdout.on(\"data\", (chunk: string) => {\n if (settled) {\n return;\n }\n\n stdout += chunk;\n const newlineIndex = stdout.indexOf(\"\\n\");\n if (newlineIndex === -1) {\n return;\n }\n\n const firstLine = stdout.slice(0, newlineIndex).trim();\n if (!firstLine) {\n return;\n }\n\n try {\n const payload = JSON.parse(firstLine) as ChildPayload;\n settled = true;\n clearTimeout(timeout);\n resolve({ pid: childPid, payload });\n } catch {\n // Wait for more output until the first line becomes valid JSON.\n }\n });\n\n child.stderr.on(\"data\", (chunk: string) => {\n stderr += chunk;\n });\n\n child.on(\"error\", (error) => {\n if (settled) {\n return;\n }\n\n settled = true;\n clearTimeout(timeout);\n reject(error);\n });\n\n child.on(\"exit\", (code, signal) => {\n if (settled) {\n return;\n }\n\n settled = true;\n clearTimeout(timeout);\n reject(\n new Error(\n `postgres-memory-server child process exited before reporting readiness (code: ${code ?? \"null\"}, signal: ${signal ?? \"null\"}). ${stderr.trim()}`.trim(),\n ),\n );\n });\n });\n}\n\nasync function resolveChildModuleUrl(): Promise<string> {\n const currentFilePath = fileURLToPath(import.meta.url);\n const currentDirectoryPath = path.dirname(currentFilePath);\n const distEntryPath = path.resolve(currentDirectoryPath, \"../dist/index.js\");\n\n try {\n await fs.access(distEntryPath);\n } catch (error) {\n if (isMissingFileError(error)) {\n throw new Error(\n `Missing built package entry at ${distEntryPath}. Run npm run build before using the Jest global setup helpers from the repository source checkout.`,\n );\n }\n\n throw error;\n }\n\n return pathToFileURL(distEntryPath).href;\n}\n\nasync function stopChildProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch (error) {\n if (isMissingProcessError(error)) {\n return;\n }\n\n throw error;\n }\n\n const deadline = Date.now() + CHILD_SHUTDOWN_TIMEOUT_MS;\n\n while (Date.now() < deadline) {\n await sleep(POLL_INTERVAL_MS);\n\n try {\n process.kill(pid, 0);\n } catch (error) {\n if (isMissingProcessError(error)) {\n return;\n }\n\n throw error;\n }\n }\n\n throw new Error(\n `Timed out waiting for postgres-memory-server child process ${pid} to stop`,\n );\n}\n\nasync function sleep(durationMs: number): Promise<void> {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, durationMs);\n });\n}\n\nfunction isMissingFileError(error: unknown): boolean {\n return isNodeErrorWithCode(error, \"ENOENT\");\n}\n\nfunction isMissingProcessError(error: unknown): boolean {\n return isNodeErrorWithCode(error, \"ESRCH\");\n}\n\nfunction isNodeErrorWithCode(error: unknown, code: string): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === code\n );\n}\n"],"mappings":"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","names":["fs","path"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postgres-memory-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "spin up an actual/real postgres server programmatically from within nodejs, for testing or mocking during development",
|
|
5
|
+
"homepage": "https://github.com/amanthegreatone/postgres-memory-server#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/amanthegreatone/postgres-memory-server/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/amanthegreatone/postgres-memory-server.git"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Aman Agarwal",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"postgres",
|
|
18
|
+
"postgresql",
|
|
19
|
+
"testcontainers",
|
|
20
|
+
"testing",
|
|
21
|
+
"integration-testing",
|
|
22
|
+
"paradedb",
|
|
23
|
+
"pgvector",
|
|
24
|
+
"database",
|
|
25
|
+
"memory-server"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"bin": {
|
|
36
|
+
"postgres-memory-server": "./dist/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"prepack": "npm run build",
|
|
49
|
+
"test:paradedb": "npm run build && vitest run test/paradedb.test.ts examples/paradedb/hybrid-search.test.ts",
|
|
50
|
+
"test:postgres": "npm run build && vitest run test/basic.test.ts test/jest.test.ts test/presets.test.ts test/snapshot.test.ts examples/vitest/basic.test.ts",
|
|
51
|
+
"test": "npm run build && vitest run",
|
|
52
|
+
"test:watch": "vitest"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@testcontainers/postgresql": "^11.13.0",
|
|
56
|
+
"pg": "^8.20.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^25.5.0",
|
|
60
|
+
"@types/pg": "^8.20.0",
|
|
61
|
+
"tsup": "^8.5.1",
|
|
62
|
+
"typescript": "^6.0.2",
|
|
63
|
+
"vitest": "^4.1.2"
|
|
64
|
+
}
|
|
65
|
+
}
|