Core-Agentic-ERP / core /data.mjs
fivetech's picture
Create data.mjs
078eb7d verified
Raw History Blame Contribute Delete
3.63 kB
export const APData = {
txDepth: 0,
scan(sql,args=[]){ const q=AP.db.prepare(sql); try {q.bind(args); const out=[]; while(q.step())out.push(q.getAsObject()); return out;} finally {q.free();} },
query(sql,args=[]){
if(typeof sql!=='string'||!sql.trim())throw Error('query requiere una sentencia');
const s=sql.trim();
if(!/^(select|with)\b/i.test(s))throw Error('query solo admite SELECT o WITH');
if(/;\s*\S/.test(s))throw Error('query admite una sola sentencia');
if(!Array.isArray(args))throw Error('query exige argumentos parametrizados');
return APData.scan(s,args);
},
exec(spec){
const {sql,args=[],actor,action,entity='',id,detail={},audit=true}=spec||{};
if(typeof sql!=='string'||!sql.trim())throw Error('exec requiere una sentencia');
const s=sql.trim();
if(!/^(insert|update|delete)\b/i.test(s))throw Error('exec solo admite INSERT, UPDATE o DELETE');
if(/;\s*\S/.test(s))throw Error('exec admite una sola sentencia');
if(!Array.isArray(args))throw Error('exec exige argumentos parametrizados');
if(audit!==false&&(!actor||!action))throw Error('exec exige actor y action para auditar');
return AP.tx(()=>{
AP.db.run(s,args);
const changes=AP.db.getRowsModified();
if(audit!==false)AP.audit(actor,action,String(entity||''),id==null?'':String(id),detail||{});
return changes;
});
},
rows(sql,args=[]){ return APData.query(sql,args); },
one(sql,args=[]){ return APData.query(sql,args)[0]; },
async storage(){ return new Promise((resolve,reject)=>{const r=indexedDB.open('adaptapro-local-v1',1); r.onupgradeneeded=()=>r.result.createObjectStore('database');r.onerror=()=>reject(r.error);r.onsuccess=()=>resolve(r.result);}); },
async loadBytes(){return new Promise((resolve,reject)=>{const t=AP.store.transaction('database','readonly');const r=t.objectStore('database').get('main');r.onsuccess=()=>resolve(r.result);r.onerror=()=>reject(r.error);});},
async persist(){const bytes=AP.db.export();return new Promise((resolve,reject)=>{const t=AP.store.transaction('database','readwrite');t.objectStore('database').put(bytes,'main');t.oncomplete=resolve;t.onerror=()=>reject(t.error);});},
tx(fn){if(APData.txDepth>0){APData.txDepth++;try{return fn();}finally{APData.txDepth--;}}APData.txDepth=1;try{AP.db.run('BEGIN IMMEDIATE');const v=fn();AP.db.run('COMMIT');return v;}catch(e){try{AP.db.run('ROLLBACK');}catch{}throw e;}finally{APData.txDepth=0;}},
async loadSeed(){const r=await fetch('data/seed/demo.json');if(!r.ok)throw Error('No se pudo cargar data/seed/demo.json');return r.json();},
async migrationPlan(){const r=await fetch('data/migrations/index.json');if(!r.ok)throw Error('No se pudo cargar data/migrations/index.json');const plan=await r.json();if(!Array.isArray(plan.migrations)||!plan.migrations.length)throw Error('Plan de migraciones vacío');let prev=0;for(const m of plan.migrations){if(!Number.isInteger(m.n)||m.n<=prev||typeof m.file!=='string')throw Error('Entrada de migración inválida: '+JSON.stringify(m));prev=m.n;}return plan.migrations;},
userVersion(){const r=AP.db.exec('PRAGMA user_version');return r.length?Number(r[0].values[0][0]):0;},
async applyMigrations(){const plan=await AP.migrationPlan();const current=AP.userVersion();let applied=0;for(const m of plan){if(m.n<=current)continue;const res=await fetch(m.file);if(!res.ok)throw Error('No se pudo cargar '+m.file);const sql=await res.text();if(!sql.trim())throw Error('Migración vacía: '+m.file);AP.db.run(sql);AP.db.run('PRAGMA user_version='+m.n);applied++;}return applied;},
};
globalThis.APData = APData;