fivetech commited on
Commit
078eb7d
路
verified 路
1 Parent(s): 4130093

Create data.mjs

Browse files
Files changed (1) hide show
  1. 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;