|
8 | 8 | // This script starts the PGlite socket server in-process, then runs a real pgpm |
9 | 9 | // deploy -> verify -> revert against it, asserting the outcome. It exits non-zero |
10 | 10 | // on any mismatch so CI fails loudly. |
11 | | -import assert from 'node:assert/strict'; |
12 | | -import { createRequire } from 'node:module'; |
13 | | -import { PGlite } from '@electric-sql/pglite'; |
14 | | -import { vector } from '@electric-sql/pglite-pgvector'; |
15 | | -import { PGLiteSocketServer } from '@electric-sql/pglite-socket'; |
16 | | -import pg from 'pg'; |
17 | | - |
18 | | -const require = createRequire(import.meta.url); |
| 11 | +const assert = require('node:assert/strict'); |
| 12 | +const { join } = require('node:path'); |
| 13 | + |
| 14 | +const { PGlite } = require('@electric-sql/pglite'); |
| 15 | +const { vector } = require('@electric-sql/pglite-pgvector'); |
| 16 | +const { PGLiteSocketServer } = require('@electric-sql/pglite-socket'); |
| 17 | +const pg = require('pg'); |
| 18 | + |
19 | 19 | // The published pgpm migrate engine — not modified, not vendored. |
20 | 20 | const { PgpmMigrate } = require('@pgpmjs/core'); |
21 | 21 |
|
22 | 22 | const HOST = '127.0.0.1'; |
23 | 23 | const PORT = Number(process.env.PGLITE_PORT || 5555); |
24 | | -const MODULE = new URL('./module', import.meta.url).pathname; |
| 24 | +const MODULE = join(__dirname, 'module'); |
25 | 25 | const pgConfig = { host: HOST, port: PORT, user: 'postgres', password: 'x', database: 'postgres' }; |
26 | 26 |
|
27 | 27 | function step(msg) { console.log(`\n=== ${msg} ===`); } |
28 | 28 |
|
29 | | -// --- boot PGlite + socket shim ------------------------------------------------- |
30 | | -const db = await PGlite.create({ extensions: { vector } }); |
31 | | -await db.waitReady; |
32 | | -// Extensions are provisioned OUT-OF-BAND: pgpm's cleanSql strips CREATE EXTENSION |
33 | | -// from migrations, exactly like pgsql-test's admin.installExtensions() / the |
34 | | -// postgres-plus image. PGlite additionally requires the ext registered in JS above. |
35 | | -await db.exec('CREATE EXTENSION IF NOT EXISTS vector;'); |
36 | | - |
37 | | -// PGlite serializes all queries onto one engine; the socket shim caps concurrent |
38 | | -// connections at 1 by default. Raise it so a pooled client works (still serialized). |
39 | | -const server = new PGLiteSocketServer({ db, host: HOST, port: PORT, maxConnections: 10 }); |
40 | | -await server.start(); |
41 | | -console.log(`[pglite] Postgres (WASM) listening on ${HOST}:${PORT}`); |
42 | | - |
43 | | -let ok = false; |
44 | | -try { |
45 | | - const migrate = new PgpmMigrate(pgConfig); |
46 | | - |
47 | | - step('initialize (bootstrap pgpm_migrate schema in PGlite)'); |
48 | | - await migrate.initialize(); |
49 | | - |
50 | | - step('deploy plan: schema -> table -> index -> pgvector column'); |
51 | | - // useTransaction:false because PGlite pins the engine to one connection while a |
52 | | - // transaction is open; the engine's deploy path mixes a txn client with pool |
53 | | - // queries, which deadlocks a single-connection backend. (See README.) |
54 | | - const deployed = await migrate.deploy({ modulePath: MODULE, useTransaction: false }); |
55 | | - console.log('deployed:', deployed.deployed); |
56 | | - assert.deepEqual(deployed.deployed, ['schema', 'table', 'index', 'embedding']); |
57 | | - assert.equal(deployed.failed, undefined); |
58 | | - |
59 | | - step('verify'); |
60 | | - const verified = await migrate.verify({ modulePath: MODULE }); |
61 | | - console.log('verify:', verified); |
62 | | - assert.deepEqual(verified.verified, ['schema', 'table', 'index', 'embedding']); |
63 | | - assert.deepEqual(verified.failed, []); |
64 | | - |
65 | | - step('data round-trip through the pgpm-deployed schema (pgvector)'); |
66 | | - const client = new pg.Client(pgConfig); |
67 | | - await client.connect(); |
68 | | - await client.query( |
69 | | - 'INSERT INTO test_app.users(name, email, embedding) VALUES ($1, $2, $3)', |
70 | | - ['ada', 'ada@example.com', '[0.1,0.2,0.3]'] |
71 | | - ); |
72 | | - const nearest = (await client.query( |
73 | | - 'SELECT name FROM test_app.users ORDER BY embedding <-> $1 LIMIT 1', |
74 | | - ['[0.1,0.2,0.3]'] |
75 | | - )).rows; |
76 | | - console.log('nearest-neighbor:', nearest); |
77 | | - assert.deepEqual(nearest, [{ name: 'ada' }]); |
78 | | - |
79 | | - const changesAfterDeploy = (await client.query( |
80 | | - 'SELECT change_name FROM pgpm_migrate.changes ORDER BY 1' |
81 | | - )).rows.map((r) => r.change_name); |
82 | | - assert.deepEqual(changesAfterDeploy, ['embedding', 'index', 'schema', 'table']); |
83 | | - |
84 | | - step('revert (full)'); |
85 | | - const reverted = await migrate.revert({ modulePath: MODULE, useTransaction: false }); |
86 | | - console.log('reverted:', reverted.reverted ?? reverted); |
87 | | - const changesAfterRevert = (await client.query( |
88 | | - 'SELECT change_name FROM pgpm_migrate.changes ORDER BY 1' |
89 | | - )).rows.map((r) => r.change_name); |
90 | | - assert.deepEqual(changesAfterRevert, []); |
91 | | - assert.equal((await client.query("SELECT to_regclass('test_app.users') AS t")).rows[0].t, null); |
92 | | - await client.end(); |
93 | | - |
94 | | - ok = true; |
95 | | - console.log('\nPASS: unmodified pgpm engine deployed + verified + reverted real migrations in PGlite; pgvector operational.'); |
96 | | -} catch (err) { |
97 | | - console.error('\nFAIL:', err); |
98 | | -} finally { |
99 | | - await server.stop(); |
100 | | - await db.close(); |
| 29 | +async function main() { |
| 30 | + // --- boot PGlite + socket shim --------------------------------------------- |
| 31 | + const db = await PGlite.create({ extensions: { vector } }); |
| 32 | + await db.waitReady; |
| 33 | + // Extensions are provisioned OUT-OF-BAND: pgpm's cleanSql strips CREATE EXTENSION |
| 34 | + // from migrations, exactly like pgsql-test's admin.installExtensions() / the |
| 35 | + // postgres-plus image. PGlite additionally requires the ext registered in JS above. |
| 36 | + await db.exec('CREATE EXTENSION IF NOT EXISTS vector;'); |
| 37 | + |
| 38 | + // PGlite serializes all queries onto one engine; the socket shim caps concurrent |
| 39 | + // connections at 1 by default. Raise it so a pooled client works (still serialized). |
| 40 | + const server = new PGLiteSocketServer({ db, host: HOST, port: PORT, maxConnections: 10 }); |
| 41 | + await server.start(); |
| 42 | + console.log(`[pglite] Postgres (WASM) listening on ${HOST}:${PORT}`); |
| 43 | + |
| 44 | + let ok = false; |
| 45 | + try { |
| 46 | + const migrate = new PgpmMigrate(pgConfig); |
| 47 | + |
| 48 | + step('initialize (bootstrap pgpm_migrate schema in PGlite)'); |
| 49 | + await migrate.initialize(); |
| 50 | + |
| 51 | + step('deploy plan: schema -> table -> index -> pgvector column'); |
| 52 | + // useTransaction:false because PGlite pins the engine to one connection while a |
| 53 | + // transaction is open; the engine's deploy path mixes a txn client with pool |
| 54 | + // queries, which deadlocks a single-connection backend. (See README.) |
| 55 | + const deployed = await migrate.deploy({ modulePath: MODULE, useTransaction: false }); |
| 56 | + console.log('deployed:', deployed.deployed); |
| 57 | + assert.deepEqual(deployed.deployed, ['schema', 'table', 'index', 'embedding']); |
| 58 | + assert.equal(deployed.failed, undefined); |
| 59 | + |
| 60 | + step('verify'); |
| 61 | + const verified = await migrate.verify({ modulePath: MODULE }); |
| 62 | + console.log('verify:', verified); |
| 63 | + assert.deepEqual(verified.verified, ['schema', 'table', 'index', 'embedding']); |
| 64 | + assert.deepEqual(verified.failed, []); |
| 65 | + |
| 66 | + step('data round-trip through the pgpm-deployed schema (pgvector)'); |
| 67 | + const client = new pg.Client(pgConfig); |
| 68 | + await client.connect(); |
| 69 | + await client.query( |
| 70 | + 'INSERT INTO test_app.users(name, email, embedding) VALUES ($1, $2, $3)', |
| 71 | + ['ada', 'ada@example.com', '[0.1,0.2,0.3]'] |
| 72 | + ); |
| 73 | + const nearest = (await client.query( |
| 74 | + 'SELECT name FROM test_app.users ORDER BY embedding <-> $1 LIMIT 1', |
| 75 | + ['[0.1,0.2,0.3]'] |
| 76 | + )).rows; |
| 77 | + console.log('nearest-neighbor:', nearest); |
| 78 | + assert.deepEqual(nearest, [{ name: 'ada' }]); |
| 79 | + |
| 80 | + const changesAfterDeploy = (await client.query( |
| 81 | + 'SELECT change_name FROM pgpm_migrate.changes ORDER BY 1' |
| 82 | + )).rows.map((r) => r.change_name); |
| 83 | + assert.deepEqual(changesAfterDeploy, ['embedding', 'index', 'schema', 'table']); |
| 84 | + |
| 85 | + step('revert (full)'); |
| 86 | + const reverted = await migrate.revert({ modulePath: MODULE, useTransaction: false }); |
| 87 | + console.log('reverted:', reverted.reverted ?? reverted); |
| 88 | + const changesAfterRevert = (await client.query( |
| 89 | + 'SELECT change_name FROM pgpm_migrate.changes ORDER BY 1' |
| 90 | + )).rows.map((r) => r.change_name); |
| 91 | + assert.deepEqual(changesAfterRevert, []); |
| 92 | + assert.equal((await client.query("SELECT to_regclass('test_app.users') AS t")).rows[0].t, null); |
| 93 | + await client.end(); |
| 94 | + |
| 95 | + ok = true; |
| 96 | + console.log('\nPASS: unmodified pgpm engine deployed + verified + reverted real migrations in PGlite; pgvector operational.'); |
| 97 | + } catch (err) { |
| 98 | + console.error('\nFAIL:', err); |
| 99 | + } finally { |
| 100 | + await server.stop(); |
| 101 | + await db.close(); |
| 102 | + } |
| 103 | + |
| 104 | + process.exit(ok ? 0 : 1); |
101 | 105 | } |
102 | 106 |
|
103 | | -process.exit(ok ? 0 : 1); |
| 107 | +main(); |
0 commit comments