zeno-db
Version:
A lightweight, offline-first client-side database with automatic sync capabilities
172 lines (171 loc) • 6.57 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgresSync = void 0;
const pg_1 = require("pg");
class PostgresSync {
constructor(config) {
this.pool = null;
this.lastTimestamp = 0;
this.receiveCallback = null;
this.pollTimer = null;
this.isConnected = false;
this.pool = new pg_1.Pool({
host: config.host,
port: config.port,
database: config.database,
user: config.user,
password: config.password,
ssl: config.ssl,
connectionTimeoutMillis: 10000,
idleTimeoutMillis: 30000,
max: 10
});
this.tableName = config.tableName || 'zenodb_changes';
this.pollInterval = config.pollInterval || 1000;
this.clientId = config.clientId;
}
connect() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.pool) {
throw new Error('PostgreSQL pool not initialized');
}
try {
yield this.pool.query('SELECT NOW()');
yield this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.tableName} (
id SERIAL PRIMARY KEY,
key TEXT NOT NULL,
value JSONB,
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
client_id TEXT NOT NULL,
operation TEXT NOT NULL
)
`);
this.isConnected = true;
this.startPolling();
if (this.onConnectedCallback) {
this.onConnectedCallback();
}
}
catch (error) {
console.error('Error connecting to PostgreSQL:', error);
this.isConnected = false;
throw error;
}
});
}
onDisconnected(callback) {
this.onDisconnectedCallback = callback;
}
startPolling() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.pool) {
throw new Error('PostgreSQL pool not initialized');
}
if (this.pollTimer) {
clearInterval(this.pollTimer);
}
const poll = () => __awaiter(this, void 0, void 0, function* () {
if (!this.isConnected || !this.pool)
return;
console.log('Polling PostgreSQL for changes, last timestamp:', this.lastTimestamp);
try {
const result = yield this.pool.query(`
SELECT * FROM ${this.tableName}
WHERE timestamp > to_timestamp($1)
AND client_id != $2
ORDER BY timestamp ASC
`, [this.lastTimestamp / 1000, this.clientId]);
for (const row of result.rows) {
this.lastTimestamp = new Date(row.timestamp).getTime();
if (this.onReceiveCallback) {
this.onReceiveCallback({
key: row.key,
value: row.value,
operation: row.operation,
timestamp: this.lastTimestamp,
clientId: row.client_id
});
}
}
}
catch (error) {
console.error('Error polling PostgreSQL:', error);
this.isConnected = false;
if (this.onDisconnectedCallback) {
this.onDisconnectedCallback();
}
}
});
this.pollTimer = setInterval(poll, this.pollInterval);
yield poll(); // Initial poll
});
}
send(change) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.pool) {
throw new Error('PostgreSQL pool not initialized');
}
console.log('Sending change to PostgreSQL:', change);
try {
yield this.pool.query(`
INSERT INTO ${this.tableName} (key, value, timestamp, client_id, operation)
VALUES ($1, $2, CURRENT_TIMESTAMP, $3, $4)
`, [change.key, change.value, this.clientId, change.operation]);
console.log('Change sent successfully');
return true;
}
catch (error) {
console.error('Error sending change to PostgreSQL:', error);
return false;
}
});
}
onReceive(callback) {
this.onReceiveCallback = callback;
}
onConnected(callback) {
this.onConnectedCallback = callback;
}
close() {
return __awaiter(this, void 0, void 0, function* () {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
this.isConnected = false;
if (this.onDisconnectedCallback) {
this.onDisconnectedCallback();
}
if (this.pool) {
yield this.pool.end();
}
});
}
disconnect() {
return __awaiter(this, void 0, void 0, function* () {
if (this.pool) {
yield this.pool.end();
this.pool = null;
}
});
}
sendChange(change) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.pool) {
throw new Error('PostgreSQL client not connected');
}
yield this.pool.query('INSERT INTO changes (key, value, timestamp, client_id) VALUES ($1, $2, $3, $4)', [change.key, change.value, change.timestamp, change.clientId]);
});
}
}
exports.PostgresSync = PostgresSync;