UNPKG

neverchange

Version:

NeverChange is a database solution for web applications using SQLite WASM and OPFS.

33 lines (30 loc) 8.63 kB
(function(c,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("@sqlite.org/sqlite-wasm")):typeof define=="function"&&define.amd?define(["exports","@sqlite.org/sqlite-wasm"],l):(c=typeof globalThis<"u"?globalThis:c||self,l(c.neverchange={},c.sqlite3InitModule))})(this,function(c,l){"use strict";var b=Object.defineProperty;var A=(c,l,u)=>l in c?b(c,l,{enumerable:!0,configurable:!0,writable:!0,value:u}):c[l]=u;var d=(c,l,u)=>A(c,typeof l!="symbol"?l+"":l,u);const u={version:0,up:async m=>{await m.execute(` CREATE TABLE IF NOT EXISTS migrations ( version INTEGER PRIMARY KEY, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `)}},E=m=>{const t=m.replace(/^\uFEFF/,"");if(t.length===0)return[];const e=[];let i=[],n="",r=!1,a=!1;const s=()=>{i.push(n),a&&e.push(i),i=[],n="",a=!1};for(let o=0;o<t.length;o++){const h=t[o],f=t[o+1];if(h==='"'){a=!0,r&&f==='"'?(n+='"',o++):r?r=!1:n===""?r=!0:n+=h;continue}if(!r&&h===","){a=!0,i.push(n),n="";continue}if(!r&&(h===` `||h==="\r")){h==="\r"&&f===` `&&o++,s();continue}a=!0,n+=h}if(r)throw new Error("CSV parsing error: unclosed quoted field");return a&&s(),e};let g=0;class w{constructor(t,e={}){d(this,"dbPromise",null);d(this,"dbId",null);d(this,"migrations",[]);d(this,"transactionDepth",0);d(this,"savepointStack",[]);this.dbName=t,this.options=e,this.options.debug=e.debug??!1,this.options.isMigrationActive=e.isMigrationActive??!0,this.options.isMigrationActive&&this.addMigrations([u])}log(...t){this.options.debug&&console.log(...t)}async init(){if(!this.dbPromise)try{this.dbPromise=this.initializeDatabase(),await this.dbPromise,this.options.isMigrationActive&&(await this.createMigrationTable(),await this.runMigrations())}catch(t){throw console.error("Failed to initialize database:",t),t}}async initializeDatabase(){this.log("Loading and initializing SQLite3 module...");const t=await this.getPromiser();this.log("Done initializing. Opening database...");const e=await this.openDatabase(t);return this.dbId=e.result.dbId,this.log("Database initialized successfully"),t}async getPromiser(){return new Promise(t=>{l.sqlite3Worker1Promiser({onready:e=>t(e)})})}async openDatabase(t){try{const e=await t("open",{filename:`file:${this.dbName}.sqlite3?vfs=opfs`});return this.log("OPFS database opened:",e.result.filename),e}catch(e){console.warn("OPFS is not available, falling back to in-memory database:",e);const i=await t("open",{filename:":memory:"});return this.log("In-memory database opened"),i}}async execute(t,e=[]){try{return await(await this.getPromiserOrThrow())("exec",{sql:t,bind:e,dbId:this.dbId})}catch(i){throw console.error("Error executing SQL:",i),i}}async query(t,e=[]){return(await(await this.getPromiserOrThrow())("exec",{sql:t,bind:e,rowMode:"object",dbId:this.dbId})).result.resultRows||[]}async close(){this.dbId&&(await(await this.getPromiserOrThrow())("close",{dbId:this.dbId}),this.dbId=null,this.dbPromise=null)}addMigrations(t){this.migrations.push(...t),this.migrations.sort((e,i)=>e.version-i.version)}async createMigrationTable(){await this.execute(` CREATE TABLE IF NOT EXISTS migrations ( version INTEGER PRIMARY KEY, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `)}async getCurrentVersion(){var i;return(await this.query("SELECT name FROM sqlite_master WHERE type='table'")).some(n=>n.name==="migrations")&&((i=(await this.query("SELECT MAX(version) as version FROM migrations"))[0])==null?void 0:i.version)||0}async runMigrations(){const t=await this.getCurrentVersion(),e=this.migrations.filter(i=>i.version>t);for(const i of e)this.log(`Running migration to version ${i.version}`),await i.up(this),await this.execute("INSERT INTO migrations (version) VALUES (?)",[i.version]),this.log(`Migration to version ${i.version} completed`)}async getPromiserOrThrow(){if(!this.dbPromise)throw new Error("Database not initialized. Call init() first.");return this.dbPromise}escapeBlob(t){return`X'${Array.from(new Uint8Array(t),e=>e.toString(16).padStart(2,"0")).join("")}'`}async dumpDatabase(t={}){const{compatibilityMode:e=!1,table:i}=t;let n="";e&&(n+=`PRAGMA foreign_keys = OFF; BEGIN TRANSACTION; `);const r=i?"SELECT type, name, sql FROM sqlite_master WHERE type='table' AND name = ?":"SELECT type, name, sql FROM sqlite_master WHERE sql NOT NULL AND name != 'sqlite_sequence'",a=await this.query(r,i?[i]:[]);for(const s of a)if(s.type==="table"){n+=`${s.sql}; `;const o=await this.query(`SELECT * FROM ${s.name}`);for(const h of o){const f=Object.keys(h).join(", "),T=Object.values(h).map(p=>p instanceof Uint8Array?this.escapeBlob(p):p===null?"NULL":typeof p=="string"?`'${p.replace(/'/g,"''")}'`:p).join(", ");n+=`INSERT INTO ${s.name} (${f}) VALUES (${T}); `}n+=` `}if(!i)try{const s=await this.query("SELECT * FROM sqlite_sequence");if(s.length>0){n+=`DELETE FROM sqlite_sequence; `;for(const o of s)n+=`INSERT INTO sqlite_sequence VALUES('${o.name}', ${o.seq}); `;n+=` `}}catch{this.log("sqlite_sequence table does not exist, skipping...")}if(!i)for(const s of a)s.type!=="table"&&(n+=`${s.sql}; `);return e&&(n+=`COMMIT; `),n}async importDump(t,e={}){const{compatibilityMode:i=!1}=e,n=t.split(";").map(r=>r.trim()).filter(Boolean);i||(await this.execute("PRAGMA foreign_keys=OFF"),await this.execute("BEGIN TRANSACTION"));try{const r=await this.query(` SELECT type, name FROM sqlite_master WHERE type IN ('table', 'view', 'index') AND name != 'sqlite_sequence' `);for(const{type:a,name:s}of r)await this.execute(`DROP ${a} IF EXISTS ${s}`);for(const a of n)a!=="COMMIT"&&await this.execute(a);i||(await this.execute("COMMIT"),await this.execute("PRAGMA foreign_keys = ON"))}catch(r){throw i||(await this.execute("ROLLBACK"),await this.execute("PRAGMA foreign_keys = ON")),r}}async dumpTableToCSV(t,e={}){const i=await this.query(`SELECT * FROM ${t}`),n=Object.keys(i[0]||(await this.query(`PRAGMA table_info(${t})`)).reduce((s,o)=>({...s,[o.name]:""}),{})).map(s=>e.quoteAllFields?`"${s}"`:s).join(",");if(i.length===0)return`${n}\r `;const r=s=>{const o=(s==null?void 0:s.toString())||"";return e.quoteAllFields||o.includes(",")||o.includes(` `)||o.includes('"')?`"${o.replace(/"/g,'""')}"`:o},a=i.map(s=>Object.values(s).map(r).join(","));return`${n}\r ${a.join(`\r `)}\r `}async importCSVToTable(t,e){const i=E(e);if(i.length===0)return;const[n,...r]=i,a=n;for(let s=0;s<r.length;s++){const o=r[s];if(o.length!==a.length)throw new Error(`CSV row ${s+2} has ${o.length} fields, but header has ${a.length} columns`);const h=a.map(()=>"?").join(",");await this.execute(`INSERT INTO ${t} (${a.join(",")}) VALUES (${h})`,o)}}async transaction(t){if(this.transactionDepth===0){await this.execute("BEGIN TRANSACTION"),this.transactionDepth=1,this.log("BEGIN TRANSACTION (top-level)");try{const e=await t(this);return await this.execute("COMMIT"),this.log("COMMIT (top-level)"),this.transactionDepth=0,e}catch(e){throw await this.execute("ROLLBACK"),this.log("ROLLBACK (top-level)"),this.transactionDepth=0,e}}else{this.transactionDepth++;const e=`sp_${g++}`;this.savepointStack.push(e),this.log(`BEGIN NESTED TRANSACTION: SAVEPOINT ${e}`),await this.execute(`SAVEPOINT ${e}`);try{const i=await t(this);return this.log(`RELEASE SAVEPOINT ${e}`),await this.execute(`RELEASE SAVEPOINT ${e}`),this.savepointStack.pop(),this.transactionDepth--,i}catch(i){throw this.log(`ROLLBACK TO ${e}`),await this.execute(`ROLLBACK TO ${e}`),this.savepointStack.pop(),this.transactionDepth--,i}}}async rollback(){if(this.transactionDepth<=0)throw new Error("rollback() called but no active transaction exists.");if(this.transactionDepth===1)throw this.log("ROLLBACK (top-level)"),await this.execute("ROLLBACK"),this.transactionDepth=0,new Error("Transaction rolled back (top-level).");{const t=this.savepointStack.pop();throw this.transactionDepth--,t?(this.log(`ROLLBACK TO ${t} (nested)`),await this.execute(`ROLLBACK TO ${t}`),new Error(`Transaction rolled back to savepoint ${t}.`)):new Error("rollback() called but no matching savepoint found.")}}async commit(){if(this.transactionDepth<=0)throw new Error("commit() called but no active transaction exists.");if(this.transactionDepth===1)await this.execute("COMMIT"),this.log("COMMIT (top-level)"),this.transactionDepth=0;else{const t=this.savepointStack.pop();if(this.transactionDepth--,!t)throw new Error("commit() called but no matching savepoint found.");this.log(`RELEASE SAVEPOINT ${t} (nested by user)`),await this.execute(`RELEASE SAVEPOINT ${t}`)}}}c.NeverChangeDB=w,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});