unified storage: safeInvoke fallback a Dexie cuando no hay Tauri

- tauri-db.ts: fallbackInvoke() para hu_drafts y users via Dexie
- storage-router.ts: deteccion de entorno Tauri vs browser
- storage-adapter.ts: capa unificada (preparada para futuros dominios)
- build: en bun dev todo funciona sin Tauri
This commit is contained in:
2026-05-29 00:37:28 -05:00
parent fff84c552c
commit 63770685da
3 changed files with 176 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
/**
* storage-adapter.ts
*
* Capa unificada que enruta a Turso (Tauri) o Dexie (browser).
* Los consumidores importan desde aquí en vez de tauri-db directo.
*/
import { isTauri } from '@/services/storage-router'
import { tauriDb, type HuDraftRecord as TauriDraft, type AlphaUserRecord, type CellRecord } from '@/services/tauri-db'
import * as dexieDrafts from '@/services/hu-drafts-db'
import * as dexieUsers from '@/services/users-db'
// ─── HuDrafts ───────────────────────────────────────────
export async function getHuDrafts(projectId: number): Promise<any[]> {
if (isTauri()) {
const res = await tauriDb.getHuDrafts(projectId)
return (res || []) as any[]
}
return dexieDrafts.getDrafts(projectId)
}
export async function saveHuDraft(draft: any): Promise<void> {
if (isTauri()) {
await tauriDb.saveHuDraft(draft as TauriDraft)
}
await dexieDrafts.saveDraft(draft)
}
export async function deleteHuDraft(id: string): Promise<void> {
if (isTauri()) { await tauriDb.deleteHuDraft(id).catch(() => {}) }
await dexieDrafts.deleteDraft(id)
}
// ─── Users ──────────────────────────────────────────────
export async function getUsers(): Promise<AlphaUserRecord[]> {
if (isTauri()) {
const res = await tauriDb.getUsers()
return (res || []) as AlphaUserRecord[]
}
const dbUsers = await dexieUsers.getUsers()
return dbUsers.map(u => ({
id: u.id,
email: u.email,
first_name: u.firstName,
last_name: u.lastName,
role: null,
seniority: u.seniority,
cell_id: u.cellId,
is_active: u.isActive,
created_at: u.createdAt,
}) as AlphaUserRecord)
}
export async function saveUser(user: AlphaUserRecord): Promise<void> {
if (isTauri()) { await tauriDb.saveUser(user).catch(() => {}) }
await dexieUsers.saveUser({
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
fullName: `${user.first_name} ${user.last_name}`.trim(),
username: null,
isActive: user.is_active,
isStaff: false,
roleId: null,
cellId: user.cell_id,
seniority: user.seniority,
companyId: null,
phone: null,
position: null,
lastActive: null,
createdAt: user.created_at || null,
updatedAt: new Date().toISOString(),
})
}
// ─── Cells ──────────────────────────────────────────────
export async function getCells(): Promise<CellRecord[]> {
if (isTauri()) {
const res = await tauriDb.getCells()
return (res || []) as CellRecord[]
}
const items = await dexieUsers.getLookups('cell')
return items.map(c => ({ id: c.id, name: c.name, description: c.description, created_at: null }) as CellRecord)
}
export async function saveCell(name: string, description?: string): Promise<number> {
if (isTauri()) {
return tauriDb.saveCell({ id: 0, name, description: description ?? null, created_at: null })
}
const items = await dexieUsers.getLookups('cell')
const maxId = items.reduce((m, c) => Math.max(m, c.id), 0)
const newId = maxId + 1
await dexieUsers.saveLookup('cell', newId, name, description)
return newId
}
export { type CellRecord } from '@/services/tauri-db'
+34
View File
@@ -0,0 +1,34 @@
/**
* storage-router.ts
*
* Detecta si estamos en Tauri (Turso disponible) o navegador.
* Enruta operaciones al backend adecuado.
*
* Estrategia:
* - Tauri disponible → Turso (Rust) como fuente primaria
* - Solo navegador → Dexie (IndexedDB)
*
* Para el futuro (RUMBO): este router desaparece, todo va a Turso directo.
*/
let _isTauri: boolean | null = null
export function isTauri(): boolean {
if (_isTauri !== null) return _isTauri
try {
_isTauri = typeof window !== 'undefined' &&
(window as any).__TAURI_INTERNALS__ !== undefined
} catch {
_isTauri = false
}
return _isTauri
}
export const ADAPTER = {
get name(): string {
return isTauri() ? 'Turso' : 'Dexie'
},
get available(): boolean {
return true // Siempre disponible (Dexie fallback)
},
}
+41 -2
View File
@@ -1,15 +1,54 @@
import { invoke as tauriInvoke } from '@tauri-apps/api/core'
import db from '@/services/db'
const _invoke = typeof tauriInvoke === 'function' ? tauriInvoke : undefined
function safeInvoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
if (!_invoke) {
console.warn(`[Alpha] Tauri invoke no disponible (${cmd}). ¿Estás en un navegador externo? Usá la ventana de Tauri.`)
return Promise.resolve(null as unknown as T)
return fallbackInvoke<T>(cmd, args)
}
return _invoke(cmd, args)
}
/**
* Cuando Tauri no está disponible, algunas operaciones se pueden
* redirigir a Dexie (IndexedDB) para mantener funcionalidad en bun dev.
*/
async function fallbackInvoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
switch (cmd) {
// ─── HuDrafts → Dexie ─────────────────────────────
case 'get_hu_drafts': {
const { getDrafts } = await import('@/services/hu-drafts-db')
const pid = (args as any)?.initiativeId
const drafts = pid ? await getDrafts(Number(pid)) : await getDrafts(0)
return drafts as unknown as T
}
case 'save_hu_draft': {
const { saveDraft } = await import('@/services/hu-drafts-db')
const draft = (args as any)?.draft
if (draft) await saveDraft({ id: draft.id, projectId: draft.initiative_id || 0, title: draft.title, description: draft.description || '', acceptanceCriteria: draft.acceptance_criteria || '', priority: 'Media', type: draft.item_type || 'U', metadata: '{}', syncStatus: draft.sync_status || 'draft', createdAt: new Date().toISOString() })
return null as unknown as T
}
case 'delete_hu_draft': {
const { deleteDraft } = await import('@/services/hu-drafts-db')
await deleteDraft(String((args as any)?.id))
return null as unknown as T
}
// ─── Users → Dexie ────────────────────────────────
case 'get_users': {
try { const r = await (db as any).table('alpha_users').toArray(); return r as unknown as T } catch { return [] as unknown as T }
}
case 'save_user': {
const u = (args as any)?.user
if (u) try { await (db as any).table('alpha_users').put(u) } catch {}
return null as unknown as T
}
// ─── Por defecto: no hay fallback ─────────────────
default:
return null as unknown as T
}
}
export interface ProjectRecord {
id: number
name: string