orez 0.0.1

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.
Files changed (47) hide show
  1. package/README.md +116 -0
  2. package/dist/config.d.ts +15 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +20 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +15 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +195 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/pg-proxy.d.ts +14 -0
  11. package/dist/pg-proxy.d.ts.map +1 -0
  12. package/dist/pg-proxy.js +385 -0
  13. package/dist/pg-proxy.js.map +1 -0
  14. package/dist/pglite-manager.d.ts +5 -0
  15. package/dist/pglite-manager.d.ts.map +1 -0
  16. package/dist/pglite-manager.js +71 -0
  17. package/dist/pglite-manager.js.map +1 -0
  18. package/dist/replication/change-tracker.d.ts +14 -0
  19. package/dist/replication/change-tracker.d.ts.map +1 -0
  20. package/dist/replication/change-tracker.js +86 -0
  21. package/dist/replication/change-tracker.js.map +1 -0
  22. package/dist/replication/handler.d.ts +24 -0
  23. package/dist/replication/handler.d.ts.map +1 -0
  24. package/dist/replication/handler.js +300 -0
  25. package/dist/replication/handler.js.map +1 -0
  26. package/dist/replication/pgoutput-encoder.d.ts +26 -0
  27. package/dist/replication/pgoutput-encoder.d.ts.map +1 -0
  28. package/dist/replication/pgoutput-encoder.js +204 -0
  29. package/dist/replication/pgoutput-encoder.js.map +1 -0
  30. package/dist/s3-local.d.ts +8 -0
  31. package/dist/s3-local.d.ts.map +1 -0
  32. package/dist/s3-local.js +131 -0
  33. package/dist/s3-local.js.map +1 -0
  34. package/package.json +56 -0
  35. package/src/config.ts +40 -0
  36. package/src/index.ts +255 -0
  37. package/src/pg-proxy.ts +474 -0
  38. package/src/pglite-manager.ts +105 -0
  39. package/src/replication/change-tracker.test.ts +179 -0
  40. package/src/replication/change-tracker.ts +115 -0
  41. package/src/replication/handler.test.ts +331 -0
  42. package/src/replication/handler.ts +378 -0
  43. package/src/replication/pgoutput-encoder.test.ts +381 -0
  44. package/src/replication/pgoutput-encoder.ts +252 -0
  45. package/src/replication/tcp-replication.test.ts +824 -0
  46. package/src/replication/zero-compat.test.ts +882 -0
  47. package/src/s3-local.ts +179 -0
