2 puntos por GN⁺ 2024-10-16 | 1 comentarios | Compartir por WhatsApp
  • Es un hub que reúne en un solo lugar la documentación de la API de WebAssembly y JavaScript para desarrolladores que quieran usar sqlite3 directamente en navegadores modernos con soporte WASM
  • Para empezar rápido, se puede arrancar con el HOWTO de 3 pasos, y revisar las rutas de instalación y despliegue con la documentación de descargas, módulo npm, compilación y detalles de Emscripten
  • La documentación de la API cubre métodos de carga, recetas de código cliente, almacenamiento persistente, estructuras C en la API de JS, y cómo crear tablas virtuales y funciones con valor de tabla
  • También organiza casos de uso reales como SQLime, Evolu, SQLiteNext, sqlite-wasm-esm y sqlite-wasm-http, junto con proyectos previos como sql.js, wa-sqlite y absurd-js
  • Conecta materiales sobre SQLite en el navegador, OPFS, IndexedDB y WASI para comparar rápidamente opciones de implementación al usar SQLite en entornos web

Alcance de la documentación de sqlite3 WASM

Casos de uso y ecosistema relacionado

  • Proyectos de terceros que usan este proyecto

    • SQLime: ofrece una interfaz de navegador de bases de datos
    • Evolu: plataforma local-first enfocada en privacidad, facilidad de uso y ausencia de dependencia de proveedor
    • SQLiteNext: demo para integrar este proyecto con next.js
    • sqlite-wasm-esm: muestra cómo usar este proyecto con la toolchain de Vite
    • sqlite-wasm-http: SQLite VFS que ofrece acceso de solo lectura a bases de datos servidas directamente por HTTP
  • Trabajo previo y proyectos cercanos

    • sql.js de Alon Zakai: es conocido como el primer caso de uso directo de sqlite3 en el navegador, excluyendo WebSQL, y se remonta a un commit de 2012
    • wa-sqlite de Roy Hashimoto: es conocido como la primera implementación de OPFS storage para bases de datos sqlite3, e incluye trabajo relacionado con SQLite VFS del lado de JS
    • absurd-js de James Long: muestra una forma de almacenar una base de datos sqlite3 dentro de una base de datos IndexedDB
    • postgres-wasm: ejecuta un Postgres database server en el navegador
    • Jaccwabyt: pequeña librería JS para manipular con código JS estructuras C alojadas en WASM, creada para apoyar la implementación de OPFS sqlite3_vfs de este proyecto
    • CoWasm: “Collaborative WebAssembly for Servers and Browsers”, e incluye una compilación WASM de la aplicación sqlite3 shell en su demo app
    • absurder-js: proyecto en el que Nicholas G. Piesco replantea absurd-js, y también tiene una presentación en el SQLite forum
    • sqlite-wasm-rs: ofrece bindings de SQLite en Rust para compilaciones wasm32-unknown-unknown
  • Documentación externa y artículos

