2 points par GN⁺ 2024-10-16 | 1 commentaires | Partager sur WhatsApp
  • Un hub qui rassemble en un seul endroit la documentation WebAssembly et API JavaScript pour les développeurs qui veulent utiliser directement sqlite3 dans les navigateurs modernes prenant en charge WASM
  • Pour une prise en main rapide, commencez par le HOWTO en 3 étapes ; les documents sur le téléchargement, le module npm, la compilation et les détails d’Emscripten permettent de vérifier les chemins d’installation et de déploiement
  • La documentation de l’API couvre les modes de chargement, les recettes de code client, le stockage persistant, les structures C dans l’API JS, jusqu’à l’écriture de tables virtuelles et de fonctions à valeur de table
  • Elle recense aussi des cas d’usage réels comme SQLime, Evolu, SQLiteNext, sqlite-wasm-esm et sqlite-wasm-http, ainsi que des projets précurseurs tels que sql.js, wa-sqlite et absurd-js
  • Des ressources liées à SQLite dans le navigateur, OPFS, IndexedDB et WASI sont reliées afin de comparer rapidement les options d’implémentation pour utiliser SQLite dans un environnement web

Périmètre de la documentation WASM de sqlite3

Cas d’usage et écosystème lié

  • Projets tiers utilisant ce projet

    • SQLime : fournit une interface de navigateur de base de données
    • Evolu : plateforme local-first axée sur la confidentialité, la facilité d’utilisation et l’absence de dépendance à un fournisseur
    • SQLiteNext : démonstration d’intégration de ce projet avec next.js
    • sqlite-wasm-esm : montre comment utiliser ce projet avec la toolchain Vite
    • sqlite-wasm-http : VFS SQLite fournissant un accès en lecture seule à une base de données servie directement via HTTP
  • Travaux précurseurs et projets adjacents

    • sql.js d’Alon Zakai : connu comme le premier cas d’utilisation directe de sqlite3 dans le navigateur, à l’exception de WebSQL, et remontant à un commit de 2012
    • wa-sqlite de Roy Hashimoto : connu comme la première implémentation du stockage OPFS pour une base de données sqlite3, avec également des travaux liés au VFS SQLite côté JS
    • absurd-js de James Long : montre une approche consistant à stocker une base de données sqlite3 dans une base IndexedDB
    • postgres-wasm : exécute un serveur de base de données Postgres dans le navigateur
    • Jaccwabyt : petite bibliothèque JS permettant de manipuler depuis du code JS des structures C hébergées dans WASM, créée pour aider à l’implémentation OPFS sqlite3_vfs de ce projet
    • CoWasm : « Collaborative WebAssembly for Servers and Browsers », dont la demo app inclut un build WASM de l’application shell sqlite3
    • absurder-js : projet dans lequel Nicholas G. Piesco réimagine absurd-js, avec aussi une annonce sur le forum SQLite
    • sqlite-wasm-rs : fournit des bindings Rust SQLite pour les builds wasm32-unknown-unknown
  • Documentation et articles externes

