experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

remove tinybase, go raw sqlite for now. migration.

+963 -849
+275
app/datastore/db.js
··· 1 + // Database module for direct SQLite access 2 + // Replaces TinyBase with better-sqlite3 3 + 4 + import Database from 'better-sqlite3'; 5 + import { createTableStatements, tableNames } from './schema-sql.js'; 6 + 7 + let db = null; 8 + 9 + /** 10 + * Initialize the SQLite database 11 + * @param {string} dbPath - Path to the database file 12 + * @returns {Database} The database instance 13 + */ 14 + export const initDatabase = (dbPath) => { 15 + console.log('main', 'initializing database at:', dbPath); 16 + 17 + db = new Database(dbPath); 18 + 19 + // Enable WAL mode for better concurrent access 20 + db.pragma('journal_mode = WAL'); 21 + 22 + // Create tables and indexes 23 + db.exec(createTableStatements); 24 + 25 + // Migrate from TinyBase if needed 26 + migrateTinyBaseData(); 27 + 28 + console.log('main', 'database initialized successfully'); 29 + return db; 30 + }; 31 + 32 + /** 33 + * One-time migration from TinyBase internal format to direct tables 34 + */ 35 + const migrateTinyBaseData = () => { 36 + // Check if tinybase table exists 37 + const tinybaseExists = db.prepare(` 38 + SELECT name FROM sqlite_master WHERE type='table' AND name='tinybase' 39 + `).get(); 40 + 41 + if (!tinybaseExists) { 42 + return; // No TinyBase data to migrate 43 + } 44 + 45 + // Check if we already migrated (addresses table has data) 46 + const existingData = db.prepare('SELECT COUNT(*) as count FROM addresses').get(); 47 + if (existingData.count > 0) { 48 + console.log('main', 'TinyBase data already migrated, skipping'); 49 + return; 50 + } 51 + 52 + console.log('main', 'Migrating TinyBase data to direct tables...'); 53 + 54 + try { 55 + // Read TinyBase data (stored as JSON in a single row) 56 + const tinybaseRow = db.prepare('SELECT * FROM tinybase').get(); 57 + if (!tinybaseRow) { 58 + console.log('main', 'No TinyBase data found'); 59 + return; 60 + } 61 + 62 + // TinyBase stores data in the second column as JSON array [tables, values] 63 + const rawData = Object.values(tinybaseRow)[1]; 64 + if (!rawData) { 65 + console.log('main', 'TinyBase data is empty'); 66 + return; 67 + } 68 + 69 + const [tables] = JSON.parse(rawData); 70 + if (!tables) { 71 + console.log('main', 'No tables in TinyBase data'); 72 + return; 73 + } 74 + 75 + // Migrate each table 76 + const tablesToMigrate = ['addresses', 'visits', 'tags', 'address_tags', 'extension_settings', 'extensions', 'content', 'blobs', 'scripts_data', 'feeds']; 77 + 78 + for (const tableName of tablesToMigrate) { 79 + const tableData = tables[tableName]; 80 + if (!tableData || typeof tableData !== 'object') continue; 81 + 82 + const entries = Object.entries(tableData); 83 + if (entries.length === 0) continue; 84 + 85 + console.log('main', ` Migrating ${entries.length} rows from ${tableName}`); 86 + 87 + for (const [id, row] of entries) { 88 + try { 89 + const fullRow = { id, ...row }; 90 + const columns = Object.keys(fullRow); 91 + const placeholders = columns.map(() => '?').join(', '); 92 + const values = columns.map(col => fullRow[col]); 93 + 94 + db.prepare(`INSERT OR IGNORE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`).run(...values); 95 + } catch (err) { 96 + console.error('main', ` Error migrating row ${id} in ${tableName}:`, err.message); 97 + } 98 + } 99 + } 100 + 101 + // Drop the tinybase table after successful migration 102 + db.exec('DROP TABLE IF EXISTS tinybase'); 103 + console.log('main', 'TinyBase migration complete, removed tinybase table'); 104 + 105 + } catch (error) { 106 + console.error('main', 'TinyBase migration failed:', error.message); 107 + } 108 + }; 109 + 110 + /** 111 + * Get the database instance 112 + * @returns {Database|null} 113 + */ 114 + export const getDb = () => db; 115 + 116 + /** 117 + * Close the database connection 118 + */ 119 + export const closeDatabase = () => { 120 + if (db) { 121 + db.close(); 122 + db = null; 123 + console.log('main', 'database closed'); 124 + } 125 + }; 126 + 127 + // Helper functions 128 + 129 + /** 130 + * Generate a unique ID with optional prefix 131 + * @param {string} prefix - Prefix for the ID 132 + * @returns {string} 133 + */ 134 + export const generateId = (prefix = 'id') => { 135 + return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; 136 + }; 137 + 138 + /** 139 + * Get current timestamp in milliseconds 140 + * @returns {number} 141 + */ 142 + export const now = () => Date.now(); 143 + 144 + /** 145 + * Parse a URL into components 146 + * @param {string} uri - The URL to parse 147 + * @returns {{protocol: string, domain: string, path: string}} 148 + */ 149 + export const parseUrl = (uri) => { 150 + try { 151 + const url = new URL(uri); 152 + return { 153 + protocol: url.protocol.replace(':', ''), 154 + domain: url.hostname, 155 + path: url.pathname + url.search + url.hash 156 + }; 157 + } catch (e) { 158 + return { 159 + protocol: 'unknown', 160 + domain: uri, 161 + path: '' 162 + }; 163 + } 164 + }; 165 + 166 + /** 167 + * Normalize a URL for consistent storage 168 + * @param {string} uri - The URL to normalize 169 + * @returns {string} 170 + */ 171 + export const normalizeUrl = (uri) => { 172 + if (!uri) return uri; 173 + 174 + try { 175 + const url = new URL(uri); 176 + 177 + // Remove trailing slash from path (except for root) 178 + if (url.pathname !== '/' && url.pathname.endsWith('/')) { 179 + url.pathname = url.pathname.slice(0, -1); 180 + } 181 + 182 + // Remove default ports 183 + if ((url.protocol === 'http:' && url.port === '80') || 184 + (url.protocol === 'https:' && url.port === '443')) { 185 + url.port = ''; 186 + } 187 + 188 + // Sort query parameters for consistency 189 + if (url.search) { 190 + const params = new URLSearchParams(url.search); 191 + const sortedParams = new URLSearchParams([...params.entries()].sort()); 192 + url.search = sortedParams.toString(); 193 + } 194 + 195 + return url.toString(); 196 + } catch (e) { 197 + return uri; 198 + } 199 + }; 200 + 201 + /** 202 + * Check if a table name is valid 203 + * @param {string} tableName - The table name to validate 204 + * @returns {boolean} 205 + */ 206 + export const isValidTable = (tableName) => { 207 + return tableNames.includes(tableName); 208 + }; 209 + 210 + /** 211 + * Get all rows from a table as an object keyed by ID 212 + * @param {string} tableName - The table name 213 + * @returns {Object} 214 + */ 215 + export const getTableAsObject = (tableName) => { 216 + if (!isValidTable(tableName)) { 217 + throw new Error(`Invalid table name: ${tableName}`); 218 + } 219 + 220 + const rows = db.prepare(`SELECT * FROM ${tableName}`).all(); 221 + const result = {}; 222 + for (const row of rows) { 223 + result[row.id] = row; 224 + } 225 + return result; 226 + }; 227 + 228 + /** 229 + * Get a single row by ID 230 + * @param {string} tableName - The table name 231 + * @param {string} id - The row ID 232 + * @returns {Object|undefined} 233 + */ 234 + export const getRow = (tableName, id) => { 235 + if (!isValidTable(tableName)) { 236 + throw new Error(`Invalid table name: ${tableName}`); 237 + } 238 + 239 + return db.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).get(id); 240 + }; 241 + 242 + /** 243 + * Insert or replace a row 244 + * @param {string} tableName - The table name 245 + * @param {string} id - The row ID 246 + * @param {Object} data - The row data 247 + */ 248 + export const setRow = (tableName, id, data) => { 249 + if (!isValidTable(tableName)) { 250 + throw new Error(`Invalid table name: ${tableName}`); 251 + } 252 + 253 + const row = { id, ...data }; 254 + const columns = Object.keys(row); 255 + const placeholders = columns.map(() => '?').join(', '); 256 + const values = columns.map(col => row[col]); 257 + 258 + const sql = `INSERT OR REPLACE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`; 259 + db.prepare(sql).run(...values); 260 + }; 261 + 262 + /** 263 + * Delete a row by ID 264 + * @param {string} tableName - The table name 265 + * @param {string} id - The row ID 266 + */ 267 + export const deleteRow = (tableName, id) => { 268 + if (!isValidTable(tableName)) { 269 + throw new Error(`Invalid table name: ${tableName}`); 270 + } 271 + 272 + db.prepare(`DELETE FROM ${tableName} WHERE id = ?`).run(id); 273 + }; 274 + 275 + export { tableNames };
+224
app/datastore/schema-sql.js
··· 1 + // SQL Schema definitions for direct SQLite 2 + // Converted from TinyBase schema.js 3 + 4 + export const createTableStatements = ` 5 + -- Addresses: URLs with metadata 6 + CREATE TABLE IF NOT EXISTS addresses ( 7 + id TEXT PRIMARY KEY, 8 + uri TEXT NOT NULL, 9 + protocol TEXT DEFAULT 'https', 10 + domain TEXT, 11 + path TEXT DEFAULT '', 12 + title TEXT DEFAULT '', 13 + mimeType TEXT DEFAULT 'text/html', 14 + favicon TEXT DEFAULT '', 15 + description TEXT DEFAULT '', 16 + tags TEXT DEFAULT '', 17 + metadata TEXT DEFAULT '{}', 18 + createdAt INTEGER, 19 + updatedAt INTEGER, 20 + lastVisitAt INTEGER DEFAULT 0, 21 + visitCount INTEGER DEFAULT 0, 22 + starred INTEGER DEFAULT 0, 23 + archived INTEGER DEFAULT 0 24 + ); 25 + 26 + CREATE INDEX IF NOT EXISTS idx_addresses_uri ON addresses(uri); 27 + CREATE INDEX IF NOT EXISTS idx_addresses_domain ON addresses(domain); 28 + CREATE INDEX IF NOT EXISTS idx_addresses_protocol ON addresses(protocol); 29 + CREATE INDEX IF NOT EXISTS idx_addresses_lastVisitAt ON addresses(lastVisitAt); 30 + CREATE INDEX IF NOT EXISTS idx_addresses_visitCount ON addresses(visitCount); 31 + CREATE INDEX IF NOT EXISTS idx_addresses_starred ON addresses(starred); 32 + 33 + -- Visits: Navigation history linked to addresses 34 + CREATE TABLE IF NOT EXISTS visits ( 35 + id TEXT PRIMARY KEY, 36 + addressId TEXT, 37 + timestamp INTEGER, 38 + duration INTEGER DEFAULT 0, 39 + source TEXT DEFAULT 'direct', 40 + sourceId TEXT DEFAULT '', 41 + windowType TEXT DEFAULT 'main', 42 + metadata TEXT DEFAULT '{}', 43 + scrollDepth INTEGER DEFAULT 0, 44 + interacted INTEGER DEFAULT 0 45 + ); 46 + 47 + CREATE INDEX IF NOT EXISTS idx_visits_addressId ON visits(addressId); 48 + CREATE INDEX IF NOT EXISTS idx_visits_timestamp ON visits(timestamp); 49 + CREATE INDEX IF NOT EXISTS idx_visits_source ON visits(source); 50 + 51 + -- Content: User-created content (notes, etc.) 52 + CREATE TABLE IF NOT EXISTS content ( 53 + id TEXT PRIMARY KEY, 54 + title TEXT DEFAULT 'Untitled', 55 + content TEXT DEFAULT '', 56 + mimeType TEXT DEFAULT 'text/plain', 57 + contentType TEXT DEFAULT 'plain', 58 + language TEXT DEFAULT '', 59 + encoding TEXT DEFAULT 'utf-8', 60 + tags TEXT DEFAULT '', 61 + addressRefs TEXT DEFAULT '', 62 + parentId TEXT DEFAULT '', 63 + metadata TEXT DEFAULT '{}', 64 + createdAt INTEGER, 65 + updatedAt INTEGER, 66 + syncPath TEXT DEFAULT '', 67 + synced INTEGER DEFAULT 0, 68 + starred INTEGER DEFAULT 0, 69 + archived INTEGER DEFAULT 0 70 + ); 71 + 72 + CREATE INDEX IF NOT EXISTS idx_content_contentType ON content(contentType); 73 + CREATE INDEX IF NOT EXISTS idx_content_mimeType ON content(mimeType); 74 + CREATE INDEX IF NOT EXISTS idx_content_synced ON content(synced); 75 + CREATE INDEX IF NOT EXISTS idx_content_updatedAt ON content(updatedAt); 76 + 77 + -- Tags: Tag definitions with frecency tracking 78 + CREATE TABLE IF NOT EXISTS tags ( 79 + id TEXT PRIMARY KEY, 80 + name TEXT NOT NULL, 81 + slug TEXT, 82 + color TEXT DEFAULT '#999999', 83 + parentId TEXT DEFAULT '', 84 + description TEXT DEFAULT '', 85 + metadata TEXT DEFAULT '{}', 86 + createdAt INTEGER, 87 + updatedAt INTEGER, 88 + frequency INTEGER DEFAULT 0, 89 + lastUsedAt INTEGER DEFAULT 0, 90 + frecencyScore INTEGER DEFAULT 0 91 + ); 92 + 93 + CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name); 94 + CREATE INDEX IF NOT EXISTS idx_tags_slug ON tags(slug); 95 + CREATE INDEX IF NOT EXISTS idx_tags_parentId ON tags(parentId); 96 + CREATE INDEX IF NOT EXISTS idx_tags_frecencyScore ON tags(frecencyScore); 97 + 98 + -- Address-Tag join table 99 + CREATE TABLE IF NOT EXISTS address_tags ( 100 + id TEXT PRIMARY KEY, 101 + addressId TEXT NOT NULL, 102 + tagId TEXT NOT NULL, 103 + createdAt INTEGER 104 + ); 105 + 106 + CREATE INDEX IF NOT EXISTS idx_address_tags_addressId ON address_tags(addressId); 107 + CREATE INDEX IF NOT EXISTS idx_address_tags_tagId ON address_tags(tagId); 108 + CREATE UNIQUE INDEX IF NOT EXISTS idx_address_tags_unique ON address_tags(addressId, tagId); 109 + 110 + -- Blobs: Binary files/media 111 + CREATE TABLE IF NOT EXISTS blobs ( 112 + id TEXT PRIMARY KEY, 113 + filename TEXT, 114 + mimeType TEXT, 115 + mediaType TEXT, 116 + size INTEGER, 117 + hash TEXT, 118 + extension TEXT, 119 + path TEXT, 120 + addressId TEXT DEFAULT '', 121 + contentId TEXT DEFAULT '', 122 + tags TEXT DEFAULT '', 123 + metadata TEXT DEFAULT '{}', 124 + createdAt INTEGER, 125 + width INTEGER DEFAULT 0, 126 + height INTEGER DEFAULT 0, 127 + duration INTEGER DEFAULT 0, 128 + thumbnail TEXT DEFAULT '' 129 + ); 130 + 131 + CREATE INDEX IF NOT EXISTS idx_blobs_mediaType ON blobs(mediaType); 132 + CREATE INDEX IF NOT EXISTS idx_blobs_mimeType ON blobs(mimeType); 133 + CREATE INDEX IF NOT EXISTS idx_blobs_addressId ON blobs(addressId); 134 + CREATE INDEX IF NOT EXISTS idx_blobs_contentId ON blobs(contentId); 135 + 136 + -- Scripts data: Script execution results 137 + CREATE TABLE IF NOT EXISTS scripts_data ( 138 + id TEXT PRIMARY KEY, 139 + scriptId TEXT, 140 + scriptName TEXT, 141 + addressId TEXT, 142 + selector TEXT, 143 + content TEXT, 144 + contentType TEXT DEFAULT 'text', 145 + metadata TEXT DEFAULT '{}', 146 + extractedAt INTEGER, 147 + previousValue TEXT DEFAULT '', 148 + changed INTEGER DEFAULT 0 149 + ); 150 + 151 + CREATE INDEX IF NOT EXISTS idx_scripts_data_scriptId ON scripts_data(scriptId); 152 + CREATE INDEX IF NOT EXISTS idx_scripts_data_addressId ON scripts_data(addressId); 153 + CREATE INDEX IF NOT EXISTS idx_scripts_data_changed ON scripts_data(changed); 154 + 155 + -- Feeds: Feed definitions 156 + CREATE TABLE IF NOT EXISTS feeds ( 157 + id TEXT PRIMARY KEY, 158 + name TEXT, 159 + description TEXT DEFAULT '', 160 + type TEXT, 161 + query TEXT DEFAULT '', 162 + schedule TEXT DEFAULT '', 163 + source TEXT DEFAULT 'internal', 164 + tags TEXT DEFAULT '', 165 + metadata TEXT DEFAULT '{}', 166 + createdAt INTEGER, 167 + updatedAt INTEGER, 168 + lastFetchedAt INTEGER DEFAULT 0, 169 + enabled INTEGER DEFAULT 1 170 + ); 171 + 172 + CREATE INDEX IF NOT EXISTS idx_feeds_type ON feeds(type); 173 + CREATE INDEX IF NOT EXISTS idx_feeds_enabled ON feeds(enabled); 174 + 175 + -- Extensions: Extension registry 176 + CREATE TABLE IF NOT EXISTS extensions ( 177 + id TEXT PRIMARY KEY, 178 + name TEXT, 179 + description TEXT DEFAULT '', 180 + version TEXT DEFAULT '1.0.0', 181 + path TEXT, 182 + backgroundUrl TEXT DEFAULT '', 183 + settingsUrl TEXT DEFAULT '', 184 + iconPath TEXT DEFAULT '', 185 + builtin INTEGER DEFAULT 0, 186 + enabled INTEGER DEFAULT 1, 187 + status TEXT DEFAULT 'installed', 188 + installedAt INTEGER, 189 + updatedAt INTEGER, 190 + lastErrorAt INTEGER DEFAULT 0, 191 + lastError TEXT DEFAULT '', 192 + metadata TEXT DEFAULT '{}' 193 + ); 194 + 195 + CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON extensions(enabled); 196 + CREATE INDEX IF NOT EXISTS idx_extensions_status ON extensions(status); 197 + CREATE INDEX IF NOT EXISTS idx_extensions_builtin ON extensions(builtin); 198 + 199 + -- Extension settings: Key-value storage for extension preferences 200 + CREATE TABLE IF NOT EXISTS extension_settings ( 201 + id TEXT PRIMARY KEY, 202 + extensionId TEXT NOT NULL, 203 + key TEXT NOT NULL, 204 + value TEXT, 205 + updatedAt INTEGER 206 + ); 207 + 208 + CREATE INDEX IF NOT EXISTS idx_extension_settings_extensionId ON extension_settings(extensionId); 209 + CREATE UNIQUE INDEX IF NOT EXISTS idx_extension_settings_unique ON extension_settings(extensionId, key); 210 + `; 211 + 212 + // List of all tables for validation and iteration 213 + export const tableNames = [ 214 + 'addresses', 215 + 'visits', 216 + 'content', 217 + 'tags', 218 + 'address_tags', 219 + 'blobs', 220 + 'scripts_data', 221 + 'feeds', 222 + 'extensions', 223 + 'extension_settings' 224 + ];
+304 -471
index.js
··· 17 17 import fs from 'node:fs'; 18 18 import path from 'node:path'; 19 19 import { pathToFileURL } from 'url'; 20 - import { createStore, createIndexes, createRelationships, createMetrics } from 'tinybase'; 21 - import { createSqlite3Persister } from 'tinybase/persisters/persister-sqlite3'; 22 - import sqlite3 from 'sqlite3'; 23 - import { schema, indexes, relationships, metrics } from './app/datastore/schema.js'; 20 + import { 21 + initDatabase, 22 + closeDatabase, 23 + getDb, 24 + generateId, 25 + now, 26 + parseUrl, 27 + normalizeUrl, 28 + getTableAsObject, 29 + getRow, 30 + setRow, 31 + deleteRow, 32 + isValidTable, 33 + tableNames 34 + } from './app/datastore/db.js'; 24 35 import unhandled from 'electron-unhandled'; 25 36 26 37 // Catch unhandled errors and promise rejections without showing alert dialogs ··· 126 137 127 138 // ***** Datastore ***** 128 139 129 - let datastoreStore = null; 130 - let datastoreIndexes = null; 131 - let datastoreRelationships = null; 132 - let datastoreMetrics = null; 133 - let datastorePersister = null; 134 - 135 - // Initialize datastore with SQLite persistence 136 - let datastoreDb = null; // SQLite database instance 137 - 138 - const initDatastore = async (userDataPath) => { 139 - console.log('main', 'initializing datastore'); 140 - 141 - try { 142 - // Create the store with schema 143 - datastoreStore = createStore(); 144 - datastoreStore.setTablesSchema(schema); 145 - 146 - // Set up SQLite persistence 147 - const dbPath = path.join(userDataPath, 'datastore.sqlite'); 148 - console.log('main', 'datastore path:', dbPath); 149 - 150 - // Create SQLite database 151 - datastoreDb = new sqlite3.Database(dbPath); 152 - 153 - // Create persister with SQLite 154 - datastorePersister = createSqlite3Persister(datastoreStore, datastoreDb); 155 - 156 - // Load existing data 157 - await datastorePersister.load(); 158 - console.log('main', 'datastore loaded from SQLite'); 159 - 160 - // Start auto-save 161 - await datastorePersister.startAutoSave(); 162 - console.log('main', 'datastore auto-save enabled'); 163 - 164 - // Create indexes 165 - datastoreIndexes = createIndexes(datastoreStore); 166 - Object.entries(indexes).forEach(([indexName, indexConfig]) => { 167 - datastoreIndexes.setIndexDefinition( 168 - indexName, 169 - indexConfig.table, 170 - indexConfig.on 171 - ); 172 - }); 173 - 174 - // Create relationships 175 - datastoreRelationships = createRelationships(datastoreStore); 176 - Object.entries(relationships).forEach(([relName, relConfig]) => { 177 - datastoreRelationships.setRelationshipDefinition( 178 - relName, 179 - relConfig.localTableId, 180 - relConfig.remoteTableId, 181 - relConfig.relationshipId 182 - ); 183 - }); 184 - 185 - // Create metrics 186 - datastoreMetrics = createMetrics(datastoreStore); 187 - Object.entries(metrics).forEach(([metricName, metricConfig]) => { 188 - if (metricConfig.metric) { 189 - datastoreMetrics.setMetricDefinition( 190 - metricName, 191 - metricConfig.table, 192 - metricConfig.aggregate, 193 - metricConfig.metric 194 - ); 195 - } else { 196 - datastoreMetrics.setMetricDefinition( 197 - metricName, 198 - metricConfig.table, 199 - 'count' 200 - ); 201 - } 202 - }); 140 + // Database reference (set during init) 141 + let db = null; 203 142 204 - console.log('main', 'datastore initialized successfully'); 205 - return true; 206 - } catch (error) { 207 - console.error('main', 'datastore initialization failed:', error); 208 - return false; 209 - } 143 + const initDatastore = (userDataPath) => { 144 + const dbPath = path.join(userDataPath, 'datastore.sqlite'); 145 + db = initDatabase(dbPath); 146 + return db !== null; 210 147 }; 211 148 212 - // Helper functions for datastore operations 213 - const generateId = (prefix = 'id') => { 214 - return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; 215 - }; 216 - 217 - const now = () => Date.now(); 218 - 219 - const parseUrl = (uri) => { 220 - try { 221 - const url = new URL(uri); 222 - return { 223 - protocol: url.protocol.replace(':', ''), 224 - domain: url.hostname, 225 - path: url.pathname + url.search + url.hash 226 - }; 227 - } catch (error) { 228 - return { 229 - protocol: '', 230 - domain: '', 231 - path: uri 232 - }; 233 - } 234 - }; 235 - 236 - // Normalize URL by ensuring root paths have trailing slash 237 - // e.g., https://example.com -> https://example.com/ 238 - const normalizeUrl = (uri) => { 239 - try { 240 - const url = new URL(uri); 241 - // If pathname is empty, set it to / 242 - if (!url.pathname || url.pathname === '') { 243 - url.pathname = '/'; 244 - } 245 - return url.toString(); 246 - } catch (error) { 247 - return uri; 248 - } 149 + // Calculate frecency score: frequency * 10 * decay_factor 150 + // decay_factor = 1 / (1 + days_since_use / 7) 151 + const calculateFrecency = (frequency, lastUsedAt) => { 152 + const currentTime = Date.now(); 153 + const daysSinceUse = (currentTime - lastUsedAt) / (1000 * 60 * 60 * 24); 154 + const decayFactor = 1 / (1 + daysSinceUse / 7); 155 + return Math.round(frequency * 10 * decayFactor); 249 156 }; 250 157 251 158 // ***** Features / Strings ***** ··· 511 418 if (builtinPath) return builtinPath; 512 419 513 420 // Check datastore for external extensions 514 - if (datastoreStore) { 515 - const ext = datastoreStore.getRow('extensions', id); 421 + if (db) { 422 + const ext = db.prepare('SELECT * FROM extensions WHERE id = ?').get(id); 516 423 if (ext && ext.path) { 517 424 return ext.path; 518 425 } 519 426 520 427 // Also check by shortname (stored in metadata) 521 - const allExts = datastoreStore.getTable('extensions'); 522 - for (const [extId, extData] of Object.entries(allExts)) { 428 + const allExts = db.prepare('SELECT * FROM extensions').all(); 429 + for (const extData of allExts) { 523 430 try { 524 431 const metadata = JSON.parse(extData.metadata || '{}'); 525 432 if (metadata.shortname === id && extData.path) { ··· 713 620 // Check if enabled in extension_settings or extensions table 714 621 let enabled = true; // Default to enabled for builtins 715 622 716 - if (datastoreStore) { 623 + if (db) { 717 624 // Check extension_settings for enabled state 718 - const settingsTable = datastoreStore.getTable('extension_settings') || {}; 719 - for (const [rowId, row] of Object.entries(settingsTable)) { 720 - if (row.extensionId === extId && row.key === 'enabled') { 721 - try { 722 - enabled = JSON.parse(row.value) !== false; 723 - } catch (e) { 724 - enabled = true; 725 - } 625 + const setting = db.prepare('SELECT * FROM extension_settings WHERE extensionId = ? AND key = ?').get(extId, 'enabled'); 626 + if (setting) { 627 + try { 628 + enabled = JSON.parse(setting.value) !== false; 629 + } catch (e) { 630 + enabled = true; 726 631 } 727 632 } 728 633 } ··· 736 641 } 737 642 738 643 // Load external extensions from datastore 739 - if (datastoreStore) { 740 - const extensionsTable = datastoreStore.getTable('extensions') || {}; 741 - for (const [extId, extData] of Object.entries(extensionsTable)) { 644 + if (db) { 645 + const externalExts = db.prepare('SELECT * FROM extensions').all(); 646 + for (const extData of externalExts) { 647 + const extId = extData.id; 742 648 // Skip if already loaded (shouldn't happen but be safe) 743 649 if (extensionWindows.has(extId)) continue; 744 650 ··· 1641 1547 const addressId = generateId('addr'); 1642 1548 const timestamp = now(); 1643 1549 1644 - const row = { 1645 - uri: normalizedUri, 1646 - protocol: options.protocol || parsed.protocol, 1647 - domain: options.domain || parsed.domain, 1648 - path: options.path || parsed.path, 1649 - title: options.title || '', 1650 - mimeType: options.mimeType || 'text/html', 1651 - favicon: options.favicon || '', 1652 - description: options.description || '', 1653 - tags: options.tags || '', 1654 - metadata: options.metadata || '{}', 1655 - createdAt: timestamp, 1656 - updatedAt: timestamp, 1657 - lastVisitAt: options.lastVisitAt || 0, 1658 - visitCount: options.visitCount || 0, 1659 - starred: options.starred || 0, 1660 - archived: options.archived || 0 1661 - }; 1550 + const stmt = db.prepare(` 1551 + INSERT INTO addresses (id, uri, protocol, domain, path, title, mimeType, favicon, description, tags, metadata, createdAt, updatedAt, lastVisitAt, visitCount, starred, archived) 1552 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 1553 + `); 1554 + 1555 + stmt.run( 1556 + addressId, 1557 + normalizedUri, 1558 + options.protocol || parsed.protocol, 1559 + options.domain || parsed.domain, 1560 + options.path || parsed.path, 1561 + options.title || '', 1562 + options.mimeType || 'text/html', 1563 + options.favicon || '', 1564 + options.description || '', 1565 + options.tags || '', 1566 + options.metadata || '{}', 1567 + timestamp, 1568 + timestamp, 1569 + options.lastVisitAt || 0, 1570 + options.visitCount || 0, 1571 + options.starred || 0, 1572 + options.archived || 0 1573 + ); 1662 1574 1663 - datastoreStore.setRow('addresses', addressId, row); 1664 1575 return { success: true, id: addressId }; 1665 1576 } catch (error) { 1666 1577 console.error('datastore-add-address error:', error); ··· 1671 1582 ipcMain.handle('datastore-get-address', async (ev, data) => { 1672 1583 try { 1673 1584 const { id } = data; 1674 - const row = datastoreStore.getRow('addresses', id); 1675 - return { success: true, data: row }; 1585 + const row = db.prepare('SELECT * FROM addresses WHERE id = ?').get(id); 1586 + return { success: true, data: row || {} }; 1676 1587 } catch (error) { 1677 1588 console.error('datastore-get-address error:', error); 1678 1589 return { success: false, error: error.message }; ··· 1682 1593 ipcMain.handle('datastore-update-address', async (ev, data) => { 1683 1594 try { 1684 1595 const { id, updates } = data; 1685 - const existing = datastoreStore.getRow('addresses', id); 1596 + const existing = db.prepare('SELECT * FROM addresses WHERE id = ?').get(id); 1686 1597 if (!existing) { 1687 1598 return { success: false, error: 'Address not found' }; 1688 1599 } 1689 1600 1690 - const updated = { 1691 - ...existing, 1692 - ...updates, 1693 - updatedAt: now() 1694 - }; 1601 + const updated = { ...existing, ...updates, updatedAt: now() }; 1602 + const columns = Object.keys(updated).filter(k => k !== 'id'); 1603 + const setClause = columns.map(col => `${col} = ?`).join(', '); 1604 + const values = columns.map(col => updated[col]); 1695 1605 1696 - datastoreStore.setRow('addresses', id, updated); 1697 - return { success: true, data: updated }; 1606 + db.prepare(`UPDATE addresses SET ${setClause} WHERE id = ?`).run(...values, id); 1607 + return { success: true, data: { id, ...updated } }; 1698 1608 } catch (error) { 1699 1609 console.error('datastore-update-address error:', error); 1700 1610 return { success: false, error: error.message }; ··· 1704 1614 ipcMain.handle('datastore-query-addresses', async (ev, data) => { 1705 1615 try { 1706 1616 const { filter = {} } = data; 1707 - const table = datastoreStore.getTable('addresses'); 1708 - let results = Object.entries(table).map(([id, row]) => ({ id, ...row })); 1617 + 1618 + let sql = 'SELECT * FROM addresses WHERE 1=1'; 1619 + const params = []; 1709 1620 1710 - // Apply filters 1711 1621 if (filter.domain) { 1712 - results = results.filter(addr => addr.domain === filter.domain); 1622 + sql += ' AND domain = ?'; 1623 + params.push(filter.domain); 1713 1624 } 1714 1625 if (filter.protocol) { 1715 - results = results.filter(addr => addr.protocol === filter.protocol); 1626 + sql += ' AND protocol = ?'; 1627 + params.push(filter.protocol); 1716 1628 } 1717 1629 if (filter.starred !== undefined) { 1718 - results = results.filter(addr => addr.starred === filter.starred); 1630 + sql += ' AND starred = ?'; 1631 + params.push(filter.starred); 1719 1632 } 1720 1633 if (filter.tag) { 1721 - results = results.filter(addr => addr.tags.includes(filter.tag)); 1634 + sql += ' AND tags LIKE ?'; 1635 + params.push(`%${filter.tag}%`); 1722 1636 } 1723 1637 1724 1638 // Sort 1725 - if (filter.sortBy === 'lastVisit') { 1726 - results.sort((a, b) => b.lastVisitAt - a.lastVisitAt); 1727 - } else if (filter.sortBy === 'visitCount') { 1728 - results.sort((a, b) => b.visitCount - a.visitCount); 1729 - } else if (filter.sortBy === 'created') { 1730 - results.sort((a, b) => b.createdAt - a.createdAt); 1731 - } 1639 + const sortMap = { 1640 + lastVisit: 'lastVisitAt DESC', 1641 + visitCount: 'visitCount DESC', 1642 + created: 'createdAt DESC' 1643 + }; 1644 + sql += ` ORDER BY ${sortMap[filter.sortBy] || 'updatedAt DESC'}`; 1732 1645 1733 1646 // Limit 1734 1647 if (filter.limit) { 1735 - results = results.slice(0, filter.limit); 1648 + sql += ' LIMIT ?'; 1649 + params.push(filter.limit); 1736 1650 } 1737 1651 1652 + const results = db.prepare(sql).all(...params); 1738 1653 return { success: true, data: results }; 1739 1654 } catch (error) { 1740 1655 console.error('datastore-query-addresses error:', error); ··· 1748 1663 const visitId = generateId('visit'); 1749 1664 const timestamp = now(); 1750 1665 1751 - const row = { 1666 + db.prepare(` 1667 + INSERT INTO visits (id, addressId, timestamp, duration, source, sourceId, windowType, metadata, scrollDepth, interacted) 1668 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 1669 + `).run( 1670 + visitId, 1752 1671 addressId, 1753 - timestamp: options.timestamp || timestamp, 1754 - duration: options.duration || 0, 1755 - source: options.source || 'direct', 1756 - sourceId: options.sourceId || '', 1757 - windowType: options.windowType || 'main', 1758 - metadata: options.metadata || '{}', 1759 - scrollDepth: options.scrollDepth || 0, 1760 - interacted: options.interacted || 0 1761 - }; 1762 - 1763 - datastoreStore.setRow('visits', visitId, row); 1672 + options.timestamp || timestamp, 1673 + options.duration || 0, 1674 + options.source || 'direct', 1675 + options.sourceId || '', 1676 + options.windowType || 'main', 1677 + options.metadata || '{}', 1678 + options.scrollDepth || 0, 1679 + options.interacted || 0 1680 + ); 1764 1681 1765 1682 // Update address visit stats 1766 - const address = datastoreStore.getRow('addresses', addressId); 1767 - if (address) { 1768 - const updated = { 1769 - ...address, 1770 - lastVisitAt: timestamp, 1771 - visitCount: address.visitCount + 1, 1772 - updatedAt: timestamp 1773 - }; 1774 - datastoreStore.setRow('addresses', addressId, updated); 1775 - } 1683 + db.prepare(` 1684 + UPDATE addresses SET lastVisitAt = ?, visitCount = visitCount + 1, updatedAt = ? 1685 + WHERE id = ? 1686 + `).run(timestamp, timestamp, addressId); 1776 1687 1777 1688 return { success: true, id: visitId }; 1778 1689 } catch (error) { ··· 1784 1695 ipcMain.handle('datastore-query-visits', async (ev, data) => { 1785 1696 try { 1786 1697 const { filter = {} } = data; 1787 - const table = datastoreStore.getTable('visits'); 1788 - let results = Object.entries(table).map(([id, row]) => ({ id, ...row })); 1789 1698 1790 - // Apply filters 1699 + let sql = 'SELECT * FROM visits WHERE 1=1'; 1700 + const params = []; 1701 + 1791 1702 if (filter.addressId) { 1792 - results = results.filter(visit => visit.addressId === filter.addressId); 1703 + sql += ' AND addressId = ?'; 1704 + params.push(filter.addressId); 1793 1705 } 1794 1706 if (filter.source) { 1795 - results = results.filter(visit => visit.source === filter.source); 1707 + sql += ' AND source = ?'; 1708 + params.push(filter.source); 1796 1709 } 1797 1710 if (filter.since) { 1798 1711 const since = typeof filter.since === 'number' ? filter.since : now() - filter.since; 1799 - results = results.filter(visit => visit.timestamp >= since); 1712 + sql += ' AND timestamp >= ?'; 1713 + params.push(since); 1800 1714 } 1801 1715 1802 - // Sort by timestamp (most recent first) 1803 - results.sort((a, b) => b.timestamp - a.timestamp); 1716 + sql += ' ORDER BY timestamp DESC'; 1804 1717 1805 - // Limit 1806 1718 if (filter.limit) { 1807 - results = results.slice(0, filter.limit); 1719 + sql += ' LIMIT ?'; 1720 + params.push(filter.limit); 1808 1721 } 1809 1722 1723 + const results = db.prepare(sql).all(...params); 1810 1724 return { success: true, data: results }; 1811 1725 } catch (error) { 1812 1726 console.error('datastore-query-visits error:', error); ··· 1820 1734 const contentId = generateId('content'); 1821 1735 const timestamp = now(); 1822 1736 1823 - const row = { 1824 - title: options.title || 'Untitled', 1825 - content: options.content || '', 1826 - mimeType: options.mimeType || 'text/plain', 1827 - contentType: options.contentType || 'plain', 1828 - language: options.language || '', 1829 - encoding: options.encoding || 'utf-8', 1830 - tags: options.tags || '', 1831 - addressRefs: options.addressRefs || '', 1832 - parentId: options.parentId || '', 1833 - metadata: options.metadata || '{}', 1834 - createdAt: timestamp, 1835 - updatedAt: timestamp, 1836 - syncPath: options.syncPath || '', 1837 - synced: options.synced || 0, 1838 - starred: options.starred || 0, 1839 - archived: options.archived || 0 1840 - }; 1841 - 1842 - datastoreStore.setRow('content', contentId, row); 1737 + db.prepare(` 1738 + INSERT INTO content (id, title, content, mimeType, contentType, language, encoding, tags, addressRefs, parentId, metadata, createdAt, updatedAt, syncPath, synced, starred, archived) 1739 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 1740 + `).run( 1741 + contentId, 1742 + options.title || 'Untitled', 1743 + options.content || '', 1744 + options.mimeType || 'text/plain', 1745 + options.contentType || 'plain', 1746 + options.language || '', 1747 + options.encoding || 'utf-8', 1748 + options.tags || '', 1749 + options.addressRefs || '', 1750 + options.parentId || '', 1751 + options.metadata || '{}', 1752 + timestamp, 1753 + timestamp, 1754 + options.syncPath || '', 1755 + options.synced || 0, 1756 + options.starred || 0, 1757 + options.archived || 0 1758 + ); 1843 1759 return { success: true, id: contentId }; 1844 1760 } catch (error) { 1845 1761 console.error('datastore-add-content error:', error); ··· 1850 1766 ipcMain.handle('datastore-query-content', async (ev, data) => { 1851 1767 try { 1852 1768 const { filter = {} } = data; 1853 - const table = datastoreStore.getTable('content'); 1854 - let results = Object.entries(table).map(([id, row]) => ({ id, ...row })); 1855 1769 1856 - // Apply filters 1770 + let sql = 'SELECT * FROM content WHERE 1=1'; 1771 + const params = []; 1772 + 1857 1773 if (filter.contentType) { 1858 - results = results.filter(item => item.contentType === filter.contentType); 1774 + sql += ' AND contentType = ?'; 1775 + params.push(filter.contentType); 1859 1776 } 1860 1777 if (filter.mimeType) { 1861 - results = results.filter(item => item.mimeType === filter.mimeType); 1778 + sql += ' AND mimeType = ?'; 1779 + params.push(filter.mimeType); 1862 1780 } 1863 1781 if (filter.synced !== undefined) { 1864 - results = results.filter(item => item.synced === filter.synced); 1782 + sql += ' AND synced = ?'; 1783 + params.push(filter.synced); 1865 1784 } 1866 1785 if (filter.starred !== undefined) { 1867 - results = results.filter(item => item.starred === filter.starred); 1786 + sql += ' AND starred = ?'; 1787 + params.push(filter.starred); 1868 1788 } 1869 1789 if (filter.tag) { 1870 - results = results.filter(item => item.tags && item.tags.includes(filter.tag)); 1790 + sql += ' AND tags LIKE ?'; 1791 + params.push(`%${filter.tag}%`); 1871 1792 } 1872 1793 1873 1794 // Sort 1874 - if (filter.sortBy === 'updated') { 1875 - results.sort((a, b) => b.updatedAt - a.updatedAt); 1876 - } else if (filter.sortBy === 'created') { 1877 - results.sort((a, b) => b.createdAt - a.createdAt); 1878 - } 1795 + const sortMap = { 1796 + updated: 'updatedAt DESC', 1797 + created: 'createdAt DESC' 1798 + }; 1799 + sql += ` ORDER BY ${sortMap[filter.sortBy] || 'updatedAt DESC'}`; 1879 1800 1880 - // Limit 1881 1801 if (filter.limit) { 1882 - results = results.slice(0, filter.limit); 1802 + sql += ' LIMIT ?'; 1803 + params.push(filter.limit); 1883 1804 } 1884 1805 1806 + const results = db.prepare(sql).all(...params); 1885 1807 return { success: true, data: results }; 1886 1808 } catch (error) { 1887 1809 console.error('datastore-query-content error:', error); ··· 1892 1814 ipcMain.handle('datastore-get-table', async (ev, data) => { 1893 1815 try { 1894 1816 const { tableName } = data; 1895 - const table = datastoreStore.getTable(tableName); 1817 + if (!isValidTable(tableName)) { 1818 + return { success: false, error: `Invalid table name: ${tableName}` }; 1819 + } 1820 + const rows = db.prepare(`SELECT * FROM ${tableName}`).all(); 1821 + // Convert to object keyed by id for compatibility 1822 + const table = {}; 1823 + for (const row of rows) { 1824 + table[row.id] = row; 1825 + } 1896 1826 return { success: true, data: table }; 1897 1827 } catch (error) { 1898 1828 console.error('datastore-get-table error:', error); ··· 1903 1833 ipcMain.handle('datastore-set-row', async (ev, data) => { 1904 1834 try { 1905 1835 const { tableName, rowId, rowData } = data; 1906 - datastoreStore.setRow(tableName, rowId, rowData); 1836 + if (!isValidTable(tableName)) { 1837 + return { success: false, error: `Invalid table name: ${tableName}` }; 1838 + } 1839 + const row = { id: rowId, ...rowData }; 1840 + const columns = Object.keys(row); 1841 + const placeholders = columns.map(() => '?').join(', '); 1842 + const values = columns.map(col => row[col]); 1843 + 1844 + db.prepare(`INSERT OR REPLACE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`).run(...values); 1907 1845 return { success: true }; 1908 1846 } catch (error) { 1909 1847 console.error('datastore-set-row error:', error); ··· 1914 1852 ipcMain.handle('datastore-get-stats', async () => { 1915 1853 try { 1916 1854 const stats = { 1917 - totalAddresses: datastoreMetrics.getMetric('totalAddresses'), 1918 - totalVisits: datastoreMetrics.getMetric('totalVisits'), 1919 - avgVisitDuration: datastoreMetrics.getMetric('avgVisitDuration'), 1920 - totalContent: datastoreMetrics.getMetric('totalContent'), 1921 - syncedContent: datastoreMetrics.getMetric('syncedContent') 1855 + totalAddresses: db.prepare('SELECT COUNT(*) as count FROM addresses').get().count, 1856 + totalVisits: db.prepare('SELECT COUNT(*) as count FROM visits').get().count, 1857 + avgVisitDuration: db.prepare('SELECT AVG(duration) as avg FROM visits').get().avg || 0, 1858 + totalContent: db.prepare('SELECT COUNT(*) as count FROM content').get().count, 1859 + syncedContent: db.prepare('SELECT COUNT(*) as count FROM content WHERE synced = 1').get().count 1922 1860 }; 1923 1861 return { success: true, data: stats }; 1924 1862 } catch (error) { ··· 1929 1867 1930 1868 // ***** Tag IPC Handlers ***** 1931 1869 1932 - // Calculate frecency score: frequency * 10 * decay_factor 1933 - // decay_factor = 1 / (1 + days_since_use / 7) 1934 - const calculateFrecency = (frequency, lastUsedAt) => { 1935 - const currentTime = Date.now(); 1936 - const daysSinceUse = (currentTime - lastUsedAt) / (1000 * 60 * 60 * 24); 1937 - const decayFactor = 1 / (1 + daysSinceUse / 7); 1938 - return frequency * 10 * decayFactor; 1939 - }; 1940 - 1941 1870 // Get or create a tag by name 1942 1871 ipcMain.handle('datastore-get-or-create-tag', async (ev, data) => { 1943 1872 try { ··· 1946 1875 const slug = name.toLowerCase().trim().replace(/\s+/g, '-'); 1947 1876 const timestamp = now(); 1948 1877 1949 - // Look for existing tag by name 1950 - const tagsTable = datastoreStore.getTable('tags'); 1951 - let existingTag = null; 1952 - let existingTagId = null; 1953 - 1954 - for (const [id, tag] of Object.entries(tagsTable)) { 1955 - if (tag.name.toLowerCase() === name.toLowerCase()) { 1956 - existingTag = tag; 1957 - existingTagId = id; 1958 - break; 1959 - } 1960 - } 1878 + // Look for existing tag by name (case-insensitive) 1879 + const existingTag = db.prepare('SELECT * FROM tags WHERE LOWER(name) = LOWER(?)').get(name); 1961 1880 1962 1881 if (existingTag) { 1963 - console.log(' -> found existing tag:', existingTagId); 1964 - return { success: true, data: { id: existingTagId, ...existingTag }, created: false }; 1882 + console.log(' -> found existing tag:', existingTag.id); 1883 + return { success: true, data: existingTag, created: false }; 1965 1884 } 1966 1885 1967 1886 // Create new tag 1968 1887 const tagId = generateId('tag'); 1969 - const newTag = { 1970 - name: name.trim(), 1971 - slug, 1972 - color: '#999999', 1973 - parentId: '', 1974 - description: '', 1975 - metadata: '{}', 1976 - createdAt: timestamp, 1977 - updatedAt: timestamp, 1978 - frequency: 0, 1979 - lastUsedAt: 0, 1980 - frecencyScore: 0 1981 - }; 1888 + db.prepare(` 1889 + INSERT INTO tags (id, name, slug, color, parentId, description, metadata, createdAt, updatedAt, frequency, lastUsedAt, frecencyScore) 1890 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 1891 + `).run(tagId, name.trim(), slug, '#999999', '', '', '{}', timestamp, timestamp, 0, 0, 0); 1982 1892 1983 - datastoreStore.setRow('tags', tagId, newTag); 1893 + const newTag = db.prepare('SELECT * FROM tags WHERE id = ?').get(tagId); 1984 1894 console.log(' -> created new tag:', tagId); 1985 - return { success: true, data: { id: tagId, ...newTag }, created: true }; 1895 + return { success: true, data: newTag, created: true }; 1986 1896 } catch (error) { 1987 1897 console.error('datastore-get-or-create-tag error:', error); 1988 1898 return { success: false, error: error.message }; ··· 1997 1907 const timestamp = now(); 1998 1908 1999 1909 // Check if link already exists 2000 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2001 - for (const [id, link] of Object.entries(addressTagsTable)) { 2002 - if (link.addressId === addressId && link.tagId === tagId) { 2003 - return { success: true, data: { id, ...link }, alreadyExists: true }; 2004 - } 1910 + const existingLink = db.prepare('SELECT * FROM address_tags WHERE addressId = ? AND tagId = ?').get(addressId, tagId); 1911 + if (existingLink) { 1912 + return { success: true, data: existingLink, alreadyExists: true }; 2005 1913 } 2006 1914 2007 1915 // Create the link 2008 1916 const linkId = generateId('address_tag'); 2009 - const newLink = { 2010 - addressId, 2011 - tagId, 2012 - createdAt: timestamp 2013 - }; 2014 - datastoreStore.setRow('address_tags', linkId, newLink); 1917 + db.prepare('INSERT INTO address_tags (id, addressId, tagId, createdAt) VALUES (?, ?, ?, ?)').run(linkId, addressId, tagId, timestamp); 2015 1918 2016 1919 // Update tag frequency and frecency 2017 - const tag = datastoreStore.getRow('tags', tagId); 1920 + const tag = db.prepare('SELECT * FROM tags WHERE id = ?').get(tagId); 2018 1921 if (tag) { 2019 1922 const newFrequency = (tag.frequency || 0) + 1; 2020 1923 const frecencyScore = calculateFrecency(newFrequency, timestamp); 2021 - datastoreStore.setRow('tags', tagId, { 2022 - ...tag, 2023 - frequency: newFrequency, 2024 - lastUsedAt: timestamp, 2025 - frecencyScore, 2026 - updatedAt: timestamp 2027 - }); 1924 + db.prepare('UPDATE tags SET frequency = ?, lastUsedAt = ?, frecencyScore = ?, updatedAt = ? WHERE id = ?') 1925 + .run(newFrequency, timestamp, frecencyScore, timestamp, tagId); 2028 1926 } 2029 1927 2030 - return { success: true, data: { id: linkId, ...newLink } }; 1928 + const newLink = db.prepare('SELECT * FROM address_tags WHERE id = ?').get(linkId); 1929 + return { success: true, data: newLink }; 2031 1930 } catch (error) { 2032 1931 console.error('datastore-tag-address error:', error); 2033 1932 return { success: false, error: error.message }; ··· 2039 1938 try { 2040 1939 const { addressId, tagId } = data; 2041 1940 2042 - // Find and remove the link 2043 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2044 - for (const [id, link] of Object.entries(addressTagsTable)) { 2045 - if (link.addressId === addressId && link.tagId === tagId) { 2046 - datastoreStore.delRow('address_tags', id); 2047 - return { success: true, removed: true }; 2048 - } 2049 - } 2050 - 2051 - return { success: true, removed: false }; 1941 + const result = db.prepare('DELETE FROM address_tags WHERE addressId = ? AND tagId = ?').run(addressId, tagId); 1942 + return { success: true, removed: result.changes > 0 }; 2052 1943 } catch (error) { 2053 1944 console.error('datastore-untag-address error:', error); 2054 1945 return { success: false, error: error.message }; ··· 2059 1950 ipcMain.handle('datastore-get-tags-by-frecency', async (ev, data = {}) => { 2060 1951 try { 2061 1952 const { domain } = data || {}; 2062 - const tagsTable = datastoreStore.getTable('tags'); 2063 - console.log('datastore-get-tags-by-frecency: tagsTable has', Object.keys(tagsTable).length, 'tags'); 2064 - let tags = Object.entries(tagsTable).map(([id, tag]) => ({ id, ...tag })); 1953 + let tags = db.prepare('SELECT * FROM tags').all(); 1954 + console.log('datastore-get-tags-by-frecency: tags table has', tags.length, 'tags'); 2065 1955 2066 1956 // Recalculate frecency scores (they decay over time) 2067 1957 tags = tags.map(tag => ({ ··· 2071 1961 2072 1962 // If domain provided, boost tags used on same-domain addresses 2073 1963 if (domain) { 2074 - const addressesTable = datastoreStore.getTable('addresses'); 2075 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2076 - 2077 - // Find addresses with matching domain 2078 - const domainAddressIds = new Set(); 2079 - for (const [id, addr] of Object.entries(addressesTable)) { 2080 - if (addr.domain === domain) { 2081 - domainAddressIds.add(id); 2082 - } 2083 - } 2084 - 2085 - // Find tags used on those addresses 2086 - const domainTagIds = new Set(); 2087 - for (const [, link] of Object.entries(addressTagsTable)) { 2088 - if (domainAddressIds.has(link.addressId)) { 2089 - domainTagIds.add(link.tagId); 2090 - } 2091 - } 1964 + // Find tag IDs used on addresses with matching domain using JOIN 1965 + const domainTagIds = new Set( 1966 + db.prepare(` 1967 + SELECT DISTINCT at.tagId FROM address_tags at 1968 + JOIN addresses a ON at.addressId = a.id 1969 + WHERE a.domain = ? 1970 + `).all(domain).map(row => row.tagId) 1971 + ); 2092 1972 2093 1973 // Apply 2x boost 2094 1974 tags = tags.map(tag => ({ ··· 2111 1991 ipcMain.handle('datastore-get-address-tags', async (ev, data) => { 2112 1992 try { 2113 1993 const { addressId } = data; 2114 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2115 - const tagsTable = datastoreStore.getTable('tags'); 2116 1994 2117 - const tagIds = []; 2118 - for (const [, link] of Object.entries(addressTagsTable)) { 2119 - if (link.addressId === addressId) { 2120 - tagIds.push(link.tagId); 2121 - } 2122 - } 2123 - 2124 - const tags = tagIds 2125 - .map(tagId => { 2126 - const tag = tagsTable[tagId]; 2127 - return tag ? { id: tagId, ...tag } : null; 2128 - }) 2129 - .filter(Boolean); 1995 + // Use JOIN to get tags directly 1996 + const tags = db.prepare(` 1997 + SELECT t.* FROM tags t 1998 + JOIN address_tags at ON t.id = at.tagId 1999 + WHERE at.addressId = ? 2000 + `).all(addressId); 2130 2001 2131 2002 return { success: true, data: tags }; 2132 2003 } catch (error) { ··· 2139 2010 ipcMain.handle('datastore-get-addresses-by-tag', async (ev, data) => { 2140 2011 try { 2141 2012 const { tagId } = data; 2142 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2143 - const addressesTable = datastoreStore.getTable('addresses'); 2144 2013 2145 - const addressIds = []; 2146 - for (const [, link] of Object.entries(addressTagsTable)) { 2147 - if (link.tagId === tagId) { 2148 - addressIds.push(link.addressId); 2149 - } 2150 - } 2151 - 2152 - const addresses = addressIds 2153 - .map(addressId => { 2154 - const addr = addressesTable[addressId]; 2155 - return addr ? { id: addressId, ...addr } : null; 2156 - }) 2157 - .filter(Boolean); 2014 + // Use JOIN to get addresses directly 2015 + const addresses = db.prepare(` 2016 + SELECT a.* FROM addresses a 2017 + JOIN address_tags at ON a.id = at.addressId 2018 + WHERE at.tagId = ? 2019 + `).all(tagId); 2158 2020 2159 2021 return { success: true, data: addresses }; 2160 2022 } catch (error) { ··· 2166 2028 // Get addresses that have no tags 2167 2029 ipcMain.handle('datastore-get-untagged-addresses', async (ev, data) => { 2168 2030 try { 2169 - const addressTagsTable = datastoreStore.getTable('address_tags'); 2170 - const addressesTable = datastoreStore.getTable('addresses'); 2171 - 2172 - // Get all address IDs that have at least one tag 2173 - const taggedAddressIds = new Set(); 2174 - for (const [, link] of Object.entries(addressTagsTable)) { 2175 - taggedAddressIds.add(link.addressId); 2176 - } 2177 - 2178 - // Get addresses that are NOT in the tagged set 2179 - const untaggedAddresses = []; 2180 - for (const [id, addr] of Object.entries(addressesTable)) { 2181 - if (!taggedAddressIds.has(id)) { 2182 - untaggedAddresses.push({ id, ...addr }); 2183 - } 2184 - } 2185 - 2186 - // Sort by visitCount descending 2187 - untaggedAddresses.sort((a, b) => (b.visitCount || 0) - (a.visitCount || 0)); 2031 + // Use LEFT JOIN + NULL check to find untagged addresses 2032 + const addresses = db.prepare(` 2033 + SELECT a.* FROM addresses a 2034 + LEFT JOIN address_tags at ON a.id = at.addressId 2035 + WHERE at.id IS NULL 2036 + ORDER BY a.visitCount DESC 2037 + `).all(); 2188 2038 2189 - return { success: true, data: untaggedAddresses }; 2039 + return { success: true, data: addresses }; 2190 2040 } catch (error) { 2191 2041 console.error('datastore-get-untagged-addresses error:', error); 2192 2042 return { success: false, error: error.message }; ··· 2281 2131 const { folderPath, manifest, enabled = false } = data; 2282 2132 2283 2133 try { 2284 - const now = Date.now(); 2285 - const id = manifest?.id || `ext-${now}`; 2134 + const timestamp = now(); 2135 + const id = manifest?.id || `ext-${timestamp}`; 2286 2136 2287 2137 // Check if extension with this ID already exists 2288 - const existing = datastoreStore.getRow('extensions', id); 2289 - if (existing && Object.keys(existing).length > 0) { 2138 + const existing = db.prepare('SELECT * FROM extensions WHERE id = ?').get(id); 2139 + if (existing) { 2290 2140 return { success: false, error: `Extension with ID '${id}' already exists` }; 2291 2141 } 2292 2142 2293 2143 // Add to extensions table 2294 - datastoreStore.setRow('extensions', id, { 2295 - name: manifest?.name || path.basename(folderPath), 2296 - description: manifest?.description || '', 2297 - version: manifest?.version || '0.0.0', 2298 - path: folderPath, 2299 - backgroundUrl: `peek://ext/${manifest?.shortname || id}/background.js`, 2300 - settingsUrl: manifest?.settings_url || '', 2301 - iconPath: manifest?.icon || '', 2302 - builtin: 0, 2303 - enabled: enabled ? 1 : 0, 2304 - status: enabled ? 'installed' : 'disabled', 2305 - installedAt: now, 2306 - updatedAt: now, 2307 - lastErrorAt: 0, 2308 - lastError: '', 2309 - metadata: JSON.stringify({ shortname: manifest?.shortname || id }) 2310 - }); 2144 + db.prepare(` 2145 + INSERT INTO extensions (id, name, description, version, path, backgroundUrl, settingsUrl, iconPath, builtin, enabled, status, installedAt, updatedAt, lastErrorAt, lastError, metadata) 2146 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 2147 + `).run( 2148 + id, 2149 + manifest?.name || path.basename(folderPath), 2150 + manifest?.description || '', 2151 + manifest?.version || '0.0.0', 2152 + folderPath, 2153 + `peek://ext/${manifest?.shortname || id}/background.js`, 2154 + manifest?.settings_url || '', 2155 + manifest?.icon || '', 2156 + 0, 2157 + enabled ? 1 : 0, 2158 + enabled ? 'installed' : 'disabled', 2159 + timestamp, 2160 + timestamp, 2161 + 0, 2162 + '', 2163 + JSON.stringify({ shortname: manifest?.shortname || id }) 2164 + ); 2311 2165 2312 2166 console.log(`Extension added: ${id} at ${folderPath}`); 2313 2167 return { success: true, data: { id } }; ··· 2322 2176 const { id } = data; 2323 2177 2324 2178 try { 2325 - const existing = datastoreStore.getRow('extensions', id); 2326 - if (!existing || Object.keys(existing).length === 0) { 2179 + const existing = db.prepare('SELECT * FROM extensions WHERE id = ?').get(id); 2180 + if (!existing) { 2327 2181 return { success: false, error: `Extension '${id}' not found` }; 2328 2182 } 2329 2183 ··· 2332 2186 return { success: false, error: 'Cannot remove built-in extensions' }; 2333 2187 } 2334 2188 2335 - datastoreStore.delRow('extensions', id); 2189 + db.prepare('DELETE FROM extensions WHERE id = ?').run(id); 2336 2190 console.log(`Extension removed: ${id}`); 2337 2191 return { success: true }; 2338 2192 } catch (error) { ··· 2346 2200 const { id, updates } = data; 2347 2201 2348 2202 try { 2349 - const existing = datastoreStore.getRow('extensions', id); 2350 - if (!existing || Object.keys(existing).length === 0) { 2203 + const existing = db.prepare('SELECT * FROM extensions WHERE id = ?').get(id); 2204 + if (!existing) { 2351 2205 return { success: false, error: `Extension '${id}' not found` }; 2352 2206 } 2353 2207 2354 2208 // Apply updates 2355 - const updatedRow = { ...existing, ...updates, updatedAt: Date.now() }; 2356 - datastoreStore.setRow('extensions', id, updatedRow); 2209 + const updatedRow = { ...existing, ...updates, updatedAt: now() }; 2210 + const columns = Object.keys(updatedRow).filter(k => k !== 'id'); 2211 + const setClause = columns.map(col => `${col} = ?`).join(', '); 2212 + const values = columns.map(col => updatedRow[col]); 2213 + 2214 + db.prepare(`UPDATE extensions SET ${setClause} WHERE id = ?`).run(...values, id); 2357 2215 2358 2216 console.log(`Extension updated: ${id}`, updates); 2359 - return { success: true, data: updatedRow }; 2217 + return { success: true, data: { id, ...updatedRow } }; 2360 2218 } catch (error) { 2361 2219 console.error('extension-update error:', error); 2362 2220 return { success: false, error: error.message }; ··· 2366 2224 // Get all extensions from datastore 2367 2225 ipcMain.handle('extension-get-all', async (ev) => { 2368 2226 try { 2369 - const table = datastoreStore.getTable('extensions'); 2370 - const extensions = Object.entries(table).map(([id, row]) => ({ id, ...row })); 2227 + const extensions = db.prepare('SELECT * FROM extensions').all(); 2371 2228 return { success: true, data: extensions }; 2372 2229 } catch (error) { 2373 2230 console.error('extension-get-all error:', error); ··· 2380 2237 const { id } = data; 2381 2238 2382 2239 try { 2383 - const row = datastoreStore.getRow('extensions', id); 2384 - if (!row || Object.keys(row).length === 0) { 2240 + const row = db.prepare('SELECT * FROM extensions WHERE id = ?').get(id); 2241 + if (!row) { 2385 2242 return { success: false, error: `Extension '${id}' not found` }; 2386 2243 } 2387 - return { success: true, data: { id, ...row } }; 2244 + return { success: true, data: row }; 2388 2245 } catch (error) { 2389 2246 console.error('extension-get error:', error); 2390 2247 return { success: false, error: error.message }; ··· 2482 2339 const { extId } = data; 2483 2340 2484 2341 try { 2485 - const table = datastoreStore.getTable('extension_settings') || {}; 2342 + const rows = db.prepare('SELECT * FROM extension_settings WHERE extensionId = ?').all(extId); 2486 2343 const settings = {}; 2487 2344 2488 - for (const [rowId, row] of Object.entries(table)) { 2489 - if (row.extensionId === extId) { 2490 - try { 2491 - settings[row.key] = JSON.parse(row.value); 2492 - } catch (e) { 2493 - settings[row.key] = row.value; 2494 - } 2345 + for (const row of rows) { 2346 + try { 2347 + settings[row.key] = JSON.parse(row.value); 2348 + } catch (e) { 2349 + settings[row.key] = row.value; 2495 2350 } 2496 2351 } 2497 2352 ··· 2507 2362 const { extId, settings } = data; 2508 2363 2509 2364 try { 2510 - const now = Date.now(); 2365 + const timestamp = now(); 2511 2366 2512 2367 for (const [key, value] of Object.entries(settings)) { 2513 2368 const rowId = `${extId}:${key}`; 2514 - datastoreStore.setRow('extension_settings', rowId, { 2515 - extensionId: extId, 2516 - key, 2517 - value: JSON.stringify(value), 2518 - updatedAt: now 2519 - }); 2369 + db.prepare(` 2370 + INSERT OR REPLACE INTO extension_settings (id, extensionId, key, value, updatedAt) 2371 + VALUES (?, ?, ?, ?, ?) 2372 + `).run(rowId, extId, key, JSON.stringify(value), timestamp); 2520 2373 } 2521 2374 2522 2375 return { success: true }; ··· 2531 2384 const { extId, key } = data; 2532 2385 2533 2386 try { 2534 - const rowId = `${extId}:${key}`; 2535 - const row = datastoreStore.getRow('extension_settings', rowId); 2387 + const row = db.prepare('SELECT * FROM extension_settings WHERE extensionId = ? AND key = ?').get(extId, key); 2536 2388 2537 - if (!row || Object.keys(row).length === 0) { 2389 + if (!row) { 2538 2390 return { success: true, data: null }; 2539 2391 } 2540 2392 ··· 2555 2407 2556 2408 try { 2557 2409 const rowId = `${extId}:${key}`; 2558 - datastoreStore.setRow('extension_settings', rowId, { 2559 - extensionId: extId, 2560 - key, 2561 - value: JSON.stringify(value), 2562 - updatedAt: Date.now() 2563 - }); 2410 + db.prepare(` 2411 + INSERT OR REPLACE INTO extension_settings (id, extensionId, key, value, updatedAt) 2412 + VALUES (?, ?, ?, ?, ?) 2413 + `).run(rowId, extId, key, JSON.stringify(value), now()); 2564 2414 2565 2415 return { success: true }; 2566 2416 } catch (error) { ··· 3139 2989 timestamp: Date.now() 3140 2990 }); 3141 2991 3142 - // Clean up datastore 3143 - if (datastorePersister) { 3144 - try { 3145 - await datastorePersister.stopAutoSave(); 3146 - await datastorePersister.save(); // Final save 3147 - datastorePersister.destroy(); 3148 - console.log('Datastore persister cleaned up'); 3149 - } catch (error) { 3150 - console.error('Error cleaning up datastore persister:', error); 3151 - } 3152 - } 3153 - 3154 2992 // Close SQLite database 3155 - if (datastoreDb) { 2993 + if (db) { 3156 2994 try { 3157 - await new Promise((resolve, reject) => { 3158 - datastoreDb.close((err) => { 3159 - if (err) reject(err); 3160 - else resolve(); 3161 - }); 3162 - }); 2995 + closeDatabase(); 3163 2996 console.log('SQLite database closed'); 3164 2997 } catch (error) { 3165 2998 console.error('Error closing SQLite database:', error);
+3 -3
package.json
··· 33 33 "help": "electron --help" 34 34 }, 35 35 "dependencies": { 36 + "better-sqlite3": "^12.5.0", 36 37 "electron-unhandled": "^5.0.0", 37 - "lil-gui": "^0.19.2", 38 - "sqlite3": "^5.1.7", 39 - "tinybase": "^6.7.2" 38 + "lil-gui": "^0.19.2" 40 39 }, 41 40 "devDependencies": { 42 41 "@electron/fuses": "^1.8.0", 42 + "@electron/rebuild": "^4.0.2", 43 43 "electron": "^35.7.5", 44 44 "electron-builder": "26.0.12" 45 45 },
+157 -375
yarn.lock
··· 153 153 languageName: node 154 154 linkType: hard 155 155 156 + "@electron/rebuild@npm:^4.0.2": 157 + version: 4.0.2 158 + resolution: "@electron/rebuild@npm:4.0.2" 159 + dependencies: 160 + "@malept/cross-spawn-promise": "npm:^2.0.0" 161 + debug: "npm:^4.1.1" 162 + detect-libc: "npm:^2.0.1" 163 + got: "npm:^11.7.0" 164 + graceful-fs: "npm:^4.2.11" 165 + node-abi: "npm:^4.2.0" 166 + node-api-version: "npm:^0.2.1" 167 + node-gyp: "npm:^11.2.0" 168 + ora: "npm:^5.1.0" 169 + read-binary-file-arch: "npm:^1.0.6" 170 + semver: "npm:^7.3.5" 171 + tar: "npm:^6.0.5" 172 + yargs: "npm:^17.0.1" 173 + dependenciesMeta: 174 + electron: 175 + built: true 176 + bin: 177 + electron-rebuild: lib/cli.js 178 + checksum: 10c0/e74af1d22c7ed155e8326c97ff1c05ff0b508252f3f9a3c86d0eedb0dbf2f34768ed2accc468d94c755bdf4dabb5dee6a59f7719652697223afa13235c62297b 179 + languageName: node 180 + linkType: hard 181 + 156 182 "@electron/universal@npm:2.0.1": 157 183 version: 2.0.1 158 184 resolution: "@electron/universal@npm:2.0.1" ··· 168 194 languageName: node 169 195 linkType: hard 170 196 171 - "@gar/promisify@npm:^1.0.1, @gar/promisify@npm:^1.1.3": 197 + "@gar/promisify@npm:^1.1.3": 172 198 version: 1.1.3 173 199 resolution: "@gar/promisify@npm:1.1.3" 174 200 checksum: 10c0/0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff ··· 235 261 languageName: node 236 262 linkType: hard 237 263 264 + "@npmcli/agent@npm:^3.0.0": 265 + version: 3.0.0 266 + resolution: "@npmcli/agent@npm:3.0.0" 267 + dependencies: 268 + agent-base: "npm:^7.1.0" 269 + http-proxy-agent: "npm:^7.0.0" 270 + https-proxy-agent: "npm:^7.0.1" 271 + lru-cache: "npm:^10.0.1" 272 + socks-proxy-agent: "npm:^8.0.3" 273 + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 274 + languageName: node 275 + linkType: hard 276 + 238 277 "@npmcli/agent@npm:^4.0.0": 239 278 version: 4.0.0 240 279 resolution: "@npmcli/agent@npm:4.0.0" ··· 245 284 lru-cache: "npm:^11.2.1" 246 285 socks-proxy-agent: "npm:^8.0.3" 247 286 checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 248 - languageName: node 249 - linkType: hard 250 - 251 - "@npmcli/fs@npm:^1.0.0": 252 - version: 1.1.1 253 - resolution: "@npmcli/fs@npm:1.1.1" 254 - dependencies: 255 - "@gar/promisify": "npm:^1.0.1" 256 - semver: "npm:^7.3.5" 257 - checksum: 10c0/4143c317a7542af9054018b71601e3c3392e6704e884561229695f099a71336cbd580df9a9ffb965d0024bf0ed593189ab58900fd1714baef1c9ee59c738c3e2 258 287 languageName: node 259 288 linkType: hard 260 289 ··· 277 306 languageName: node 278 307 linkType: hard 279 308 280 - "@npmcli/move-file@npm:^1.0.1": 281 - version: 1.1.2 282 - resolution: "@npmcli/move-file@npm:1.1.2" 283 - dependencies: 284 - mkdirp: "npm:^1.0.4" 285 - rimraf: "npm:^3.0.2" 286 - checksum: 10c0/02e946f3dafcc6743132fe2e0e2b585a96ca7265653a38df5a3e53fcf26c7c7a57fc0f861d7c689a23fdb6d6836c7eea5050c8086abf3c994feb2208d1514ff0 287 - languageName: node 288 - linkType: hard 289 - 290 309 "@npmcli/move-file@npm:^2.0.0": 291 310 version: 2.0.1 292 311 resolution: "@npmcli/move-file@npm:2.0.1" ··· 320 339 languageName: node 321 340 linkType: hard 322 341 323 - "@tootallnate/once@npm:1": 324 - version: 1.1.2 325 - resolution: "@tootallnate/once@npm:1.1.2" 326 - checksum: 10c0/8fe4d006e90422883a4fa9339dd05a83ff626806262e1710cee5758d493e8cbddf2db81c0e4690636dc840b02c9fda62877866ea774ebd07c1777ed5fafbdec6 327 - languageName: node 328 - linkType: hard 329 - 330 342 "@tootallnate/once@npm:2": 331 343 version: 2.0.0 332 344 resolution: "@tootallnate/once@npm:2.0.0" ··· 443 455 resolution: "Peek@workspace:." 444 456 dependencies: 445 457 "@electron/fuses": "npm:^1.8.0" 458 + "@electron/rebuild": "npm:^4.0.2" 459 + better-sqlite3: "npm:^12.5.0" 446 460 electron: "npm:^35.7.5" 447 461 electron-builder: "npm:26.0.12" 448 462 electron-unhandled: "npm:^5.0.0" 449 463 lil-gui: "npm:^0.19.2" 450 - sqlite3: "npm:^5.1.7" 451 - tinybase: "npm:^6.7.2" 452 464 languageName: unknown 453 465 linkType: soft 454 466 455 - "abbrev@npm:1, abbrev@npm:^1.0.0": 467 + "abbrev@npm:^1.0.0": 456 468 version: 1.1.1 457 469 resolution: "abbrev@npm:1.1.1" 458 470 checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 459 471 languageName: node 460 472 linkType: hard 461 473 474 + "abbrev@npm:^3.0.0": 475 + version: 3.0.1 476 + resolution: "abbrev@npm:3.0.1" 477 + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf 478 + languageName: node 479 + linkType: hard 480 + 462 481 "abbrev@npm:^4.0.0": 463 482 version: 4.0.0 464 483 resolution: "abbrev@npm:4.0.0" ··· 482 501 languageName: node 483 502 linkType: hard 484 503 485 - "agentkeepalive@npm:^4.1.3, agentkeepalive@npm:^4.2.1": 504 + "agentkeepalive@npm:^4.2.1": 486 505 version: 4.6.0 487 506 resolution: "agentkeepalive@npm:4.6.0" 488 507 dependencies: ··· 600 619 dmg-builder: 26.0.12 601 620 electron-builder-squirrel-windows: 26.0.12 602 621 checksum: 10c0/d0c1706c6147f1ffdf6b4ba4b3510ed629fc64c9f708413ea298957f945bb29349a5ea72c741be9da5ef81cbfb7b09be37ecc26b6fc104797fb7c8ba172a332f 603 - languageName: node 604 - linkType: hard 605 - 606 - "aproba@npm:^1.0.3 || ^2.0.0": 607 - version: 2.1.0 608 - resolution: "aproba@npm:2.1.0" 609 - checksum: 10c0/ec8c1d351bac0717420c737eb062766fb63bde1552900e0f4fdad9eb064c3824fef23d1c416aa5f7a80f21ca682808e902d79b7c9ae756d342b5f1884f36932f 610 - languageName: node 611 - linkType: hard 612 - 613 - "are-we-there-yet@npm:^3.0.0": 614 - version: 3.0.1 615 - resolution: "are-we-there-yet@npm:3.0.1" 616 - dependencies: 617 - delegates: "npm:^1.0.0" 618 - readable-stream: "npm:^3.6.0" 619 - checksum: 10c0/8373f289ba42e4b5ec713bb585acdac14b5702c75f2a458dc985b9e4fa5762bc5b46b40a21b72418a3ed0cfb5e35bdc317ef1ae132f3035f633d581dd03168c3 620 622 languageName: node 621 623 linkType: hard 622 624 ··· 697 699 languageName: node 698 700 linkType: hard 699 701 702 + "better-sqlite3@npm:^12.5.0": 703 + version: 12.5.0 704 + resolution: "better-sqlite3@npm:12.5.0" 705 + dependencies: 706 + bindings: "npm:^1.5.0" 707 + node-gyp: "npm:latest" 708 + prebuild-install: "npm:^7.1.1" 709 + checksum: 10c0/50016aaa9a9db768907da5b109f12f8bb6f6d9be34d68b5623d901fe038cdac0d81be37aa280c27c8e0604d6c2482584b14f9c40f75ad6aa0baa59278c5f7a6a 710 + languageName: node 711 + linkType: hard 712 + 700 713 "bindings@npm:^1.5.0": 701 714 version: 1.5.0 702 715 resolution: "bindings@npm:1.5.0" ··· 802 815 languageName: node 803 816 linkType: hard 804 817 805 - "cacache@npm:^15.2.0": 806 - version: 15.3.0 807 - resolution: "cacache@npm:15.3.0" 808 - dependencies: 809 - "@npmcli/fs": "npm:^1.0.0" 810 - "@npmcli/move-file": "npm:^1.0.1" 811 - chownr: "npm:^2.0.0" 812 - fs-minipass: "npm:^2.0.0" 813 - glob: "npm:^7.1.4" 814 - infer-owner: "npm:^1.0.4" 815 - lru-cache: "npm:^6.0.0" 816 - minipass: "npm:^3.1.1" 817 - minipass-collect: "npm:^1.0.2" 818 - minipass-flush: "npm:^1.0.5" 819 - minipass-pipeline: "npm:^1.2.2" 820 - mkdirp: "npm:^1.0.3" 821 - p-map: "npm:^4.0.0" 822 - promise-inflight: "npm:^1.0.1" 823 - rimraf: "npm:^3.0.2" 824 - ssri: "npm:^8.0.1" 825 - tar: "npm:^6.0.2" 826 - unique-filename: "npm:^1.1.1" 827 - checksum: 10c0/886fcc0acc4f6fd5cd142d373d8276267bc6d655d7c4ce60726fbbec10854de3395ee19bbf9e7e73308cdca9fdad0ad55060ff3bd16c6d4165c5b8d21515e1d8 828 - languageName: node 829 - linkType: hard 830 - 831 818 "cacache@npm:^16.1.0": 832 819 version: 16.1.3 833 820 resolution: "cacache@npm:16.1.3" ··· 851 838 tar: "npm:^6.1.11" 852 839 unique-filename: "npm:^2.0.0" 853 840 checksum: 10c0/cdf6836e1c457d2a5616abcaf5d8240c0346b1f5bd6fdb8866b9d84b6dff0b54e973226dc11e0d099f35394213d24860d1989c8358d2a41b39eb912b3000e749 841 + languageName: node 842 + linkType: hard 843 + 844 + "cacache@npm:^19.0.1": 845 + version: 19.0.1 846 + resolution: "cacache@npm:19.0.1" 847 + dependencies: 848 + "@npmcli/fs": "npm:^4.0.0" 849 + fs-minipass: "npm:^3.0.0" 850 + glob: "npm:^10.2.2" 851 + lru-cache: "npm:^10.0.1" 852 + minipass: "npm:^7.0.3" 853 + minipass-collect: "npm:^2.0.1" 854 + minipass-flush: "npm:^1.0.5" 855 + minipass-pipeline: "npm:^1.2.4" 856 + p-map: "npm:^7.0.2" 857 + ssri: "npm:^12.0.0" 858 + tar: "npm:^7.4.3" 859 + unique-filename: "npm:^4.0.0" 860 + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c 854 861 languageName: node 855 862 linkType: hard 856 863 ··· 1035 1042 languageName: node 1036 1043 linkType: hard 1037 1044 1038 - "color-support@npm:^1.1.3": 1039 - version: 1.1.3 1040 - resolution: "color-support@npm:1.1.3" 1041 - bin: 1042 - color-support: bin.js 1043 - checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 1044 - languageName: node 1045 - linkType: hard 1046 - 1047 1045 "combined-stream@npm:^1.0.8": 1048 1046 version: 1.0.8 1049 1047 resolution: "combined-stream@npm:1.0.8" ··· 1084 1082 languageName: node 1085 1083 linkType: hard 1086 1084 1087 - "console-control-strings@npm:^1.1.0": 1088 - version: 1.1.0 1089 - resolution: "console-control-strings@npm:1.1.0" 1090 - checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 1091 - languageName: node 1092 - linkType: hard 1093 - 1094 1085 "core-util-is@npm:1.0.2": 1095 1086 version: 1.0.2 1096 1087 resolution: "core-util-is@npm:1.0.2" ··· 1214 1205 languageName: node 1215 1206 linkType: hard 1216 1207 1217 - "delegates@npm:^1.0.0": 1218 - version: 1.0.0 1219 - resolution: "delegates@npm:1.0.0" 1220 - checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 1221 - languageName: node 1222 - linkType: hard 1223 - 1224 1208 "detect-libc@npm:^2.0.0": 1225 1209 version: 2.1.2 1226 1210 resolution: "detect-libc@npm:2.1.2" ··· 1417 1401 languageName: node 1418 1402 linkType: hard 1419 1403 1420 - "encoding@npm:^0.1.12, encoding@npm:^0.1.13": 1404 + "encoding@npm:^0.1.13": 1421 1405 version: 0.1.13 1422 1406 resolution: "encoding@npm:0.1.13" 1423 1407 dependencies: ··· 1724 1708 languageName: node 1725 1709 linkType: hard 1726 1710 1727 - "gauge@npm:^4.0.3": 1728 - version: 4.0.4 1729 - resolution: "gauge@npm:4.0.4" 1730 - dependencies: 1731 - aproba: "npm:^1.0.3 || ^2.0.0" 1732 - color-support: "npm:^1.1.3" 1733 - console-control-strings: "npm:^1.1.0" 1734 - has-unicode: "npm:^2.0.1" 1735 - signal-exit: "npm:^3.0.7" 1736 - string-width: "npm:^4.2.3" 1737 - strip-ansi: "npm:^6.0.1" 1738 - wide-align: "npm:^1.1.5" 1739 - checksum: 10c0/ef10d7981113d69225135f994c9f8c4369d945e64a8fc721d655a3a38421b738c9fe899951721d1b47b73c41fdb5404ac87cc8903b2ecbed95d2800363e7e58c 1740 - languageName: node 1741 - linkType: hard 1742 - 1743 1711 "generator-function@npm:^2.0.0": 1744 1712 version: 2.0.1 1745 1713 resolution: "generator-function@npm:2.0.1" ··· 1813 1781 languageName: node 1814 1782 linkType: hard 1815 1783 1816 - "glob@npm:^10.3.12": 1784 + "glob@npm:^10.2.2, glob@npm:^10.3.12": 1817 1785 version: 10.5.0 1818 1786 resolution: "glob@npm:10.5.0" 1819 1787 dependencies: ··· 1845 1813 languageName: node 1846 1814 linkType: hard 1847 1815 1848 - "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": 1816 + "glob@npm:^7.1.3, glob@npm:^7.1.6": 1849 1817 version: 7.2.3 1850 1818 resolution: "glob@npm:7.2.3" 1851 1819 dependencies: ··· 1930 1898 languageName: node 1931 1899 linkType: hard 1932 1900 1933 - "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": 1901 + "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6": 1934 1902 version: 4.2.11 1935 1903 resolution: "graceful-fs@npm:4.2.11" 1936 1904 checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 ··· 1980 1948 dependencies: 1981 1949 has-symbols: "npm:^1.0.3" 1982 1950 checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c 1983 - languageName: node 1984 - linkType: hard 1985 - 1986 - "has-unicode@npm:^2.0.1": 1987 - version: 2.0.1 1988 - resolution: "has-unicode@npm:2.0.1" 1989 - checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c 1990 1951 languageName: node 1991 1952 linkType: hard 1992 1953 ··· 2019 1980 version: 4.2.0 2020 1981 resolution: "http-cache-semantics@npm:4.2.0" 2021 1982 checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 2022 - languageName: node 2023 - linkType: hard 2024 - 2025 - "http-proxy-agent@npm:^4.0.1": 2026 - version: 4.0.1 2027 - resolution: "http-proxy-agent@npm:4.0.1" 2028 - dependencies: 2029 - "@tootallnate/once": "npm:1" 2030 - agent-base: "npm:6" 2031 - debug: "npm:4" 2032 - checksum: 10c0/4fa4774d65b5331814b74ac05cefea56854fc0d5989c80b13432c1b0d42a14c9f4342ca3ad9f0359a52e78da12b1744c9f8a28e50042136ea9171675d972a5fd 2033 1983 languageName: node 2034 1984 linkType: hard 2035 1985 ··· 2410 2360 languageName: node 2411 2361 linkType: hard 2412 2362 2413 - "lru-cache@npm:^10.2.0": 2363 + "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": 2414 2364 version: 10.4.3 2415 2365 resolution: "lru-cache@npm:10.4.3" 2416 2366 checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb ··· 2464 2414 languageName: node 2465 2415 linkType: hard 2466 2416 2467 - "make-fetch-happen@npm:^15.0.0": 2468 - version: 15.0.2 2469 - resolution: "make-fetch-happen@npm:15.0.2" 2417 + "make-fetch-happen@npm:^14.0.3": 2418 + version: 14.0.3 2419 + resolution: "make-fetch-happen@npm:14.0.3" 2470 2420 dependencies: 2471 - "@npmcli/agent": "npm:^4.0.0" 2472 - cacache: "npm:^20.0.1" 2421 + "@npmcli/agent": "npm:^3.0.0" 2422 + cacache: "npm:^19.0.1" 2473 2423 http-cache-semantics: "npm:^4.1.1" 2474 2424 minipass: "npm:^7.0.2" 2475 2425 minipass-fetch: "npm:^4.0.0" ··· 2479 2429 proc-log: "npm:^5.0.0" 2480 2430 promise-retry: "npm:^2.0.1" 2481 2431 ssri: "npm:^12.0.0" 2482 - checksum: 10c0/3cc9b4e71bba88bcec53f5307f9c3096c6193a2357e825bf3a3a03c99896d2fa14abba8363a84199829dade639e85dc0eb07de77d247aa249d13ff80511adf2c 2432 + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 2483 2433 languageName: node 2484 2434 linkType: hard 2485 2435 2486 - "make-fetch-happen@npm:^9.1.0": 2487 - version: 9.1.0 2488 - resolution: "make-fetch-happen@npm:9.1.0" 2436 + "make-fetch-happen@npm:^15.0.0": 2437 + version: 15.0.2 2438 + resolution: "make-fetch-happen@npm:15.0.2" 2489 2439 dependencies: 2490 - agentkeepalive: "npm:^4.1.3" 2491 - cacache: "npm:^15.2.0" 2492 - http-cache-semantics: "npm:^4.1.0" 2493 - http-proxy-agent: "npm:^4.0.1" 2494 - https-proxy-agent: "npm:^5.0.0" 2495 - is-lambda: "npm:^1.0.1" 2496 - lru-cache: "npm:^6.0.0" 2497 - minipass: "npm:^3.1.3" 2498 - minipass-collect: "npm:^1.0.2" 2499 - minipass-fetch: "npm:^1.3.2" 2440 + "@npmcli/agent": "npm:^4.0.0" 2441 + cacache: "npm:^20.0.1" 2442 + http-cache-semantics: "npm:^4.1.1" 2443 + minipass: "npm:^7.0.2" 2444 + minipass-fetch: "npm:^4.0.0" 2500 2445 minipass-flush: "npm:^1.0.5" 2501 2446 minipass-pipeline: "npm:^1.2.4" 2502 - negotiator: "npm:^0.6.2" 2447 + negotiator: "npm:^1.0.0" 2448 + proc-log: "npm:^5.0.0" 2503 2449 promise-retry: "npm:^2.0.1" 2504 - socks-proxy-agent: "npm:^6.0.0" 2505 - ssri: "npm:^8.0.0" 2506 - checksum: 10c0/2c737faf6a7f67077679da548b5bfeeef890595bf8c4323a1f76eae355d27ebb33dcf9cf1a673f944cf2f2a7cbf4e2b09f0a0a62931737728f210d902c6be966 2450 + ssri: "npm:^12.0.0" 2451 + checksum: 10c0/3cc9b4e71bba88bcec53f5307f9c3096c6193a2357e825bf3a3a03c99896d2fa14abba8363a84199829dade639e85dc0eb07de77d247aa249d13ff80511adf2c 2507 2452 languageName: node 2508 2453 linkType: hard 2509 2454 ··· 2630 2575 languageName: node 2631 2576 linkType: hard 2632 2577 2633 - "minipass-fetch@npm:^1.3.2": 2634 - version: 1.4.1 2635 - resolution: "minipass-fetch@npm:1.4.1" 2636 - dependencies: 2637 - encoding: "npm:^0.1.12" 2638 - minipass: "npm:^3.1.0" 2639 - minipass-sized: "npm:^1.0.3" 2640 - minizlib: "npm:^2.0.0" 2641 - dependenciesMeta: 2642 - encoding: 2643 - optional: true 2644 - checksum: 10c0/a43da7401cd7c4f24b993887d41bd37d097356083b0bb836fd655916467463a1e6e9e553b2da4fcbe8745bf23d40c8b884eab20745562199663b3e9060cd8e7a 2645 - languageName: node 2646 - linkType: hard 2647 - 2648 2578 "minipass-fetch@npm:^2.0.3": 2649 2579 version: 2.1.2 2650 2580 resolution: "minipass-fetch@npm:2.1.2" ··· 2684 2614 languageName: node 2685 2615 linkType: hard 2686 2616 2687 - "minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4": 2617 + "minipass-pipeline@npm:^1.2.4": 2688 2618 version: 1.2.4 2689 2619 resolution: "minipass-pipeline@npm:1.2.4" 2690 2620 dependencies: ··· 2702 2632 languageName: node 2703 2633 linkType: hard 2704 2634 2705 - "minipass@npm:^3.0.0, minipass@npm:^3.1.0, minipass@npm:^3.1.1, minipass@npm:^3.1.3, minipass@npm:^3.1.6": 2635 + "minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": 2706 2636 version: 3.3.6 2707 2637 resolution: "minipass@npm:3.3.6" 2708 2638 dependencies: ··· 2725 2655 languageName: node 2726 2656 linkType: hard 2727 2657 2728 - "minizlib@npm:^2.0.0, minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": 2658 + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": 2729 2659 version: 2.1.2 2730 2660 resolution: "minizlib@npm:2.1.2" 2731 2661 dependencies: ··· 2781 2711 languageName: node 2782 2712 linkType: hard 2783 2713 2784 - "negotiator@npm:^0.6.2, negotiator@npm:^0.6.3": 2714 + "negotiator@npm:^0.6.3": 2785 2715 version: 0.6.4 2786 2716 resolution: "negotiator@npm:0.6.4" 2787 2717 checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea ··· 2804 2734 languageName: node 2805 2735 linkType: hard 2806 2736 2737 + "node-abi@npm:^4.2.0": 2738 + version: 4.24.0 2739 + resolution: "node-abi@npm:4.24.0" 2740 + dependencies: 2741 + semver: "npm:^7.6.3" 2742 + checksum: 10c0/9bf9f4e79c875b98f8026f2ad80150b2d5077f48529444232c9574cfd82e45d42a3ab2dcf6fb374cf7775becbf58e7c1b8704596ad3bef27cdeab7bc93eca7a3 2743 + languageName: node 2744 + linkType: hard 2745 + 2807 2746 "node-addon-api@npm:^1.6.3": 2808 2747 version: 1.7.2 2809 2748 resolution: "node-addon-api@npm:1.7.2" ··· 2813 2752 languageName: node 2814 2753 linkType: hard 2815 2754 2816 - "node-addon-api@npm:^7.0.0": 2817 - version: 7.1.1 2818 - resolution: "node-addon-api@npm:7.1.1" 2819 - dependencies: 2820 - node-gyp: "npm:latest" 2821 - checksum: 10c0/fb32a206276d608037fa1bcd7e9921e177fe992fc610d098aa3128baca3c0050fc1e014fa007e9b3874cf865ddb4f5bd9f43ccb7cbbbe4efaff6a83e920b17e9 2822 - languageName: node 2823 - linkType: hard 2824 - 2825 - "node-api-version@npm:^0.2.0": 2755 + "node-api-version@npm:^0.2.0, node-api-version@npm:^0.2.1": 2826 2756 version: 0.2.1 2827 2757 resolution: "node-api-version@npm:0.2.1" 2828 2758 dependencies: ··· 2831 2761 languageName: node 2832 2762 linkType: hard 2833 2763 2834 - "node-gyp@npm:8.x": 2835 - version: 8.4.1 2836 - resolution: "node-gyp@npm:8.4.1" 2764 + "node-gyp@npm:^11.2.0": 2765 + version: 11.5.0 2766 + resolution: "node-gyp@npm:11.5.0" 2837 2767 dependencies: 2838 2768 env-paths: "npm:^2.2.0" 2839 - glob: "npm:^7.1.4" 2769 + exponential-backoff: "npm:^3.1.1" 2840 2770 graceful-fs: "npm:^4.2.6" 2841 - make-fetch-happen: "npm:^9.1.0" 2842 - nopt: "npm:^5.0.0" 2843 - npmlog: "npm:^6.0.0" 2844 - rimraf: "npm:^3.0.2" 2771 + make-fetch-happen: "npm:^14.0.3" 2772 + nopt: "npm:^8.0.0" 2773 + proc-log: "npm:^5.0.0" 2845 2774 semver: "npm:^7.3.5" 2846 - tar: "npm:^6.1.2" 2847 - which: "npm:^2.0.2" 2775 + tar: "npm:^7.4.3" 2776 + tinyglobby: "npm:^0.2.12" 2777 + which: "npm:^5.0.0" 2848 2778 bin: 2849 2779 node-gyp: bin/node-gyp.js 2850 - checksum: 10c0/80ef333b3a882eb6a2695a8e08f31d618f4533eff192864e4a3a16b67ff0abc9d8c1d5fac0395550ec699326b9248c5e2b3be178492f7f4d1ccf97d2cf948021 2780 + checksum: 10c0/31ff49586991b38287bb15c3d529dd689cfc32f992eed9e6997b9d712d5d21fe818a8b1bbfe3b76a7e33765c20210c5713212f4aa329306a615b87d8a786da3a 2851 2781 languageName: node 2852 2782 linkType: hard 2853 2783 ··· 2871 2801 languageName: node 2872 2802 linkType: hard 2873 2803 2874 - "nopt@npm:^5.0.0": 2875 - version: 5.0.0 2876 - resolution: "nopt@npm:5.0.0" 2804 + "nopt@npm:^6.0.0": 2805 + version: 6.0.0 2806 + resolution: "nopt@npm:6.0.0" 2877 2807 dependencies: 2878 - abbrev: "npm:1" 2808 + abbrev: "npm:^1.0.0" 2879 2809 bin: 2880 2810 nopt: bin/nopt.js 2881 - checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 2811 + checksum: 10c0/837b52c330df16fcaad816b1f54fec6b2854ab1aa771d935c1603fbcf9b023bb073f1466b1b67f48ea4dce127ae675b85b9d9355700e9b109de39db490919786 2882 2812 languageName: node 2883 2813 linkType: hard 2884 2814 2885 - "nopt@npm:^6.0.0": 2886 - version: 6.0.0 2887 - resolution: "nopt@npm:6.0.0" 2815 + "nopt@npm:^8.0.0": 2816 + version: 8.1.0 2817 + resolution: "nopt@npm:8.1.0" 2888 2818 dependencies: 2889 - abbrev: "npm:^1.0.0" 2819 + abbrev: "npm:^3.0.0" 2890 2820 bin: 2891 2821 nopt: bin/nopt.js 2892 - checksum: 10c0/837b52c330df16fcaad816b1f54fec6b2854ab1aa771d935c1603fbcf9b023bb073f1466b1b67f48ea4dce127ae675b85b9d9355700e9b109de39db490919786 2822 + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef 2893 2823 languageName: node 2894 2824 linkType: hard 2895 2825 ··· 2908 2838 version: 6.1.0 2909 2839 resolution: "normalize-url@npm:6.1.0" 2910 2840 checksum: 10c0/95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 2911 - languageName: node 2912 - linkType: hard 2913 - 2914 - "npmlog@npm:^6.0.0": 2915 - version: 6.0.2 2916 - resolution: "npmlog@npm:6.0.2" 2917 - dependencies: 2918 - are-we-there-yet: "npm:^3.0.0" 2919 - console-control-strings: "npm:^1.1.0" 2920 - gauge: "npm:^4.0.3" 2921 - set-blocking: "npm:^2.0.0" 2922 - checksum: 10c0/0cacedfbc2f6139c746d9cd4a85f62718435ad0ca4a2d6459cd331dd33ae58206e91a0742c1558634efcde3f33f8e8e7fd3adf1bfe7978310cf00bd55cccf890 2923 2841 languageName: node 2924 2842 linkType: hard 2925 2843 ··· 3193 3111 languageName: node 3194 3112 linkType: hard 3195 3113 3196 - "readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": 3114 + "readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": 3197 3115 version: 3.6.2 3198 3116 resolution: "readable-stream@npm:3.6.2" 3199 3117 dependencies: ··· 3344 3262 languageName: node 3345 3263 linkType: hard 3346 3264 3347 - "semver@npm:^7.3.8, semver@npm:^7.5.3": 3265 + "semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.6.3": 3348 3266 version: 7.7.3 3349 3267 resolution: "semver@npm:7.7.3" 3350 3268 bin: ··· 3371 3289 languageName: node 3372 3290 linkType: hard 3373 3291 3374 - "set-blocking@npm:^2.0.0": 3375 - version: 2.0.0 3376 - resolution: "set-blocking@npm:2.0.0" 3377 - checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 3378 - languageName: node 3379 - linkType: hard 3380 - 3381 3292 "shebang-command@npm:^2.0.0": 3382 3293 version: 2.0.0 3383 3294 resolution: "shebang-command@npm:2.0.0" ··· 3394 3305 languageName: node 3395 3306 linkType: hard 3396 3307 3397 - "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": 3308 + "signal-exit@npm:^3.0.2": 3398 3309 version: 3.0.7 3399 3310 resolution: "signal-exit@npm:3.0.7" 3400 3311 checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 ··· 3453 3364 languageName: node 3454 3365 linkType: hard 3455 3366 3456 - "socks-proxy-agent@npm:^6.0.0": 3457 - version: 6.2.1 3458 - resolution: "socks-proxy-agent@npm:6.2.1" 3459 - dependencies: 3460 - agent-base: "npm:^6.0.2" 3461 - debug: "npm:^4.3.3" 3462 - socks: "npm:^2.6.2" 3463 - checksum: 10c0/d75c1cf1fdd7f8309a43a77f84409b793fc0f540742ef915154e70ac09a08b0490576fe85d4f8d68bbf80e604a62957a17ab5ef50d312fe1442b0ab6f8f6e6f6 3464 - languageName: node 3465 - linkType: hard 3466 - 3467 3367 "socks-proxy-agent@npm:^7.0.0": 3468 3368 version: 7.0.0 3469 3369 resolution: "socks-proxy-agent@npm:7.0.0" ··· 3530 3430 languageName: node 3531 3431 linkType: hard 3532 3432 3533 - "sqlite3@npm:^5.1.7": 3534 - version: 5.1.7 3535 - resolution: "sqlite3@npm:5.1.7" 3536 - dependencies: 3537 - bindings: "npm:^1.5.0" 3538 - node-addon-api: "npm:^7.0.0" 3539 - node-gyp: "npm:8.x" 3540 - prebuild-install: "npm:^7.1.1" 3541 - tar: "npm:^6.1.11" 3542 - peerDependencies: 3543 - node-gyp: 8.x 3544 - dependenciesMeta: 3545 - node-gyp: 3546 - optional: true 3547 - peerDependenciesMeta: 3548 - node-gyp: 3549 - optional: true 3550 - checksum: 10c0/10daab5d7854bd0ec3c7690c00359cd3444eabc869b68c68dcb61374a8fa5e2f4be06cf0aba78f7a16336d49e83e4631e8af98f8bd33c772fe8d60b45fa60bc1 3551 - languageName: node 3552 - linkType: hard 3553 - 3554 3433 "ssri@npm:^12.0.0": 3555 3434 version: 12.0.0 3556 3435 resolution: "ssri@npm:12.0.0" 3557 3436 dependencies: 3558 3437 minipass: "npm:^7.0.3" 3559 3438 checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d 3560 - languageName: node 3561 - linkType: hard 3562 - 3563 - "ssri@npm:^8.0.0, ssri@npm:^8.0.1": 3564 - version: 8.0.1 3565 - resolution: "ssri@npm:8.0.1" 3566 - dependencies: 3567 - minipass: "npm:^3.1.1" 3568 - checksum: 10c0/5cfae216ae02dcd154d1bbed2d0a60038a4b3a2fcaac3c7e47401ff4e058e551ee74cfdba618871bf168cd583db7b8324f94af6747d4303b73cd4c3f6dc5c9c2 3569 3439 languageName: node 3570 3440 linkType: hard 3571 3441 ··· 3585 3455 languageName: node 3586 3456 linkType: hard 3587 3457 3588 - "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": 3458 + "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": 3589 3459 version: 4.2.3 3590 3460 resolution: "string-width@npm:4.2.3" 3591 3461 dependencies: ··· 3684 3554 languageName: node 3685 3555 linkType: hard 3686 3556 3687 - "tar@npm:^6.0.2, tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.12, tar@npm:^6.1.2, tar@npm:^6.2.1": 3557 + "tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.12, tar@npm:^6.2.1": 3688 3558 version: 6.2.1 3689 3559 resolution: "tar@npm:6.2.1" 3690 3560 dependencies: ··· 3698 3568 languageName: node 3699 3569 linkType: hard 3700 3570 3701 - "tar@npm:^7.5.2": 3571 + "tar@npm:^7.4.3, tar@npm:^7.5.2": 3702 3572 version: 7.5.2 3703 3573 resolution: "tar@npm:7.5.2" 3704 3574 dependencies: ··· 3730 3600 languageName: node 3731 3601 linkType: hard 3732 3602 3733 - "tinybase@npm:^6.7.2": 3734 - version: 6.7.2 3735 - resolution: "tinybase@npm:6.7.2" 3736 - peerDependencies: 3737 - "@automerge/automerge-repo": ^1.2.1 3738 - "@cloudflare/workers-types": ^4.20251014.0 3739 - "@electric-sql/pglite": ^0.3.11 3740 - "@libsql/client": ^0.15.15 3741 - "@powersync/common": ^1.40.0 3742 - "@sqlite.org/sqlite-wasm": ^3.50.4-build1 3743 - "@vlcn.io/crsqlite-wasm": ^0.16.0 3744 - bun: ^1.3.1 3745 - electric-sql: ^0.12.1 3746 - expo: ^54.0.10 3747 - expo-sqlite: ^16.0.8 3748 - partykit: ^0.0.115 3749 - partysocket: ^1.1.5 3750 - postgres: ^3.4.7 3751 - react: ^19.0.0 3752 - react-dom: ^19.0.0 3753 - react-native-mmkv: 4.0.0 3754 - react-native-sqlite-storage: ^6.0.1 3755 - sqlite3: ^5.1.7 3756 - ws: ^8.18.3 3757 - yjs: ^13.6.27 3758 - peerDependenciesMeta: 3759 - "@automerge/automerge-repo": 3760 - optional: true 3761 - "@cloudflare/workers-types": 3762 - optional: true 3763 - "@electric-sql/pglite": 3764 - optional: true 3765 - "@libsql/client": 3766 - optional: true 3767 - "@powersync/common": 3768 - optional: true 3769 - "@sqlite.org/sqlite-wasm": 3770 - optional: true 3771 - "@vlcn.io/crsqlite-wasm": 3772 - optional: true 3773 - bun: 3774 - optional: true 3775 - electric-sql: 3776 - optional: true 3777 - expo: 3778 - optional: true 3779 - expo-sqlite: 3780 - optional: true 3781 - partykit: 3782 - optional: true 3783 - partysocket: 3784 - optional: true 3785 - postgres: 3786 - optional: true 3787 - react: 3788 - optional: true 3789 - react-dom: 3790 - optional: true 3791 - react-native-mmkv: 3792 - optional: true 3793 - react-native-sqlite-storage: 3794 - optional: true 3795 - sqlite3: 3796 - optional: true 3797 - ws: 3798 - optional: true 3799 - yjs: 3800 - optional: true 3801 - checksum: 10c0/ca9f9cdc756c8b54feb95c0e9be0c14f700f532da25260ab8b9363d99414491937f4e4cfb78eef6bcffac36e676a02997f5b4a2135c18d4ffa4851744be18852 3802 - languageName: node 3803 - linkType: hard 3804 - 3805 3603 "tinyglobby@npm:^0.2.12": 3806 3604 version: 0.2.15 3807 3605 resolution: "tinyglobby@npm:0.2.15" ··· 3887 3685 languageName: node 3888 3686 linkType: hard 3889 3687 3890 - "unique-filename@npm:^1.1.1": 3891 - version: 1.1.1 3892 - resolution: "unique-filename@npm:1.1.1" 3893 - dependencies: 3894 - unique-slug: "npm:^2.0.0" 3895 - checksum: 10c0/d005bdfaae6894da8407c4de2b52f38b3c58ec86e79fc2ee19939da3085374413b073478ec54e721dc8e32b102cf9e50d0481b8331abdc62202e774b789ea874 3896 - languageName: node 3897 - linkType: hard 3898 - 3899 3688 "unique-filename@npm:^2.0.0": 3900 3689 version: 2.0.1 3901 3690 resolution: "unique-filename@npm:2.0.1" ··· 3911 3700 dependencies: 3912 3701 unique-slug: "npm:^5.0.0" 3913 3702 checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc 3914 - languageName: node 3915 - linkType: hard 3916 - 3917 - "unique-slug@npm:^2.0.0": 3918 - version: 2.0.2 3919 - resolution: "unique-slug@npm:2.0.2" 3920 - dependencies: 3921 - imurmurhash: "npm:^0.1.4" 3922 - checksum: 10c0/9eabc51680cf0b8b197811a48857e41f1364b25362300c1ff636c0eca5ec543a92a38786f59cf0697e62c6f814b11ecbe64e8093db71246468a1f03b80c83970 3923 3703 languageName: node 3924 3704 linkType: hard 3925 3705 ··· 4009 3789 languageName: node 4010 3790 linkType: hard 4011 3791 4012 - "which@npm:^6.0.0": 4013 - version: 6.0.0 4014 - resolution: "which@npm:6.0.0" 3792 + "which@npm:^5.0.0": 3793 + version: 5.0.0 3794 + resolution: "which@npm:5.0.0" 4015 3795 dependencies: 4016 3796 isexe: "npm:^3.1.1" 4017 3797 bin: 4018 3798 node-which: bin/which.js 4019 - checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 3799 + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b 4020 3800 languageName: node 4021 3801 linkType: hard 4022 3802 4023 - "wide-align@npm:^1.1.5": 4024 - version: 1.1.5 4025 - resolution: "wide-align@npm:1.1.5" 3803 + "which@npm:^6.0.0": 3804 + version: 6.0.0 3805 + resolution: "which@npm:6.0.0" 4026 3806 dependencies: 4027 - string-width: "npm:^1.0.2 || 2 || 3 || 4" 4028 - checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 3807 + isexe: "npm:^3.1.1" 3808 + bin: 3809 + node-which: bin/which.js 3810 + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 4029 3811 languageName: node 4030 3812 linkType: hard 4031 3813