1 comentarios

 
GN⁺ 2024-10-16
Opiniones de Hacker News
  • Sería interesante ejecutar SQLite en memoria en el navegador y transmitir una copia del log WAL al servidor, de forma similar a como lo hacen Litestream y Cloudflare Durable Objects
    Se podría usar WebSocket, o también parece posible hacerlo con POST esporádicos mediante fetch(). En visitas posteriores, se usarían los datos del lado del servidor para volver a poblar el SQLite del cliente
    Sin embargo, viendo esta publicación del foro de SQLite, parece que el build WASM predeterminado de SQLite no incluye el modo WAL, así que haría falta un build personalizado

    • Aquí hay varios niveles de “eso no funciona así”
      SQLite en memoria no usa WAL, y Wasm en el navegador no soporta cosas como la API de memoria compartida que espera el modo WAL de SQLite
      Litestream requiere una configuración de WAL muy precisa, que por casualidad encaja bien en la configuración predeterminada de SQLite nativo, pero es difícil de reproducir en Wasm
      Cloudflare Durable Objects quizá se haya inspirado en Litestream, pero en la práctica funciona de manera bastante distinta; lo mismo ocurre con LiteFS o Turso
      La idea general de transmitir los cambios de SQLite sí es posible, pero implica bastante trabajo, y el modelo de concurrencia de Wasm en el navegador hace que la implementación sea complicada
      Escribí ese post del foro hace tiempo y logré hacer funcionar WAL en builds de SQLite Wasm del lado del servidor, pero no era una opción utilizable ni viable para el navegador
    • En agosto de 2022 hice una prueba de concepto parecida a esto y mandé un DM por Twitter, pero no recibí respuesta. Mi handle es justjs14
      Tendría que buscar el código de entonces, pero compilaba SQLite con un VFS a Wasm en el navegador, abría la DB, hacía cambios y permitía hacer push/pull del WAL al servidor. También era posible sincronizar entre navegadores, ya fuera manualmente o con WebRTC, y si le dabas un token de GitHub al cliente del navegador, funcionaba incluso en GitHub Pages
      Si hay interés, podría rehacerlo desde cero para que vuelva a ejecutarse. En ese momento experimenté bastante con este enfoque y creo que puede ser útil al menos para algunas aplicaciones
      También hay una demo solo con función de pull aquí: https://just.billywhizz.io/sqlite/demo/#https://just.billywhizz.io/sqlite/demo/db/chinook.db
    • A diferencia de otras ideas aquí, creo que transmitir una copia del WAL es técnicamente posible. Eso sí, habría que implementarlo dentro del VFS
      La “memoria compartida” puede ser SharedArrayBuffer, y si se abre una sola conexión a la DB a la vez, también puede ser un buffer normal. Por ejemplo, se puede hacer así dentro de un SharedWorker, que ya es algo bastante común
      La implementación no sería simple, pero claramente es posible. De hecho, el problema más grande es que captura cambios a nivel de bloque, no a nivel de fila
      Por eso sirve para replicar toda la DB, pero es difícil hacer sincronización parcial, como compartir solo algunas filas con otros usuarios
      Si se necesitan cambios a nivel de fila para sincronización parcial, habría que usar triggers o algo como la extensión de sesiones de SQLite: https://sqlite.org/sessionintro.html
      En PowerSync usamos triggers del lado del cliente, y no hemos encontrado grandes desventajas aparte del trabajo de mantenimiento de los triggers
    • ¿Estás pensando en algo como https://electric-sql.com/?
    • No estoy seguro de haber entendido del todo esta idea de “hidratación”, pero últimamente me ha interesado usar Wasm para ejecutar parte de la lógica de backend en el navegador
      Por ejemplo, si se ejecuta con Wasm el renderizado de plantillas comunes o algunos endpoints de Htmx, incluso un framework web tradicional del lado del servidor podría ofrecer funcionalidad offline con un almacenamiento SQLite Wasm similar a lo que menciona el OP
      Ejemplo completo con Django: https://github.com/m-butterfield/django_webassembly
      Ejemplo mínimo en Rust que separa cliente/servidor con macros: https://github.com/richardanaya/wasm-service
      Clojure Electric hace algo parecido sin Wasm, usando más macros: https://github.com/hyperfiddle/electric
      También hay una herramienta web reciente de Microsoft que empieza con B
      En los últimos años, una cantidad sorprendentemente grande del ecosistema de Python obtuvo soporte para Wasm, así que se volvió utilizable para este enfoque. No solo las bibliotecas obviamente relacionadas con machine learning, sino también bibliotecas de juegos como Pygame y Pyxel; además, como SDL2 agregó soporte, parece que el soporte para Kivy, una biblioteca de UI multitouch, podría llegar pronto
      Parece que se abren muchas posibilidades nuevas para ejecutar código en cualquier computadora y, si hace falta, evitar burocracias de plataforma o comisiones dentro del sandbox de un navegador web
  • La documentación dice que estos componentes aparecieron por primera vez como beta pública en la versión 3.40, y que la API se estabilizaría en la 3.41 según el feedback de la comunidad.
    Pero la versión más reciente de SQLite es la 3.46.1 del 2024-08-13, así que probablemente la API ya se estabilizó, pero la documentación no se actualizó.
    Creo que sería un buen medio de distribución si el equipo de SQLite sacara un paquete oficial de npm que incluyera la versión WASM. Vi después, según una respuesta, que ya existe.
    Personalmente, mi SQLite-in-WASM favorito sigue siendo la variante de Pyodide. Existía desde mucho antes que la implementación oficial de SQLite, y si usas Pyodide, WASM SQLite viene incluido como parte de la biblioteca estándar de Python.
    Así lo uso en https://lite.datasette.io/, y también se puede probar en https://pyodide.org/en/stable/console.html.
    Si ejecutas select sqlite_version() con sqlite3.connect(':memory:'), devuelve la 3.39.0 del 2022-06-25, así que parece que Pyodide necesita actualizar la versión. Parece que esta versión la hereda de emscripten: https://github.com/emscripten-core/emscripten/blob/main/tools/ports/sqlite3.py

    • Sí, la API se estabilizó. La próxima vez que esté frente a la computadora intentaré actualizar esa frase.
      Sin embargo, el equipo de SQLite no va a sacar un paquete oficial de npm. Solo distribuimos JS puro, y nos negamos rotundamente a meternos en la madriguera de conejo que implica dar soporte a herramientas externas al lenguaje. Ningún miembro del proyecto usa esas herramientas.
      Damos soporte a un build de npm “aprobado oficialmente” mantenido por Thomas Steiner, pero no apuntamos activamente a ningún framework de JS como objetivo de desarrollo.
      Si damos soporte directo a un framework específico, incluido npm, puede dar la impresión de que avalamos ese framework, y evitamos activamente avalar proyectos de terceros.
    • Creo que el equipo de SQLite ya lleva un tiempo haciendo eso. Desde un módulo JS puedes traer @sqlite.org/sqlite-wasm con jsdelivr y cargar sqlite3-bundler-friendly.mjs y sqlite3.wasm.
      Con eso puedes usar la API en C de SQLite y la API orientada a objetos oo1, y también funciona con una etiqueta de script normal type=module o en Deno.
      Aquí hay un HTML de ejemplo: https://github.com/Ciantic/experimenting-sqlite-wasm/blob/main/standalone.html
    • https://github.com/sqlite/sqlite-wasm
      sqlite-wasm carga mucho más rápido que Pyodide, así que si no necesitas Python, la primera opción es mejor.
    • DuckDB WASM se puede usar independientemente de Pyodide y también permite extensiones de SQLite.
      Aunque parece bastante limitado. No tiene sqlite_version(), así que ni siquiera pude verificar la versión, y la versión del repositorio es la 3.38.1, bastante antigua.
      Actualmente, la shell web de DuckDB no puede cargar la extensión de SQLite porque todavía no se ha lanzado la extensión de SQLite para la 1.1.2, que salió ayer. La versión anterior funciona con la edición WASM actualizada recientemente.
      También se pueden agregar extensiones espaciales, GDAL, búsqueda vectorial, etc.: https://github.com/duckdb/duckdb-wasm/releases/tag/v1.29.0
      Crear tu propia “shell web de SQL” tampoco fue muy difícil, pero la documentación no estaba lo suficientemente completa.
      https://github.com/duckdb/sqlite_scanner/blob/main/src/sqlite/sqlite3.h
    • Puede ser útil leer las FAQ de esa página.
  • Para usarlo en Go, me gusta mucho el paquete Wasm SQLite de ncruces https://github.com/ncruces/go-sqlite3.
    El paquete de Go de cznic también es excelente, pero esta versión Wasm funciona bien incluso en entornos como OpenBSD.

  • WebSQL simplemente debería haber sido SQLite, y si lo hubiera sido, el enfoque offline-first y el ecosistema general de almacenamiento de apps habrían estado mucho mejor.
    ¿Todavía habrá alguna posibilidad? En lugar de abstraer SQLite y sobrespecificarlo, ¿no podrían especificar la versión de la API de SQLite que los navegadores deben soportar y subir la versión periódicamente?

    • La idea de “subir la versión periódicamente” en los navegadores probablemente traería bastantes problemas.
      Una de las bases de la Web es que, en lo posible, lo existente debe seguir funcionando, y ese tipo de cambios incompatibles es difícil.
      Aunque la especificación enumerara varias versiones y dejara que la página web eligiera, los navegadores cargarían con la complicada responsabilidad de mantener versiones antiguas de SQLite que quizá ya no tengan soporte. Además, cada versión ocupa alrededor de 1 MB.
      Si es así, me parece mejor dejar que cada sitio web asuma el costo de su propia elección, como ahora.
      Si la preocupación es descargar repetidamente recursos comunes, es un problema que otros CDN ya han aceptado. Una solución de caché compartido para dependencias comunes tendría mucho más valor que algo limitado solo a SQLite.
      El enfoque actual me parece mejor que una versión provista por el navegador.
    • Me gusta SQLite, pero hay que reconocer que tiene varios comportamientos peculiares como para meterlo en todos los navegadores. Por eso creo que sería mejor algo como una API similar a SQLite.
    • Creo que fue una suerte que WebSQL no se consolidara.
      Ahora, cuando lo necesitas, puedes traer la versión más reciente de SQLite como un blob WASM comprimido de 410 KB. Si hubiéramos quedado atados a la versión de SQLite impuesta por el navegador, quizá ahora estaríamos usando una versión de hace 10 años.
  • Al hojear la página rápido, no me queda claro el caso de uso.
    Más concretamente, ¿esto puede ser un reemplazo de IndexedDB? ¿Los datos persisten, o hay que guardar directamente el archivo SQLite en la File System API o en IndexedDB/localStorage?

    • Según la página de introducción, uno de los objetivos de este proyecto es admitir almacenamiento persistente del lado del cliente con APIs de JS disponibles en la medida de lo posible.
      Actualmente incluye almacenamiento mediante Origin-Private FileSystem (OPFS) y, aunque de forma muy limitada, backends de window.localStorage y window.sessionStorage.
  • Últimamente me interesa mucho el ámbito local-first, pero comparado con usar las APIs de almacenamiento integradas del navegador, especialmente IndexedDB, embeber SQLite se siente bastante pesado.
    Las principales bibliotecas open source también parecen usar, en general, las APIs integradas.
    Me gustaría ver una solución open source que ofrezca una API similar a SQLite y sincronización sobre el almacenamiento nativo del navegador, en lugar de intentar meter otro ejecutable con WebAssembly.

    • SQL puede considerarse más fácil de usar que la API de IndexedDB y, por ejemplo, usar una cláusula WHERE en vez de filtros con if() también podría usar menos memoria y CPU.
    • Quizá te interese TinyBase. Es una biblioteca que hice yo.
  • Si lo descargas desde la página oficial y eliminas los archivos mjs y los archivos “bundler-friendly”, la dependencia mínima de SQLite Wasm queda en unos 1.3 MB.
    Para una app de navegador parece algo grande, pero hoy Wasm también se ejecuta en otros lugares, así que en esos entornos puede tener más sentido.

    • Aun así, comprime bien. sqlite3.js y el Wasm pesan 1.3 MB sin comprimir, pero si se minimiza el JS y se comprimen ambos archivos con Brotli, baja a 410 KB.
    • Creo que 1.3 MB es un tamaño bastante razonable para una app web moderna. Sobre todo porque se cacheará después de la primera visita al sitio.
      Si solo vas a guardar preferencias de usuario, obviamente no hace falta descargar SQLite, pero si la tarea se beneficia de tener una base de datos completa, no tiene sentido preocuparse demasiado por 1 MB y terminar reinventando la rueda.
      Si el otro comentario tiene razón, en la red ni siquiera serían 1.3 MB para empezar.
    • Es un factor a considerar junto con el hecho de que el navegador ya trae IndexedDB integrado.
      Aun así, en apps de navegador donde el código de la aplicación y los datos ya son grandes, como apps parecidas a Figma o Photoshop, o apps de machine learning, 1.3 MB quizá no sea una carga adicional importante.
      También vale la pena considerar que el parseo de Wasm es bastante más rápido que el de JS. Ahora no encuentro la fuente de esa afirmación, pero había al menos un buen artículo relacionado.
      https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
    • Hay que tener en cuenta el modelo de sandbox de WebAssembly. En teoría, un programa —en este caso SQLite— puede ejecutarse en cualquier lugar donde sea apropiado hacerlo.
      Puede ser local, en un servidor central o en el edge cercano.
    • Del lado del servidor, es muy probable que se necesite una compilación Wasm de SQLite separada que maneje de otra manera la concurrencia y el bloqueo de archivos.
      Además, WASI todavía está lejos de ser la respuesta. La amalgamación de SQLite compila bien con WASI, pero la concurrencia sigue siendo un problema sin resolver.
      Tuve que crear un VFS desde cero para dejar un driver de SQLite basado en Wasm en un estado utilizable.
      https://github.com/ncruces/go-sqlite3/blob/main/vfs/README.md
  • Durante un tiempo intenté hacer que esto funcionara en el ecosistema Rust, pero parecía que los wrappers SQL de blessed.rs, rusqlite y sqlx, todavía no podían aprovecharlo.
    También era difícil envolverlo directamente, porque no encontré cómo hacer que el código Wasm de emscripten funcionara bien con wasm32-unknown-unknown.
    Al final se necesita algún tipo de wrapper JS, y entonces hay que implementar en JS la interfaz que esperan esos crates y exponerla. Si esto se resuelve en Rust, sería realmente genial.

    • Estoy trabajando en algo relacionado. Si te interesa o quieres contribuir, puedes participar aquí: https://github.com/rhashimoto/wa-sqlite/discussions/154
      En esencia, hay que importar en wasm-bindgen una compilación emscripten de SQLite mediante headers extern C, compilarla en modo multihilo para permitir acceso a shared array buffers y reimplementar el VFS en Rust.
      Después se podría acceder a las filas de SQLite como bytes Wasm crudos y crear un wrapper o una integración al estilo Rust-SQLite.
      Seguirían faltando comodidades como connection pooling, pero en Wasm probablemente convenga usar la DB en modo exclusivo.
    • Aquí hay algo que hizo funcionar esta compilación de SQLite con Diesel SQLite en Rust: https://github.com/xmtp/diesel-wasm-sqlite
      Esta semana estoy preparando el release 0.2 y voy a pulir algunos problemas. Fuera de eso, se puede usar Diesel ORM prácticamente como en nativo.
    • Hay buenas noticias sobre la parte de hacer encajar bien el código Wasm de emscripten con wasm32-unknown-unknown.
      Hay un plan para que Emscripten y Rust funcionen bien juntos en Wasm: https://github.com/rustwasm/wasm-bindgen/pull/4014#issuecomment-2356908451
    • Yo también llevo bastante tiempo esperando esto. Hay algunos PR que llevan como un año en pausa, y he visto pruebas de concepto, pero estaban lejos de ser algo realmente usable.
  • Creé una biblioteca SQLite que se ejecuta en una JVM pura usando la compilación Wasm de SQLite y el runtime Chicory: https://github.com/dylibso/sqlite-zero
    Es más un experimento que algo con el objetivo de estar listo para producción, pero creo que puede ser útil para llevar una herramienta SQLite sin dependencias al ecosistema JVM

    • ¿Cómo funciona el acceso al sistema de archivos? ¿Es WASI?
  • Pregunta general: ¿en qué situaciones conviene más enviar toda la DB y hacer que el navegador procese las consultas?
    Dicho de otra forma, ¿cuándo se usaría esto para mejorar la experiencia de usuario frente al modelo tradicional de una DB alojada en el servidor?

    • En mi caso, lo uso en un sitio web con hosting estático, así que una base de datos alojada en el servidor no era una opción desde el principio
      Con el driver adecuado, se pueden transmitir por streaming los fragmentos necesarios de la base de datos en vez de enviar la base completa: https://github.com/mmomtchev/sqlite-wasm-http
      Se puede hacer búsqueda de texto completo de SQLite sin descargar toda la base de datos FTS. Si el término de búsqueda es lo bastante corto, igual se descarga la mayor parte, pero no todo
    • Al ver el título de este artículo, lo que se me vino intuitivamente a la mente fue algo como la forma en que PouchDB y CouchDB interactúan en apps offline-first
      https://medium.com/offline-camp/couchdb-pouchdb-and-hoodie-as-a-stack-for-progressive-web-apps-a6078a985f18
    • Para uso offline, puede ser bueno cuando se manejan grandes volúmenes de datos
      Puede ir desde bibliotecas de audio hasta software de modelado 3D. Se pueden crear cambios localmente, persistirlos y luego sincronizarlos con el servidor de forma periódica o cuando se vuelva a estar en línea
    • Estoy creando dashboards que se comunican directamente con varios servicios. En mi servidor solo hay una cantidad mínima de datos para control de acceso y gestión de tokens; los datos reales no pasan por mi servidor
      Este tipo de apps son buenas candidatas para una DB embebida del lado del cliente