1 commentaires

 
GN⁺ 2024-10-16
Avis sur Hacker News
  • Ce serait intéressant de faire tourner SQLite en mémoire dans le navigateur et de streamer une copie du journal WAL vers un serveur, un peu comme le font Litestream et Cloudflare Durable Objects
    On pourrait utiliser WebSocket, ou même des POST fetch() intermittents. Lors des visites suivantes, les données côté serveur serviraient ensuite à réhydrater le SQLite côté client
    Cela dit, d’après ce billet du forum SQLite, le build SQLite WASM par défaut n’inclut pas le mode WAL, donc il faudrait probablement un build personnalisé

    • Il y a plusieurs niveaux de « ça ne fonctionne pas comme ça » ici
      SQLite en mémoire n’utilise pas WAL, et le Wasm dans le navigateur ne prend pas en charge des choses comme l’API de mémoire partagée attendue par le mode WAL de SQLite
      Litestream exige une configuration WAL très précise, qui se trouve fonctionner avec la configuration native par défaut de SQLite, mais qui est difficile à reproduire en Wasm
      Cloudflare Durable Objects a peut-être été inspiré par Litestream, mais son fonctionnement réel est assez différent, et c’est aussi le cas de LiteFS ou Turso
      L’idée générale de streamer les changements SQLite est possible, mais représente beaucoup de travail, et le modèle de concurrence du Wasm dans le navigateur rend l’implémentation délicate
      J’avais écrit ce billet de forum à l’époque et j’ai bien fait fonctionner WAL dans des builds Wasm SQLite côté serveur, mais ce n’était ni une option vraiment utilisable ni réellement viable dans le navigateur
    • En août 2022, j’ai créé une preuve de concept assez similaire et envoyé un DM Twitter, mais je n’ai pas reçu de réponse. Mon handle est justjs14
      Il faudrait que je retrouve le code de l’époque, mais il compilait SQLite avec un VFS en Wasm dans le navigateur, ouvrait la DB, permettait de la modifier, puis de pousser/tirer le WAL vers un serveur. La synchronisation entre navigateurs était possible, manuellement ou via WebRTC, et cela fonctionnait même sur GitHub Pages si l’on donnait un token GitHub au client navigateur
      Si ça intéresse quelqu’un, je peux essayer de le remettre en état de marche depuis zéro. J’ai beaucoup expérimenté avec cette approche à l’époque, et je pense qu’elle peut être utile au moins pour certaines applications
      Il y a aussi une démo avec uniquement la fonction de pull ici : https://just.billywhizz.io/sqlite/demo/#https://just.billywhizz.io/sqlite/demo/db/chinook.db
    • Contrairement à d’autres idées ici, je pense que le streaming d’une copie du WAL est techniquement possible. Il faut simplement l’implémenter dans le VFS
      La « mémoire partagée » peut être un SharedArrayBuffer, et si l’on n’ouvre qu’une seule connexion à la DB à la fois, un buffer classique peut aussi suffire. Par exemple, on peut déjà faire cela dans un SharedWorker, ce qui est assez courant
      L’implémentation ne serait pas simple, mais elle est clairement possible. En pratique, le plus gros problème est qu’on capture des changements au niveau des blocs, pas au niveau des lignes
      C’est donc utilisable pour répliquer toute la DB, mais difficile pour une synchronisation partielle du type partager seulement certaines lignes avec d’autres utilisateurs
      Si l’on a besoin de changements au niveau des lignes pour une synchronisation partielle, il faut utiliser des triggers ou quelque chose comme l’extension sessions de SQLite : https://sqlite.org/sessionintro.html
      Chez PowerSync, nous utilisons des triggers côté client, et à part le travail de maintenance des triggers, nous n’avons pas trouvé de gros inconvénient
    • Tu penses à quelque chose comme https://electric-sql.com/ ?
    • Je ne suis pas sûr de comprendre complètement cette idée d’« hydratation », mais récemment je me suis intéressé à l’utilisation de Wasm pour exécuter une partie de la logique backend dans le navigateur
      Par exemple, si l’on fait tourner le rendu de templates classique ou certains endpoints Htmx en Wasm, même un framework web côté serveur classique pourrait fournir des fonctionnalités hors ligne avec un stockage SQLite Wasm similaire à ce que décrit l’OP
      Exemple Django complet : https://github.com/m-butterfield/django_webassembly
      Exemple Rust minimal avec macros pour séparer client/serveur : https://github.com/richardanaya/wasm-service
      Clojure Electric fait quelque chose de similaire sans Wasm, avec davantage de macros : https://github.com/hyperfiddle/electric
      Il y a aussi un outil web récent de Microsoft dont le nom commence par B
      Ces dernières années, une partie étonnamment large de l’écosystème Python a gagné une prise en charge de Wasm, ce qui la rend exploitable pour cette approche. Pas seulement les bibliothèques clairement liées au machine learning, mais aussi des bibliothèques de jeux comme Pygame et Pyxel ; et comme SDL2 a ajouté ce support, la prise en charge de Kivy, une bibliothèque d’UI multitouch, semble également envisageable à court terme
      On voit apparaître beaucoup de nouvelles possibilités pour exécuter du code sur n’importe quel ordinateur et, si nécessaire, dans le sandbox d’un navigateur web afin d’éviter la bureaucratie des plateformes et leurs commissions
  • La documentation indique que ces composants sont apparus pour la première fois en bêta publique dans la version 3.40, et que l’API devait être stabilisée dans la 3.41 en fonction des retours de la communauté.
    Or la dernière version de SQLite est la 3.46.1 du 13/08/2024 ; l’API est donc probablement déjà stabilisée, mais la documentation ne semble pas avoir été mise à jour.
    Ce serait un bon moyen de distribution si l’équipe SQLite publiait un package npm officiel regroupant la version WASM. En regardant plus tard, d’après les réponses, il en existe déjà un.
    Personnellement, ma version SQLite-in-WASM préférée reste la variante Pyodide. Elle existe depuis bien plus longtemps que l’implémentation officielle de SQLite, et avec Pyodide, SQLite en WASM est fourni comme une partie de la bibliothèque standard Python.
    C’est utilisé ainsi sur https://lite.datasette.io/, et on peut aussi l’essayer sur https://pyodide.org/en/stable/console.html.
    Si l’on exécute select sqlite_version() avec sqlite3.connect(':memory:'), on obtient la version 3.39.0 du 25/06/2022 ; Pyodide semble donc avoir besoin d’une mise à jour. Cette version semble héritée d’emscripten : https://github.com/emscripten-core/emscripten/blob/main/tools/ports/sqlite3.py

    • La stabilisation de l’API est bien effective. Je mettrai ce passage à jour la prochaine fois que je serai devant mon ordinateur.
      En revanche, l’équipe SQLite ne publiera pas de package npm officiel. Nous ne distribuons que du JS pur, et nous refusons fermement de nous engager dans le terrier du support d’outils extérieurs au langage. Aucun membre du projet n’utilise ce genre d’outils.
      Nous prenons en charge le build npm « officiellement approuvé » maintenu par Thomas Steiner, mais nous ne ciblons activement aucun framework JS dans le développement.
      Prendre directement en charge un framework particulier, y compris npm, pourrait donner l’impression que nous le cautionnons, et nous évitons activement toute caution de projets tiers.
    • Il me semble que l’équipe SQLite fait déjà cela depuis un moment. Il suffit d’importer @sqlite.org/sqlite-wasm depuis jsdelivr dans un module JS, puis de charger sqlite3-bundler-friendly.mjs et sqlite3.wasm.
      On peut alors utiliser l’API C de SQLite ainsi que l’API orientée objet oo1, et cela fonctionne aussi avec une balise script classique type=module ou avec Deno.
      Un exemple HTML est disponible ici : https://github.com/Ciantic/experimenting-sqlite-wasm/blob/main/standalone.html
    • https://github.com/sqlite/sqlite-wasm
      sqlite-wasm se charge beaucoup plus vite que Pyodide ; si vous n’avez pas besoin de Python, c’est donc le meilleur choix.
    • DuckDB WASM peut être utilisé indépendamment de Pyodide et peut aussi charger l’extension SQLite.
      Cela dit, cela semble assez limité. Comme il n’y a pas de sqlite_version(), je n’ai même pas pu vérifier la version, et celle présente dans le dépôt est la 3.38.1, qui commence à dater.
      Le shell web actuel de DuckDB ne peut pas charger l’extension SQLite, car celle destinée à la version 1.1.2 sortie hier n’a pas encore été publiée. Les versions précédentes fonctionnent avec l’édition WASM récemment mise à jour.
      On peut aussi y ajouter l’extension spatiale, GDAL, la recherche vectorielle, etc. : https://github.com/duckdb/duckdb-wasm/releases/tag/v1.29.0
      Créer soi-même un « shell web SQL » n’était pas très difficile, mais la documentation n’était pas suffisamment complète.
      https://github.com/duckdb/sqlite_scanner/blob/main/src/sqlite/sqlite3.h
    • Lire la FAQ de cette page peut être utile.
  • Pour une utilisation avec Go, j’aime vraiment beaucoup le package Wasm SQLite de ncruces : https://github.com/ncruces/go-sqlite3
    Le package Go de cznic est également excellent, mais cette version Wasm fonctionne bien même dans des environnements comme OpenBSD.

  • WebSQL aurait simplement dû être SQLite ; si cela avait été le cas, l’écosystème offline-first et, plus généralement, celui du stockage applicatif auraient été bien meilleurs.
    Y a-t-il encore une possibilité aujourd’hui ? Plutôt que d’abstraire SQLite et de surspécifier les choses, ne pourrait-on pas spécifier la version de l’API SQLite que les navigateurs doivent prendre en charge, puis relever régulièrement cette version ?

    • Dans les navigateurs, une approche consistant à « relever régulièrement la version » risquerait d’être assez problématique.
      L’un des principes fondamentaux du Web est que l’existant doit continuer à fonctionner autant que possible, et ce genre de changement cassant est difficile.
      Même si la spécification listait plusieurs versions et laissait les pages web choisir, les navigateurs se retrouveraient avec la lourde responsabilité de conserver d’anciennes versions de SQLite qui pourraient ne plus être prises en charge. Chaque version occupe en plus environ 1 Mo.
      Dans ce cas, il me semble préférable de laisser chaque site web assumer le coût de son propre choix, comme aujourd’hui.
      Si le téléchargement répété de ressources communes vous inquiète, c’est un problème que d’autres CDN acceptent déjà. Une solution de cache partagé pour les dépendances communes aurait bien plus de valeur qu’une solution limitée à SQLite.
      L’approche actuelle me semble meilleure qu’une version fournie par le navigateur.
    • J’aime SQLite, mais il faut reconnaître qu’il présente plusieurs comportements particuliers pour être intégré dans tous les navigateurs. Une API de type SQLite serait donc sans doute préférable.
    • À mon avis, c’est plutôt une bonne chose que WebSQL ne se soit pas imposé.
      Aujourd’hui, on peut récupérer la dernière version de SQLite sous forme de blob WASM compressé de 410 Ko quand on en a besoin. Si nous avions été liés à une version de SQLite imposée par les navigateurs, nous utiliserions peut-être aujourd’hui une version vieille de 10 ans.
  • En parcourant rapidement la page, le cas d’usage n’est pas très clair
    Plus précisément, est-ce que cela peut servir de remplaçant à IndexedDB ? Les données sont-elles persistées, ou faut-il stocker directement le fichier SQLite dans la File System API ou dans IndexedDB/localStorage ?

    • D’après la page d’introduction, l’un des objectifs du projet est de prendre en charge, dans la mesure du possible, le stockage persistant côté client via une API JS utilisable
      Pour l’instant, cela inclut le stockage via Origin-Private FileSystem (OPFS) et, de façon très limitée, via les backends window.localStorage et window.sessionStorage
  • Je m’intéresse beaucoup ces derniers temps au domaine local-first, mais embarquer SQLite me paraît assez lourd comparé à l’utilisation des API de stockage intégrées au navigateur, en particulier IndexedDB
    Les principales bibliothèques open source semblent elles aussi utiliser principalement les API intégrées
    Plutôt que d’essayer d’ajouter encore un exécutable en WebAssembly, j’aimerais voir une solution open source qui fournisse une API proche de SQLite et de la synchronisation par-dessus le stockage natif du navigateur

    • SQL peut être considéré comme plus agréable à utiliser que l’API IndexedDB et, par exemple, utiliser une clause WHERE au lieu d’un filtre if() pourrait aussi consommer moins de mémoire et de CPU
    • TinyBase pourrait t’intéresser. C’est une bibliothèque que j’ai créée
  • Même en le téléchargeant depuis la page officielle et en supprimant tous les fichiers mjs ainsi que les fichiers « bundler-friendly », la dépendance SQLite Wasm minimale pèse environ 1,3 Mo
    Cela semble un peu gros pour une application navigateur, mais comme Wasm s’exécute aujourd’hui aussi ailleurs, cela peut avoir davantage de sens dans ces environnements

    • Cela dit, ça se compresse bien. sqlite3.js et le Wasm font 1,3 Mo bruts, mais en minifiant le JS et en compressant les deux fichiers avec Brotli, on descend à 410 Ko
    • Pour une application web moderne, 1,3 Mo me paraît tout à fait raisonnable. D’autant plus qu’après la première visite du site, ce sera probablement mis en cache
      S’il ne s’agit que de stocker des préférences utilisateur, bien sûr, il n’est pas nécessaire de télécharger SQLite ; mais si une vraie base de données apporte un bénéfice pour la tâche visée, il n’y a pas lieu de trop s’inquiéter pour 1 Mo et de réinventer inutilement la roue
      Si l’autre commentaire est exact, cela ne fait de toute façon même pas 1,3 Mo sur le réseau
    • C’est un élément à prendre en compte avec le fait que IndexedDB est déjà intégré au navigateur
      Cela dit, pour des applications navigateur dont le code applicatif et les données sont déjà volumineux, comme des apps à la Figma ou Photoshop, ou des apps de machine learning, 1,3 Mo peut ne pas représenter une surcharge importante
      Il faut aussi garder à l’esprit que le parsing du Wasm est nettement plus rapide que celui du JS. Je ne retrouve pas de source pour cette affirmation pour l’instant, mais il y avait au moins un bon article à ce sujet
      https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
    • Il faut penser au modèle de sandbox de WebAssembly. En théorie, le programme — ici SQLite — peut tourner partout où son exécution est appropriée
      Cela peut être en local, sur un serveur central, ou sur un edge proche
    • Côté serveur, il faudra très probablement un build Wasm de SQLite distinct, qui gère autrement la concurrence et les verrous de fichiers
      Par ailleurs, WASI est encore loin d’être une réponse. L’amalgamation SQLite se compile bien avec WASI, mais la concurrence reste un problème non résolu
      Il a fallu créer un VFS à partir de zéro pour rendre un pilote SQLite basé sur Wasm utilisable
      https://github.com/ncruces/go-sqlite3/blob/main/vfs/README.md
  • J’ai essayé pendant un moment de faire fonctionner ça dans l’écosystème Rust, mais les wrappers SQL de blessed.rs, rusqlite et sqlx, ne semblaient pas encore pouvoir en tirer parti
    L’envelopper soi-même était également délicat : je n’ai pas trouvé comment faire en sorte que le code Wasm d’emscripten fonctionne correctement avec wasm32-unknown-unknown
    Au final, il faut un wrapper JS sous une forme ou une autre, puis exposer depuis JS l’interface attendue par ces crates. Si cela se résout côté Rust, ce sera vraiment intéressant

    • Je travaille sur un sujet lié. Si ça t’intéresse ou si tu veux contribuer, tu peux participer ici : https://github.com/rhashimoto/wa-sqlite/discussions/154
      En substance, il faut importer le build emscripten de SQLite dans wasm-bindgen via des en-têtes extern C, compiler en mode multithread pour permettre l’accès aux buffers de tableaux partagés, et réimplémenter le VFS en Rust
      Ensuite, les lignes SQLite deviennent accessibles sous forme d’octets Wasm bruts, et il devient possible de créer un wrapper ou une intégration de style Rust-SQLite
      Il n’y aura toujours pas de fonctionnalités pratiques comme le pooling de connexions, mais en Wasm il est probablement préférable d’utiliser la DB en mode exclusif
    • Voici un projet qui fait fonctionner ce build SQLite avec Diesel SQLite côté Rust : https://github.com/xmtp/diesel-wasm-sqlite
      Une release 0.2 est en préparation cette semaine, avec quelques problèmes à régler. À part cela, on peut utiliser Diesel ORM comme en natif
    • Il y a une bonne nouvelle concernant l’intégration entre le code Wasm emscripten et wasm32-unknown-unknown
      Il existe un projet visant à faire mieux fonctionner ensemble Emscripten et Rust sur Wasm : https://github.com/rustwasm/wasm-bindgen/pull/4014#issuecomment-2356908451
    • Moi aussi, j’attends ça depuis assez longtemps. Il y a quelques PR en attente depuis environ un an, et j’ai vu des preuves de concept, mais on était encore loin de quelque chose de réellement utilisable
  • J’ai créé une bibliothèque SQLite qui s’exécute sur une JVM pure, en utilisant le build Wasm de SQLite et le runtime Chicory : https://github.com/dylibso/sqlite-zero
    C’est plutôt une expérimentation qu’un projet visant la production, mais je pense que ça peut être utile pour apporter un outil SQLite sans dépendances à l’écosystème JVM

    • Comment se fait l’accès au système de fichiers ? Via WASI ?
  • Question générale : dans quelles situations est-il plus avantageux d’envoyer toute la DB et de laisser le navigateur traiter les requêtes ?
    Autrement dit, quand utiliserait-on cela pour améliorer l’expérience utilisateur par rapport au modèle traditionnel de DB hébergée côté serveur ?

    • Personnellement, je l’utilise pour un site web hébergé statiquement, donc une base de données hébergée côté serveur n’était de toute façon pas une option
      Avec le bon driver, on peut streamer les chunks nécessaires de la base de données au lieu d’envoyer toute la base : https://github.com/mmomtchev/sqlite-wasm-http
      On peut faire de la recherche plein texte SQLite sans télécharger toute la base FTS. Si la requête est assez courte, on en récupère la plus grande partie, mais pas la totalité
    • En voyant le titre de cet article, ce qui m’est venu intuitivement à l’esprit, c’est quelque chose comme la façon dont PouchDB et CouchDB interagissent dans les apps offline-first
      https://medium.com/offline-camp/couchdb-pouchdb-and-hoodie-as-a-stack-for-progressive-web-apps-a6078a985f18
    • Pour un usage hors ligne, ça peut être intéressant quand on manipule de gros volumes de données
      Cela peut aller d’une bibliothèque audio à un logiciel de modélisation 3D. On peut créer et persister les modifications en local, puis les synchroniser avec le côté serveur périodiquement ou lorsque l’on repasse en ligne
    • Je construis des dashboards qui communiquent directement avec plusieurs services. Sur mon serveur, il n’y a qu’un très petit volume de données pour le contrôle d’accès et la gestion des tokens ; les données réelles ne passent pas par mon serveur
      Ce type d’app est un bon candidat pour une DB embarquée côté client