Spaces:
Running
Running
Create data.mjs
Browse files- core/data.mjs +38 -0
core/data.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const APData = {
|
| 2 |
+
txDepth: 0,
|
| 3 |
+
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();} },
|
| 4 |
+
query(sql,args=[]){
|
| 5 |
+
if(typeof sql!=='string'||!sql.trim())throw Error('query requiere una sentencia');
|
| 6 |
+
const s=sql.trim();
|
| 7 |
+
if(!/^(select|with)\b/i.test(s))throw Error('query solo admite SELECT o WITH');
|
| 8 |
+
if(/;\s*\S/.test(s))throw Error('query admite una sola sentencia');
|
| 9 |
+
if(!Array.isArray(args))throw Error('query exige argumentos parametrizados');
|
| 10 |
+
return APData.scan(s,args);
|
| 11 |
+
},
|
| 12 |
+
exec(spec){
|
| 13 |
+
const {sql,args=[],actor,action,entity='',id,detail={},audit=true}=spec||{};
|
| 14 |
+
if(typeof sql!=='string'||!sql.trim())throw Error('exec requiere una sentencia');
|
| 15 |
+
const s=sql.trim();
|
| 16 |
+
if(!/^(insert|update|delete)\b/i.test(s))throw Error('exec solo admite INSERT, UPDATE o DELETE');
|
| 17 |
+
if(/;\s*\S/.test(s))throw Error('exec admite una sola sentencia');
|
| 18 |
+
if(!Array.isArray(args))throw Error('exec exige argumentos parametrizados');
|
| 19 |
+
if(audit!==false&&(!actor||!action))throw Error('exec exige actor y action para auditar');
|
| 20 |
+
return AP.tx(()=>{
|
| 21 |
+
AP.db.run(s,args);
|
| 22 |
+
const changes=AP.db.getRowsModified();
|
| 23 |
+
if(audit!==false)AP.audit(actor,action,String(entity||''),id==null?'':String(id),detail||{});
|
| 24 |
+
return changes;
|
| 25 |
+
});
|
| 26 |
+
},
|
| 27 |
+
rows(sql,args=[]){ return APData.query(sql,args); },
|
| 28 |
+
one(sql,args=[]){ return APData.query(sql,args)[0]; },
|
| 29 |
+
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);}); },
|
| 30 |
+
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);});},
|
| 31 |
+
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);});},
|
| 32 |
+
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;}},
|
| 33 |
+
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();},
|
| 34 |
+
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;},
|
| 35 |
+
userVersion(){const r=AP.db.exec('PRAGMA user_version');return r.length?Number(r[0].values[0][0]):0;},
|
| 36 |
+
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;},
|
| 37 |
+
};
|
| 38 |
+
globalThis.APData = APData;
|