@@ -0,0 +1,385 @@
1
+ /**
2
+ * tcp proxy that makes pglite speak postgresql wire protocol.
3
+ *
4
+ * uses pg-gateway to handle protocol lifecycle for regular connections,
5
+ * and directly handles the raw socket for replication connections.
6
+ *
7
+ * regular connections: forwarded to pglite via execProtocolRaw()
8
+ * replication connections: intercepted, replication protocol faked
9
+ */
10
+ import { createServer } from 'node:net';
11
+ import { fromNodeSocket } from 'pg-gateway/node';
12
+ import { handleReplicationQuery, handleStartReplication, } from './replication/handler';
13
+ // database name -> search_path mapping
14
+ const DB_SCHEMA_MAP = {
15
+ postgres: 'public',
16
+ zero_cvr: 'zero_cvr, public',
17
+ zero_cdb: 'zero_cdb, public',
18
+ };
19
+ // query rewrites: make pglite look like real postgres with logical replication
20
+ const QUERY_REWRITES = [
21
+ // wal_level check
22
+ {
23
+ match: /current_setting\s*\(\s*'wal_level'\s*\)/gi,
24
+ replace: "'logical'::text",
25
+ },
26
+ // strip READ ONLY from BEGIN
27
+ {
28
+ match: /\bREAD\s+ONLY\b/gi,
29
+ replace: '',
30
+ },
31
+ // redirect pg_replication_slots to our fake table
32
+ {
33
+ match: /\bpg_replication_slots\b/g,
34
+ replace: 'public._zero_replication_slots',
35
+ },
36
+ ];
37
+ // queries to intercept and return no-op success
38
+ const NOOP_QUERY_PATTERNS = [
39
+ /^\s*SET\s+TRANSACTION\s+SNAPSHOT\s+/i,
40
+ ];
41
+ /**
42
+ * extract query text from a Parse message (0x50).
43
+ */
44
+ function extractParseQuery(data) {
45
+ if (data[0] !== 0x50)
46
+ return null;
47
+ let offset = 5;
48
+ while (offset < data.length && data[offset] !== 0)
49
+ offset++;
50
+ offset++;
51
+ const queryStart = offset;
52
+ while (offset < data.length && data[offset] !== 0)
53
+ offset++;
54
+ return new TextDecoder().decode(data.subarray(queryStart, offset));
55
+ }
56
+ /**
57
+ * rebuild a Parse message with a modified query string.
58
+ */
59
+ function rebuildParseMessage(data, newQuery) {
60
+ let offset = 5;
61
+ while (offset < data.length && data[offset] !== 0)
62
+ offset++;
63
+ const nameEnd = offset + 1;
64
+ const nameBytes = data.subarray(5, nameEnd);
65
+ offset = nameEnd;
66
+ while (offset < data.length && data[offset] !== 0)
67
+ offset++;
68
+ offset++;
69
+ const suffix = data.subarray(offset);
70
+ const encoder = new TextEncoder();
71
+ const queryBytes = encoder.encode(newQuery);
72
+ const totalLen = 4 + nameBytes.length + queryBytes.length + 1 + suffix.length;
73
+ const result = new Uint8Array(1 + totalLen);
74
+ const dv = new DataView(result.buffer);
75
+ result[0] = 0x50;
76
+ dv.setInt32(1, totalLen);
77
+ let pos = 5;
78
+ result.set(nameBytes, pos);
79
+ pos += nameBytes.length;
80
+ result.set(queryBytes, pos);
81
+ pos += queryBytes.length;
82
+ result[pos++] = 0;
83
+ result.set(suffix, pos);
84
+ return result;
85
+ }
86
+ /**
87
+ * rebuild a Simple Query message with a modified query string.
88
+ */
89
+ function rebuildSimpleQuery(newQuery) {
90
+ const encoder = new TextEncoder();
91
+ const queryBytes = encoder.encode(newQuery + '\0');
92
+ const buf = new Uint8Array(5 + queryBytes.length);
93
+ buf[0] = 0x51;
94
+ new DataView(buf.buffer).setInt32(1, 4 + queryBytes.length);
95
+ buf.set(queryBytes, 5);
96
+ return buf;
97
+ }
98
+ /**
99
+ * intercept and rewrite query messages to make pglite look like real postgres.
100
+ */
101
+ function interceptQuery(data) {
102
+ const msgType = data[0];
103
+ if (msgType === 0x51) {
104
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
105
+ const len = view.getInt32(1);
106
+ let query = new TextDecoder()
107
+ .decode(data.subarray(5, 1 + len - 1))
108
+ .replace(/\0$/, '');
109
+ let modified = false;
110
+ for (const rw of QUERY_REWRITES) {
111
+ if (rw.match.test(query)) {
112
+ query = query.replace(rw.match, rw.replace);
113
+ modified = true;
114
+ rw.match.lastIndex = 0;
115
+ }
116
+ rw.match.lastIndex = 0;
117
+ }
118
+ if (modified) {
119
+ return rebuildSimpleQuery(query);
120
+ }
121
+ }
122
+ else if (msgType === 0x50) {
123
+ const query = extractParseQuery(data);
124
+ if (query) {
125
+ let newQuery = query;
126
+ let modified = false;
127
+ for (const rw of QUERY_REWRITES) {
128
+ if (rw.match.test(newQuery)) {
129
+ newQuery = newQuery.replace(rw.match, rw.replace);
130
+ modified = true;
131
+ rw.match.lastIndex = 0;
132
+ }
133
+ rw.match.lastIndex = 0;
134
+ }
135
+ if (modified) {
136
+ return rebuildParseMessage(data, newQuery);
137
+ }
138
+ }
139
+ }
140
+ return data;
141
+ }
142
+ /**
143
+ * check if a query should be intercepted as a no-op.
144
+ */
145
+ function isNoopQuery(data) {
146
+ let query = null;
147
+ if (data[0] === 0x51) {
148
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
149
+ const len = view.getInt32(1);
150
+ query = new TextDecoder()
151
+ .decode(data.subarray(5, 1 + len - 1))
152
+ .replace(/\0$/, '');
153
+ }
154
+ else if (data[0] === 0x50) {
155
+ query = extractParseQuery(data);
156
+ }
157
+ if (!query)
158
+ return false;
159
+ return NOOP_QUERY_PATTERNS.some((p) => p.test(query));
160
+ }
161
+ /**
162
+ * build a synthetic "SET" command complete response.
163
+ */
164
+ function buildSetCompleteResponse() {
165
+ const encoder = new TextEncoder();
166
+ const tag = encoder.encode('SET\0');
167
+ const cc = new Uint8Array(1 + 4 + tag.length);
168
+ cc[0] = 0x43;
169
+ new DataView(cc.buffer).setInt32(1, 4 + tag.length);
170
+ cc.set(tag, 5);
171
+ const rfq = new Uint8Array(6);
172
+ rfq[0] = 0x5a;
173
+ new DataView(rfq.buffer).setInt32(1, 5);
174
+ rfq[5] = 0x54; // 'T' = in transaction
175
+ const result = new Uint8Array(cc.length + rfq.length);
176
+ result.set(cc, 0);
177
+ result.set(rfq, cc.length);
178
+ return result;
179
+ }
180
+ /**
181
+ * build a synthetic ParseComplete response for extended protocol no-ops.
182
+ */
183
+ function buildParseCompleteResponse() {
184
+ const pc = new Uint8Array(5);
185
+ pc[0] = 0x31; // ParseComplete
186
+ new DataView(pc.buffer).setInt32(1, 4);
187
+ return pc;
188
+ }
189
+ /**
190
+ * strip ReadyForQuery messages from a response buffer.
191
+ */
192
+ function stripReadyForQuery(data) {
193
+ if (data.length === 0)
194
+ return data;
195
+ const parts = [];
196
+ let offset = 0;
197
+ while (offset < data.length) {
198
+ const msgType = data[offset];
199
+ if (offset + 5 > data.length)
200
+ break;
201
+ const msgLen = new DataView(data.buffer, data.byteOffset + offset + 1).getInt32(0);
202
+ const totalLen = 1 + msgLen;
203
+ if (msgType !== 0x5a) {
204
+ parts.push(data.subarray(offset, offset + totalLen));
205
+ }
206
+ offset += totalLen;
207
+ }
208
+ if (parts.length === 0)
209
+ return new Uint8Array(0);
210
+ if (parts.length === 1)
211
+ return parts[0];
212
+ const total = parts.reduce((sum, p) => sum + p.length, 0);
213
+ const result = new Uint8Array(total);
214
+ let pos = 0;
215
+ for (const p of parts) {
216
+ result.set(p, pos);
217
+ pos += p.length;
218
+ }
219
+ return result;
220
+ }
221
+ // simple mutex for serializing pglite access
222
+ class Mutex {
223
+ locked = false;
224
+ queue = [];
225
+ async acquire() {
226
+ if (!this.locked) {
227
+ this.locked = true;
228
+ return;
229
+ }
230
+ return new Promise((resolve) => {
231
+ this.queue.push(resolve);
232
+ });
233
+ }
234
+ release() {
235
+ const next = this.queue.shift();
236
+ if (next) {
237
+ next();
238
+ }
239
+ else {
240
+ this.locked = false;
241
+ }
242
+ }
243
+ }
244
+ const mutex = new Mutex();
245
+ // module-level search_path tracking
246
+ let currentSearchPath = 'public';
247
+ export async function startPgProxy(db, config) {
248
+ const server = createServer(async (socket) => {
249
+ let dbName = 'postgres';
250
+ let isReplicationConnection = false;
251
+ try {
252
+ const connection = await fromNodeSocket(socket, {
253
+ auth: {
254
+ method: 'password',
255
+ getClearTextPassword() {
256
+ return config.pgPassword;
257
+ },
258
+ validateCredentials(credentials) {
259
+ return (credentials.password ===
260
+ credentials.clearTextPassword &&
261
+ credentials.username === config.pgUser);
262
+ },
263
+ },
264
+ async onStartup(state) {
265
+ const params = state.clientParams;
266
+ if (params?.replication === 'database') {
267
+ isReplicationConnection = true;
268
+ }
269
+ dbName = params?.database || 'postgres';
270
+ console.info(`[orez] new connection: db=${dbName} user=${params?.user} replication=${params?.replication || 'none'}`);
271
+ await db.waitReady;
272
+ },
273
+ async onMessage(data, state) {
274
+ if (!state.isAuthenticated)
275
+ return;
276
+ // handle replication connections
277
+ if (isReplicationConnection) {
278
+ if (data[0] === 0x51) {
279
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
280
+ const len = view.getInt32(1);
281
+ const query = new TextDecoder()
282
+ .decode(data.subarray(5, 1 + len - 1))
283
+ .replace(/\0$/, '');
284
+ console.info(`[orez] repl query: ${query.slice(0, 200)}`);
285
+ }
286
+ return handleReplicationMessage(data, socket, db, connection);
287
+ }
288
+ // check for no-op queries
289
+ if (isNoopQuery(data)) {
290
+ if (data[0] === 0x51) {
291
+ return buildSetCompleteResponse();
292
+ }
293
+ else if (data[0] === 0x50) {
294
+ return buildParseCompleteResponse();
295
+ }
296
+ }
297
+ // intercept and rewrite queries
298
+ data = interceptQuery(data);
299
+ // regular query: set search_path based on database name, then forward
300
+ await mutex.acquire();
301
+ try {
302
+ const searchPath = DB_SCHEMA_MAP[dbName] || 'public';
303
+ if (currentSearchPath !== searchPath) {
304
+ await db.exec(`SET search_path TO ${searchPath}`);
305
+ currentSearchPath = searchPath;
306
+ }
307
+ let result = await db.execProtocolRaw(data, {
308
+ throwOnError: false,
309
+ });
310
+ // strip ReadyForQuery from non-Sync responses
311
+ if (data[0] !== 0x53 && data[0] !== 0x51) {
312
+ result = stripReadyForQuery(result);
313
+ }
314
+ return result;
315
+ }
316
+ finally {
317
+ mutex.release();
318
+ }
319
+ },
320
+ });
321
+ }
322
+ catch (err) {
323
+ if (!socket.destroyed) {
324
+ socket.destroy();
325
+ }
326
+ }
327
+ });
328
+ return new Promise((resolve, reject) => {
329
+ server.listen(config.pgPort, '127.0.0.1', () => {
330
+ console.info(`[orez] pg proxy listening on port ${config.pgPort}`);
331
+ resolve(server);
332
+ });
333
+ server.on('error', reject);
334
+ });
335
+ }
336
+ async function handleReplicationMessage(data, socket, db, connection) {
337
+ if (data[0] !== 0x51)
338
+ return undefined;
339
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
340
+ const len = view.getInt32(1);
341
+ const query = new TextDecoder()
342
+ .decode(data.subarray(5, 1 + len - 1))
343
+ .replace(/\0$/, '');
344
+ const upper = query.trim().toUpperCase();
345
+ // check if this is a START_REPLICATION command
346
+ if (upper.startsWith('START_REPLICATION')) {
347
+ await connection.detach();
348
+ const writer = {
349
+ write(chunk) {
350
+ if (!socket.destroyed) {
351
+ socket.write(chunk);
352
+ }
353
+ },
354
+ };
355
+ // drain incoming standby status updates
356
+ socket.on('data', (_chunk) => { });
357
+ socket.on('close', () => {
358
+ socket.destroy();
359
+ });
360
+ handleStartReplication(query, writer, db).catch((err) => {
361
+ console.info(`[orez] replication stream ended: ${err}`);
362
+ });
363
+ return undefined;
364
+ }
365
+ // handle other replication queries
366
+ const response = await handleReplicationQuery(query, db);
367
+ if (response)
368
+ return response;
369
+ // fall through to pglite for unrecognized queries
370
+ await mutex.acquire();
371
+ try {
372
+ const searchPath = 'public';
373
+ if (currentSearchPath !== searchPath) {
374
+ await db.exec(`SET search_path TO ${searchPath}`);
375
+ currentSearchPath = searchPath;
376
+ }
377
+ return await db.execProtocolRaw(data, {
378
+ throwOnError: false,
379
+ });
380
+ }
381
+ finally {
382
+ mutex.release();
383
+ }
384
+ }
385
+ //# sourceMappingURL=pg-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-proxy.js","sourceRoot":"","sources":["../src/pg-proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { PGlite } from '@electric-sql/pglite';
2
+ import type { ZeroLiteConfig } from './config';
3
+ export declare function createPGliteInstance(config: ZeroLiteConfig): Promise<PGlite>;
4
+ export declare function runMigrations(db: PGlite, config: ZeroLiteConfig): Promise<void>;
5
+ //# sourceMappingURL=pglite-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-manager.d.ts","sourceRoot":"","sources":["../src/pglite-manager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
@@ -0,0 +1,71 @@
1
+ import { readFileSync, readdirSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { PGlite } from '@electric-sql/pglite';
4
+ export async function createPGliteInstance(config) {
5
+ const dataPath = resolve(config.dataDir, 'pgdata');
6
+ mkdirSync(dataPath, { recursive: true });
7
+ console.info(`[orez] creating pglite instance at ${dataPath}`);
8
+ const db = new PGlite(dataPath);
9
+ await db.waitReady;
10
+ console.info('[orez] pglite ready');
11
+ // create schemas for multi-db simulation
12
+ await db.exec('CREATE SCHEMA IF NOT EXISTS zero_cvr');
13
+ await db.exec('CREATE SCHEMA IF NOT EXISTS zero_cdb');
14
+ // create publication for zero-cache
15
+ const pubName = process.env.ZERO_APP_PUBLICATIONS || 'zero_pub';
16
+ const pubs = await db.query(`SELECT count(*) as count FROM pg_publication WHERE pubname = $1`, [pubName]);
17
+ if (Number(pubs.rows[0].count) === 0) {
18
+ const quoted = '"' + pubName.replace(/"/g, '""') + '"';
19
+ await db.exec(`CREATE PUBLICATION ${quoted} FOR ALL TABLES`);
20
+ }
21
+ return db;
22
+ }
23
+ export async function runMigrations(db, config) {
24
+ const migrationsDir = resolve(config.migrationsDir);
25
+ if (!existsSync(migrationsDir)) {
26
+ console.info('[orez] no migrations directory found, skipping');
27
+ return;
28
+ }
29
+ // create migrations tracking table
30
+ await db.exec(`
31
+ CREATE TABLE IF NOT EXISTS public.migrations (
32
+ id SERIAL PRIMARY KEY,
33
+ name TEXT NOT NULL UNIQUE,
34
+ applied_at TIMESTAMPTZ DEFAULT NOW()
35
+ )
36
+ `);
37
+ // read drizzle journal for correct migration order
38
+ const journalPath = join(migrationsDir, 'meta', '_journal.json');
39
+ let files;
40
+ if (existsSync(journalPath)) {
41
+ const journal = JSON.parse(readFileSync(journalPath, 'utf-8'));
42
+ files = journal.entries.map((e) => `${e.tag}.sql`);
43
+ }
44
+ else {
45
+ files = readdirSync(migrationsDir)
46
+ .filter((f) => f.endsWith('.sql'))
47
+ .sort();
48
+ }
49
+ for (const file of files) {
50
+ const name = file.replace(/\.sql$/, '');
51
+ // check if already applied
52
+ const result = await db.query('SELECT count(*) as count FROM public.migrations WHERE name = $1', [name]);
53
+ if (Number(result.rows[0].count) > 0) {
54
+ continue;
55
+ }
56
+ console.info(`[orez] applying migration: ${name}`);
57
+ const sql = readFileSync(join(migrationsDir, file), 'utf-8');
58
+ // split by drizzle's statement-breakpoint marker
59
+ const statements = sql
60
+ .split('--> statement-breakpoint')
61
+ .map((s) => s.trim())
62
+ .filter(Boolean);
63
+ for (const stmt of statements) {
64
+ await db.exec(stmt);
65
+ }
66
+ await db.query('INSERT INTO public.migrations (name) VALUES ($1)', [name]);
67
+ console.info(`[orez] applied migration: ${name}`);
68
+ }
69
+ console.info('[orez] migrations complete');
70
+ }
71
+ //# sourceMappingURL=pglite-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-manager.js","sourceRoot":"","sources":["../src/pglite-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { PGlite } from '@electric-sql/pglite';
2
+ export interface ChangeRecord {
3
+ id: number;
4
+ watermark: number;
5
+ table_name: string;
6
+ op: 'INSERT' | 'UPDATE' | 'DELETE';
7
+ row_data: Record<string, unknown> | null;
8
+ old_data: Record<string, unknown> | null;
9
+ changed_at: string;
10
+ }
11
+ export declare function installChangeTracking(db: PGlite): Promise<void>;
12
+ export declare function getChangesSince(db: PGlite, watermark: number, limit?: number): Promise<ChangeRecord[]>;
13
+ export declare function getCurrentWatermark(db: PGlite): Promise<number>;
14
+ //# sourceMappingURL=change-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-tracker.d.ts","sourceRoot":"","sources":["../../src/replication/change-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDrE;AA6BD,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,KAAK,SAAO,GACX,OAAO,CAAC,YAAY,EAAE,CAAC,CAMzB;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE"}
@@ -0,0 +1,86 @@
1
+ export async function installChangeTracking(db) {
2
+ // create changes table and watermark sequence
3
+ await db.exec(`
4
+ CREATE SEQUENCE IF NOT EXISTS public._zero_watermark;
5
+
6
+ CREATE TABLE IF NOT EXISTS public._zero_changes (
7
+ id BIGSERIAL PRIMARY KEY,
8
+ watermark BIGINT NOT NULL DEFAULT nextval('public._zero_watermark'),
9
+ table_name TEXT NOT NULL,
10
+ op TEXT NOT NULL,
11
+ row_data JSONB,
12
+ old_data JSONB,
13
+ changed_at TIMESTAMPTZ DEFAULT NOW()
14
+ );
15
+
16
+ CREATE INDEX IF NOT EXISTS _zero_changes_watermark_idx ON public._zero_changes (watermark);
17
+
18
+ CREATE TABLE IF NOT EXISTS public._zero_replication_slots (
19
+ slot_name TEXT PRIMARY KEY,
20
+ restart_lsn TEXT NOT NULL DEFAULT '0/1000000',
21
+ confirmed_flush_lsn TEXT NOT NULL DEFAULT '0/1000000',
22
+ wal_status TEXT NOT NULL DEFAULT 'reserved',
23
+ plugin TEXT NOT NULL DEFAULT 'pgoutput',
24
+ slot_type TEXT NOT NULL DEFAULT 'logical',
25
+ active BOOLEAN NOT NULL DEFAULT false,
26
+ active_pid INTEGER DEFAULT NULL,
27
+ created_at TIMESTAMPTZ DEFAULT NOW()
28
+ );
29
+ `);
30
+ // create trigger function
31
+ await db.exec(`
32
+ CREATE OR REPLACE FUNCTION public._zero_track_change() RETURNS TRIGGER AS $$
33
+ BEGIN
34
+ IF TG_OP = 'DELETE' THEN
35
+ INSERT INTO public._zero_changes (table_name, op, old_data)
36
+ VALUES (TG_TABLE_NAME, 'DELETE', row_to_json(OLD)::jsonb);
37
+ RETURN OLD;
38
+ ELSIF TG_OP = 'UPDATE' THEN
39
+ INSERT INTO public._zero_changes (table_name, op, row_data, old_data)
40
+ VALUES (TG_TABLE_NAME, 'UPDATE', row_to_json(NEW)::jsonb, row_to_json(OLD)::jsonb);
41
+ RETURN NEW;
42
+ ELSIF TG_OP = 'INSERT' THEN
43
+ INSERT INTO public._zero_changes (table_name, op, row_data)
44
+ VALUES (TG_TABLE_NAME, 'INSERT', row_to_json(NEW)::jsonb);
45
+ RETURN NEW;
46
+ END IF;
47
+ RETURN NULL;
48
+ END;
49
+ $$ LANGUAGE plpgsql;
50
+ `);
51
+ // install triggers on all public tables
52
+ await installTriggersOnAllTables(db);
53
+ }
54
+ function quoteIdent(name) {
55
+ return '"' + name.replace(/"/g, '""') + '"';
56
+ }
57
+ async function installTriggersOnAllTables(db) {
58
+ const tables = await db.query(`SELECT tablename FROM pg_tables
59
+ WHERE schemaname = 'public'
60
+ AND tablename NOT IN ('migrations', '_zero_changes')
61
+ AND tablename NOT LIKE '_zero_%'`);
62
+ let count = 0;
63
+ for (const { tablename } of tables.rows) {
64
+ const quoted = quoteIdent(tablename);
65
+ await db.exec(`
66
+ DROP TRIGGER IF EXISTS _zero_change_trigger ON public.${quoted};
67
+ CREATE TRIGGER _zero_change_trigger
68
+ AFTER INSERT OR UPDATE OR DELETE ON public.${quoted}
69
+ FOR EACH ROW EXECUTE FUNCTION public._zero_track_change();
70
+ `);
71
+ count++;
72
+ }
73
+ console.info(`[orez] installed change tracking triggers on ${count} tables`);
74
+ }
75
+ export async function getChangesSince(db, watermark, limit = 1000) {
76
+ const result = await db.query('SELECT * FROM public._zero_changes WHERE watermark > $1 ORDER BY watermark LIMIT $2', [watermark, limit]);
77
+ return result.rows;
78
+ }
79
+ export async function getCurrentWatermark(db) {
80
+ const result = await db.query('SELECT last_value, is_called FROM public._zero_watermark');
81
+ const { last_value, is_called } = result.rows[0];
82
+ if (!is_called)
83
+ return 0;
84
+ return Number(last_value);
85
+ }
86
+ //# sourceMappingURL=change-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-tracker.js","sourceRoot":"","sources":["../../src/replication/change-tracker.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAU;IACpD,8CAA8C;IAC9C,MAAM,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Bb,CAAC,CAAA;IAEF,0BAA0B;IAC1B,MAAM,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;GAmBb,CAAC,CAAA;IAEF,wCAAwC;IACxC,MAAM,0BAA0B,CAAC,EAAE,CAAC,CAAA;AACtC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,GAAG,CAAA;AAC7C,CAAC;AAED,KAAK,UAAU,0BAA0B,CAAC,EAAU;IAClD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAC3B;;;wCAGoC,CACrC,CAAA;IAED,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,EAAE,SAAS,EAAE,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;QACpC,MAAM,EAAE,CAAC,IAAI,CAAC;8DAC4C,MAAM;;qDAEf,MAAM;;KAEtD,CAAC,CAAA;QACF,KAAK,EAAE,CAAA;IACT,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,gDAAgD,KAAK,SAAS,CAAC,CAAA;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,EAAU,EACV,SAAiB,EACjB,KAAK,GAAG,IAAI;IAEZ,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAC3B,qFAAqF,EACrF,CAAC,SAAS,EAAE,KAAK,CAAC,CACnB,CAAA;IACD,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAU;IAClD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAC3B,0DAA0D,CAC3D,CAAA;IACD,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAChD,IAAI,CAAC,SAAS;QAAE,OAAO,CAAC,CAAA;IACxB,OAAO,MAAM,CAAC,UAAU,CAAC,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * replication protocol handler.
3
+ *
4
+ * intercepts replication-mode queries (IDENTIFY_SYSTEM, CREATE_REPLICATION_SLOT,
5
+ * START_REPLICATION) and returns fake responses that make zero-cache believe
6
+ * it's talking to a real postgres with logical replication.
7
+ */
8
+ import type { PGlite } from '@electric-sql/pglite';
9
+ export interface ReplicationWriter {
10
+ write(data: Uint8Array): void;
11
+ }
12
+ declare function buildErrorResponse(message: string): Uint8Array;
13
+ /**
14
+ * handle a replication query. returns response bytes or null if not handled.
15
+ * async because slot operations need to write to pglite.
16
+ */
17
+ export declare function handleReplicationQuery(query: string, db: PGlite): Promise<Uint8Array | null>;
18
+ /**
19
+ * start streaming replication changes to the client.
20
+ * this runs indefinitely until the connection is closed.
21
+ */
22
+ export declare function handleStartReplication(query: string, writer: ReplicationWriter, db: PGlite): Promise<void>;
23
+ export { buildErrorResponse };
24
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/replication/handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAiBlD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;CAC9B;AA4HD,iBAAS,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAqBvD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAoDlG;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,iBAAiB,EACzB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAgFf;AAyDD,OAAO,EAAE,kBAAkB,EAAE,CAAA"}