mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-22 23:31:12 +02:00
feat: Enhance Electric SQL integration and update notification handling
- Added initialization script for Electric SQL user in Docker setup. - Updated Electric SQL client to support new PGlite architecture and sync functionality. - Improved notification fetching and syncing logic in useNotifications hook. - Refactored ElectricProvider to handle initialization state and errors more gracefully. - Removed deprecated electric.config.ts file and adjusted package dependencies accordingly.
This commit is contained in:
parent
82c6dd0221
commit
f441c7b0ce
10 changed files with 376 additions and 1046 deletions
|
|
@ -1,24 +1,56 @@
|
|||
/**
|
||||
* Electric SQL client setup
|
||||
* This initializes the Electric SQL client with local PGlite database (PostgreSQL in browser)
|
||||
* Electric SQL client setup for ElectricSQL 1.x with PGlite
|
||||
*
|
||||
* This uses the new ElectricSQL 1.x architecture:
|
||||
* - PGlite: In-browser PostgreSQL database (local storage)
|
||||
* - @electric-sql/pglite-sync: Sync plugin to sync Electric shapes into PGlite
|
||||
* - @electric-sql/client: HTTP client for subscribing to shapes
|
||||
*/
|
||||
|
||||
import { PGlite } from '@electric-sql/pglite'
|
||||
import { electrify } from 'electric-sql/pglite'
|
||||
import { getElectricAuthToken } from './auth'
|
||||
import { electricSync } from '@electric-sql/pglite-sync'
|
||||
|
||||
// We'll generate the schema after running electric:generate
|
||||
// For now, we'll use a placeholder type
|
||||
type Electric = any
|
||||
type Schema = any
|
||||
// Types
|
||||
export interface ElectricClient {
|
||||
db: PGlite
|
||||
syncShape: <T = Record<string, unknown>>(options: SyncShapeOptions) => Promise<SyncHandle<T>>
|
||||
}
|
||||
|
||||
let electric: Electric | null = null
|
||||
export interface SyncShapeOptions {
|
||||
table: string
|
||||
where?: string
|
||||
columns?: string[]
|
||||
primaryKey?: string[]
|
||||
}
|
||||
|
||||
export interface SyncHandle<T = Record<string, unknown>> {
|
||||
unsubscribe: () => void
|
||||
isUpToDate: boolean
|
||||
shape: {
|
||||
handle?: string
|
||||
offset?: string
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
let electricClient: ElectricClient | null = null
|
||||
let isInitializing = false
|
||||
let initPromise: Promise<Electric> | null = null
|
||||
let initPromise: Promise<ElectricClient> | null = null
|
||||
|
||||
export async function initElectric(): Promise<Electric> {
|
||||
if (electric) {
|
||||
return electric
|
||||
// Get Electric URL from environment
|
||||
function getElectricUrl(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
return process.env.NEXT_PUBLIC_ELECTRIC_URL || 'http://localhost:5133'
|
||||
}
|
||||
return 'http://localhost:5133'
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Electric SQL client with PGlite and sync plugin
|
||||
*/
|
||||
export async function initElectric(): Promise<ElectricClient> {
|
||||
if (electricClient) {
|
||||
return electricClient
|
||||
}
|
||||
|
||||
if (isInitializing && initPromise) {
|
||||
|
|
@ -28,40 +60,75 @@ export async function initElectric(): Promise<Electric> {
|
|||
isInitializing = true
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
const config = {
|
||||
auth: {
|
||||
token: await getElectricAuthToken(),
|
||||
},
|
||||
url: process.env.NEXT_PUBLIC_ELECTRIC_URL || 'http://localhost:5133',
|
||||
}
|
||||
|
||||
// Initialize PGlite database (PostgreSQL in browser)
|
||||
// Use idb:// prefix for IndexedDB storage in browser
|
||||
// relaxedDurability improves responsiveness by scheduling flush after query returns
|
||||
const conn = new PGlite('idb://surfsense.db', {
|
||||
// Create PGlite instance with Electric sync plugin
|
||||
const db = await PGlite.create('idb://surfsense-notifications', {
|
||||
relaxedDurability: true,
|
||||
extensions: {
|
||||
electric: electricSync(),
|
||||
},
|
||||
})
|
||||
|
||||
// Import schema (will be generated by electric:generate)
|
||||
// For now, we'll use a dynamic import that will work after schema generation
|
||||
let schema: Schema
|
||||
try {
|
||||
const schemaModule = await import('./generated/schema')
|
||||
schema = schemaModule.schema
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Electric SQL schema not found. Run "pnpm electric:generate" to generate it.',
|
||||
error
|
||||
)
|
||||
// Return a mock electric client for now
|
||||
return null as any
|
||||
// Create the notifications table schema in PGlite
|
||||
// This matches the backend schema
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
search_space_id INTEGER,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
read BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_read ON notifications(read);
|
||||
`)
|
||||
|
||||
const electricUrl = getElectricUrl()
|
||||
|
||||
// Create the client wrapper
|
||||
electricClient = {
|
||||
db,
|
||||
syncShape: async <T = Record<string, unknown>>(options: SyncShapeOptions): Promise<SyncHandle<T>> => {
|
||||
const { table, where, columns, primaryKey = ['id'] } = options
|
||||
|
||||
// Build params for the shape request
|
||||
const params: Record<string, string> = { table }
|
||||
if (where) params.where = where
|
||||
if (columns) params.columns = columns.join(',')
|
||||
|
||||
// Use PGlite's electric sync plugin to sync the shape
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const shape = await (db as any).electric.syncShapeToTable({
|
||||
shape: {
|
||||
url: `${electricUrl}/v1/shape`,
|
||||
params,
|
||||
},
|
||||
table,
|
||||
primaryKey,
|
||||
})
|
||||
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
if (shape && typeof shape.unsubscribe === 'function') {
|
||||
shape.unsubscribe()
|
||||
}
|
||||
},
|
||||
isUpToDate: shape?.isUpToDate ?? false,
|
||||
shape: {
|
||||
handle: shape?.handle,
|
||||
offset: shape?.offset,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Electrify the PGlite database connection
|
||||
electric = await electrify(conn, schema, config)
|
||||
|
||||
console.log('Electric SQL initialized successfully with PGlite')
|
||||
return electric
|
||||
return electricClient
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Electric SQL:', error)
|
||||
throw error
|
||||
|
|
@ -73,14 +140,26 @@ export async function initElectric(): Promise<Electric> {
|
|||
return initPromise
|
||||
}
|
||||
|
||||
export function getElectric(): Electric {
|
||||
if (!electric) {
|
||||
/**
|
||||
* Get the Electric client (throws if not initialized)
|
||||
*/
|
||||
export function getElectric(): ElectricClient {
|
||||
if (!electricClient) {
|
||||
throw new Error('Electric not initialized. Call initElectric() first.')
|
||||
}
|
||||
return electric
|
||||
return electricClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Electric is initialized
|
||||
*/
|
||||
export function isElectricInitialized(): boolean {
|
||||
return electric !== null
|
||||
return electricClient !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PGlite database instance
|
||||
*/
|
||||
export function getDb(): PGlite | null {
|
||||
return electricClient?.db ?? null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Electric SQL configuration
|
||||
* This file will be used by @electric-sql/cli to generate the schema
|
||||
*/
|
||||
|
||||
export const electricConfig = {
|
||||
connection: {
|
||||
host: process.env.ELECTRIC_HOST || 'localhost',
|
||||
port: parseInt(process.env.ELECTRIC_PORT || '5133', 10),
|
||||
database: process.env.POSTGRES_DB || 'surfsense',
|
||||
user: process.env.ELECTRIC_USER || 'electric',
|
||||
password: process.env.ELECTRIC_PASSWORD || 'electric_password',
|
||||
},
|
||||
service: {
|
||||
host: process.env.ELECTRIC_HOST || 'localhost',
|
||||
port: parseInt(process.env.ELECTRIC_PORT || '5133', 10),
|
||||
},
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue