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:
@@ -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'
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user