diff --git a/.github/workflows/build_pglite.yml b/.github/workflows/build_pglite.yml index 7fd808bf..f01058f4 100644 --- a/.github/workflows/build_pglite.yml +++ b/.github/workflows/build_pglite.yml @@ -126,6 +126,13 @@ jobs: run: | bash ./cibuild.sh demo-site + - name: Build docs + working-directory: ./docs + run: | + pnpm install + pnpm run docs:build + cp -r ./.vitepress/dist/* /tmp/web/ + - name: Upload Postgres to Github artifacts id: upload-postgres-wasm uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 8e95d80d..e64fa62d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store .vscode -/node_modules +node_modules /packages/pglite/dist /packages/pglite/pgdata-test /packages/pglite/package-lock.json @@ -11,3 +11,6 @@ /postgresql-16.3.tar.bz2 /postgresql-16.3 /postgresql + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/cibuild.sh b/cibuild.sh index 0fef351f..9df39a50 100755 --- a/cibuild.sh +++ b/cibuild.sh @@ -382,29 +382,18 @@ do ;; demo-site) echo "==================== demo-site ==========================" + # Move all existing files to a subfolder + mkdir -p /tmp/web/x-term-repl + mv -r /tmp/web/* /tmp/web/x-term-repl/ - echo " - - - -" > /tmp/web/index.html - - mkdir -p /tmp/web/pglite - mkdir -p /tmp/web/repl - mkdir -p /tmp/web/benchmarks + mkdir -p /tmp/web/dist + mkdir -p /tmp/web/examples + mkdir -p /tmp/web/benchmark PGLITE=$(pwd)/packages/pglite - cp -r ${PGLITE}/dist /tmp/web/pglite/ - cp -r ${PGLITE}/examples /tmp/web/pglite/ - cp -r ${WORKSPACE}/packages/repl/dist-webcomponent /tmp/web/repl/ - cp -r ${WORKSPACE}/packages/benchmark/dist/* /tmp/web/benchmarks/ + cp -r ${PGLITE}/dist/* /tmp/web/dist/ + cp -r ${PGLITE}/examples/* /tmp/web/examples/ + cp -r ${WORKSPACE}/packages/benchmark/dist/* /tmp/web/benchmark/ ;; esac shift diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 00000000..5921e8ca --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,2 @@ +.vitepress/dist +.vitepress/cache \ No newline at end of file diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 00000000..61258700 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,95 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + lang: 'en', + title: 'PGlite', + description: 'Lightweight WASM Postgres', + appearance: 'force-dark', + base: '/', + cleanUrls: true, + ignoreDeadLinks: [ + (url) => { + // Ignore links to our example pages + return url.toLowerCase().startsWith('./examples') + }, + (url) => { + // Ignore links to the benchmark runners + return url.toLowerCase().startsWith('./benchmark/') + }, + ], + head: [ + [ + 'link', + { + rel: 'icon', + type: 'image/svg+xml', + href: '/img/brand/icon-light.svg', + }, + ], + ], + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + logo: { + dark: '/img/brand/logo.svg', + light: '/img/brand/logo-light.svg', + }, + nav: [ + { text: 'Home', link: '/' }, + { text: 'About', link: '/docs/about' }, + { text: 'Docs', link: '/docs/' }, + { text: 'Extensions', link: '/extensions/' }, + { text: 'ElectricSQL', link: 'https://www.electric-sql.com' }, + ], + sidebar: [ + { + text: 'About', + items: [{ text: 'What is PGlite', link: '/docs/about' }], + }, + { + text: 'Docs', + items: [ + { text: 'Getting Started', link: '/docs/' }, + { text: 'PGlite API', link: '/docs/api' }, + { text: 'Live Queries', link: '/docs/live-queries' }, + { text: 'Filesystems', link: '/docs/filesystems' }, + { text: 'Framework Hooks', link: '/docs/framework-hooks' }, + { text: 'Multi-tab Worker', link: '/docs/multi-tab-worker' }, + { text: 'REPL Component', link: '/docs/repl' }, + { text: 'ORM Support', link: '/docs/orm-support' }, + ], + }, + { + text: 'Extensions', + items: [ + { text: 'Extensions Catalog', link: '/extensions/' }, + { text: 'Extension Development', link: '/extensions/development' }, + ], + }, + { + text: 'Reference', + items: [ + { text: 'Examples', link: '/examples.md' }, + { text: 'Benchmarks', link: '/benchmarks.md' }, + ], + }, + ], + siteTitle: false, + socialLinks: [ + { icon: 'discord', link: 'https://discord.electric-sql.com' }, + { icon: 'github', link: 'https://github.com/electric-sql/pglite' }, + ], + footer: { + message: + 'Dual-licensed under Apache 2.0 and the PostgreSQL License', + copyright: '© ElectricSQL', + }, + }, + vue: { + template: { + compilerOptions: { + isCustomElement: (tag) => tag.startsWith('pglite-'), + }, + }, + }, +}) diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 00000000..654f7330 --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,125 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap'); + +:root, +.dark { + --vp-c-indigo-1: #d0bcff; + --vp-c-indigo-2: #998fe7; + --vp-c-indigo-3: #7e78db; + + --vp-nav-logo-height: 30px; + + --electric-color: #00d2a0; + --ddn-color: #d0bcff; + --pglite-color: #f6f95c; + + --vp-c-brand-1: var(--pglite-color); + --vp-c-brand-2: #f1f35e; + --vp-c-brand-3: #d8da53; +} + +.dark { + --vp-c-text-1: rgba(255, 255, 245, 0.92); + --vp-c-text-2: rgba(235, 235, 245, 0.75); + --vp-c-text-3: rgba(235, 235, 245, 0.55); +} + +.img-row { + display: grid; + grid-template-columns: repeat(1, 1fr); + flex-direction: row; + margin: 20px 0; + gap: 10px; +} +.img-row-2 { + grid-template-columns: repeat(2, 1fr); +} +@media (max-width: 767px) { + .img-row-2 { + grid-template-columns: repeat(1, 1fr); + } +} +.img-border { + border: 1px #ccc solid; + border-radius: 10px; + padding: 10px; + background: rgb(20, 21, 23); +} +figure { + margin: 40px 0; +} +figcaption { + text-align: right; + font-size: 90%; + max-width: 460px; + margin-left: auto; +} + +iframe { + color-scheme: auto; +} +.twitter-tweet { + margin: 35px auto -95px !important; +} +.twitter-tweet iframe { + transform: scale(0.8); + transform-origin: top left; +} + +.VPHomeHero .VPImage.image-src { + max-width: min(calc(250px + 25vw), 560px); + margin-left: -40px; + margin-top: 10px; +} +@media (max-width: 959px) { + .VPHomeHero .image .image-container { + height: calc(190px + 15vw) !important; + margin-top: -20px; + } + .VPHomeHero .VPImage.image-src { + margin-left: 0px; + } +} + +.VPFeatures { + padding-top: 15px !important; + padding-bottom: 45px; +} +.VPFeature .VPImage { + width: 50px; +} +.VPFeature h2.title { + font-size: 20px; +} +.VPFeature.link[href='/product/electric']:hover { + border-color: var(--electric-color); +} +.VPFeature.link[href='/product/pglite']:hover { + border-color: var(--pglite-color); +} + +.VPFeature .details { + font-weight: 600 !important; +} + +.product-icon { + width: 84px; + margin-bottom: 20px; +} + +.about-zap { + max-width: min(calc(150px + 25vw), 420px); + margin: -20px 0 30px; +} +@media (max-width: 767px) { + .about-zap-container { + display: none; + } +} + +.vp-doc blockquote { + margin: 25px 10px 30px; +} + +.VPButton.brand { + color: var(--vp-c-gray-3) !important; +} diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 00000000..87772cb1 --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1,14 @@ +// .vitepress/theme/index.js +import { h } from 'vue' +import DefaultTheme from 'vitepress/theme-without-fonts' +import './custom.css' +import HeroImage from '../../components/HeroImage.vue' + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + 'home-hero-image': () => h(HeroImage), + }) + }, +} diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 00000000..81be7010 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,129 @@ + + +# Benchmarks + +There are two sets of micro-benchmarks: one testing [round trip time](#round-trip-time-benchmarks) for both PGlite and wa-sqlite, and [another one](#sqlite-benchmark-suite) based on the [SQLite speed test](https://sqlite.org/src/file?name=tool/speedtest.tcl&ci=trunk) which was ported for the [wa-sqlite benchmarks](https://rhashimoto.github.io/wa-sqlite/demo/benchmarks.html). + +We also have a set of [native baseline](#native-baseline) results comparing native SQLite (via the Node better-sqlite3 package) to full Postgres. + +Comparing Postgres to SQLite is challenging, as they are quite different databases, particularly when you take into account the complexities of WASM. Therefore, these benchmarks provide a view of performance only as a starting point to investigate the difference between the two, and the improvements we can make going forward. + +Another consideration when analysing the speed, is the performance of the various different VFS implementations providing persistance to both PGlite and wa-sqlite. + +The key findings are: + +1. wa-sqlite is faster than PGlite when run purely in memory. This is to be expected as it's a simpler database with fewer features; it's designed to go fast. Having said that, PGlite is not slow; it's well within the range you would expect when [comparing native SQLite to Postgres](#native-baseline). + +2. For single row CRUD inserts and updates, PGlite is faster then wa-sqlite. This is likely due to PGlite using the Postgres WAL, whereas wa-sqlite is only using the SQLite rollback journal mode and not a WAL. + +3. An fsync or flush to the underlying storage can be quite slow, particularly in the browser with IndexedDB for PGlite, or OPFS for wa-sqlite. Both offer some level of "relaxed durability" that can be used to accelerate these queries, and this mode is likely suitable for many embedded use cases. + +We plan to continue to use these micro-benchmarks to feed back into the development of PGlite, and update them, and the findings, as we move forward. + +These results below were run on an M2 Macbook Air. + +## Round-trip-time benchmarks + +These tests run a series of inserts/updates/deletes to find the average time to execute the type of CRUD operations that are regularly used in an app. + +Values are average ms - lower is better. + +![](./public/img/benckmark/rtt.svg) + +| Test | PGlite Memory | PGlite IDB | PGlite IDB
_relaxed durability_ | PGlite OPFS AHP | PGlite OPFS AHP
_relaxed durability_ | SQLite Memory | SQLite IDB | SQLite IDB
_relaxed durability_ | SQLite IDB BatchAtomic | SQLite IDB BatchAtomic
_relaxed durability_ | SQLite OPFS | SQLite OPFS AHP | +| ------------------------ | ------------- | ---------- | ---------------------------------- | --------------- | --------------------------------------- | ------------- | ---------- | ---------------------------------- | ---------------------- | ---------------------------------------------- | ----------- | --------------- | +| Test 1: insert small row | 0.058 | 21.041 | 0.085 | 3.946 | 0.079 | 0.083 | 2.948 | 2.813 | 1.627 | 1.321 | 15.535 | 19.816 | +| Test 2: select small row | 0.088 | 14.49 | 0.108 | 0.126 | 0.082 | 0.042 | 0.673 | 0.744 | 0.423 | 0.458 | 0.819 | 0.03 | +| Test 3: update small row | 0.073 | 14.518 | 0.074 | 0.076 | 0.071 | 0.036 | 0.524 | 0.538 | 0.467 | 0.546 | 1.185 | 0.016 | +| Test 4: delete small row | 0.145 | 23.746 | 0.142 | 3.949 | 0.15 | 0.1 | 2.196 | 2.111 | 1.118 | 0.999 | 15.954 | 20.04 | +| Test 5: insert 1kb row | 0.075 | 23.679 | 0.08 | 3.963 | 0.115 | 0.04 | 2.701 | 3.247 | 1.394 | 1.16 | 16.072 | 19.934 | +| Test 6: select 1kb row | 0.14 | 14.752 | 0.17 | 0.192 | 0.131 | 0.034 | 0.505 | 0.475 | 0.334 | 0.35 | 0.801 | 0.071 | +| Test 7: update 1kb row | 0.1 | 23.659 | 0.105 | 3.959 | 0.121 | 0.022 | 0.549 | 0.539 | 0.384 | 0.383 | 1.171 | 0.017 | +| Test 8: delete 1kb row | 0.125 | 23.752 | 0.124 | 4.03 | 0.166 | 0.037 | 2.979 | 2.933 | 1.314 | 1.068 | 15.787 | 19.827 | +| Test 9: insert 10kb row | 0.345 | 23.604 | 0.348 | 4.251 | 0.363 | 0.122 | 3.02 | 3.371 | 1.683 | 1.501 | 15.74 | 20.041 | +| Test 10: select 10kb row | 0.2 | 14.656 | 0.192 | 0.246 | 0.207 | 0.049 | 0.551 | 0.613 | 0.482 | 0.489 | 1.521 | 0.091 | +| Test 11: update 10kb row | 0.326 | 14.731 | 0.306 | 0.328 | 0.325 | 0.072 | 0.506 | 0.504 | 0.419 | 0.418 | 1.182 | 0.083 | +| Test 12: delete 10kb row | 0.105 | 23.524 | 0.124 | 3.981 | 0.134 | 0.039 | 3.24 | 3.214 | 1.481 | 1.238 | 15.794 | 19.884 | + +## SQLite benchmark suite + +The SQLite benchmark suite, converted to web for wa-sqlite - it performs a number of large queries to test the performance of the sql engine. + +Values are seconds to complete the test - lower is better. + +![](./public/img/benckmark/sqlite-suite.svg) + +| Test | PGlite
Memory | PGlite
IDB FS | PGlite
IDB FS
_relaxed durability_ | PGlite
OPFS Access Handle Pool | PGlite
OPFS Access Handle Pool
_relaxed durability_ | wa-sqlite
Memory (sync) | wa-sqlite
Memory (async) | wa-sqlite
DB Minimal | wa-sqlite
IDB Minimal
_relaxed durability_ | wa-sqlite
IDB Batch Atomic | wa-sqlite
IDB Batch Atomic
_relaxed durability_ | wa-sqlite
OPFS | wa-sqlite
OPFS Access Handle Pool | +| ---------------------------------------------------- | ---------------- | ---------------- | ---------------------------------------- | --------------------------------- | --------------------------------------------------------- | -------------------------- | --------------------------- | ----------------------- | ------------------------------------------------ | ----------------------------- | ----------------------------------------------------- | ----------------- | ------------------------------------ | +| Test 1: 1000 INSERTs | 0.016 | 0.035 | 0.015 | 0.025 | \\ | 0.035 | 0.051 | 2.384 | 2.588 | 1.094 | 0.939 | 18.847 | 24.67 | +| Test 2: 25000 INSERTs in a transaction | 0.292 | 0.299 | 0.278 | 0.296 | 0.304 | 0.077 | 0.12 | 0.14 | 0.105 | 0.15 | 0.107 | 0.141 | 0.137 | +| Test 3: 25000 INSERTs into an indexed table | 0.355 | 0.388 | 0.351 | 0.402 | 0.374 | 0.1 | 0.138 | 0.23 | 0.185 | 0.228 | 0.198 | 0.174 | 0.143 | +| Test 4: 100 SELECTs without an index | 0.218 | 0.229 | 0.217 | 0.215 | 0.215 | 0.104 | 0.17 | 0.185 | 0.281 | 0.185 | 0.275 | 0.285 | 0.103 | +| Test 5: 100 SELECTs on a string comparison | 0.485 | 0.504 | 0.482 | 0.482 | 0.484 | 0.451 | 0.546 | 0.549 | 0.553 | 0.546 | 0.548 | 0.545 | 0.452 | +| Test 6: Creating an index | 0.018 | 0.043 | 0.018 | 0.035 | 0.022 | 0.012 | 0.016 | 0.031 | 0.024 | 0.033 | 0.024 | 0.191 | 0.061 | +| Test 7: 5000 SELECTs with an index | 0.162 | 0.163 | 0.149 | 0.178 | 0.183 | 0.042 | 0.064 | 0.06 | 0.067 | 0.071 | 0.068 | 0.061 | 0.044 | +| Test 8: 1000 UPDATEs without an index | 0.106 | 0.129 | 0.104 | 0.113 | 0.108 | 0.032 | 0.055 | 0.062 | 0.057 | 0.059 | 0.056 | 0.077 | 0.053 | +| Test 9: 25000 UPDATEs with an index | 0.547 | 0.579 | 0.537 | 0.727 | 0.685 | 0.131 | 0.211 | 0.391 | 0.364 | 0.258 | 0.219 | 0.274 | 0.163 | +| Test 10: 25000 text UPDATEs with an index | 0.729 | 0.781 | 0.72 | 0.936 | 0.894 | 0.101 | 0.168 | 0.348 | 0.362 | 0.244 | 0.267 | 0.23 | 0.132 | +| Test 11: INSERTs from a SELECT | 0.123 | 0.182 | 0.123 | 0.186 | 0.14 | 0.047 | 0.057 | 0.311 | 0.33 | 0.347 | 0.358 | 0.171 | 0.102 | +| Test 12: DELETE without an index | 0.014 | 0.038 | 0.014 | 0.027 | 0.015 | 0.02 | 0.023 | 0.915 | 0.936 | 1.148 | 1.146 | 0.222 | 0.094 | +| Test 13: DELETE with an index | 0.02 | 0.043 | 0.02 | 0.039 | 0.024 | 0.038 | 0.044 | 0.298 | 0.365 | 0.161 | 0.217 | 0.31 | 0.065 | +| Test 14: A big INSERT after a big DELETE | 0.096 | 0.158 | 0.097 | 0.148 | 0.112 | 0.036 | 0.045 | 0.221 | 0.169 | 0.207 | 0.21 | 0.175 | 0.084 | +| Test 15: A big DELETE followed by many small INSERTs | 0.141 | 0.174 | 0.14 | 0.161 | 0.14 | 0.031 | 0.043 | 0.138 | 0.138 | 0.083 | 0.137 | 0.189 | 0.058 | +| Test 16: DROP TABLE | 0.004 | 0.025 | 0.002 | 0.012 | 0.004 | 0.003 | 0.002 | 0.096 | 0.163 | 0.098 | 0.144 | 0.61 | 0.077 | + +## Native baseline + +All tests run with Node, [Better-SQLite3](https://www.npmjs.com/package/better-sqlite3) and [node-postgres](https://www.npmjs.com/package/pg) (via [embedded-postgres](https://github.com/leinelissen/embedded-postgres)) + +![](./public/img/benckmark/baseline.svg) + +| Test | SQLite In-Memory | SQLite On-Disk | Postgres | +| ---------------------------------------------------- | ---------------- | -------------- | -------- | +| Test 1: 1000 INSERTs | 0.002 | 0.288 | 0.007 | +| Test 2: 25000 INSERTs in a transaction | 0.022 | 0.019 | 0.114 | +| Test 3: 25000 INSERTs into an indexed table | 0.035 | 0.04 | 0.383 | +| Test 4: 100 SELECTs without an index | 0.076 | 0.078 | 0.094 | +| Test 5: 100 SELECTs on a string comparison | 0.268 | 0.429 | 0.259 | +| Test 6: Creating an index | 0.007 | 0.011 | 0.01 | +| Test 7: 5000 SELECTs with an index | 0.01 | 0.01 | 0.078 | +| Test 8: 1000 UPDATEs without an index | 0.018 | 0.021 | 0.047 | +| Test 9: 25000 UPDATEs with an index | 0.047 | 0.056 | 0.307 | +| Test 10: 25000 text UPDATEs with an index | 0.032 | 0.041 | 0.416 | +| Test 11: INSERTs from a SELECT | 0.022 | 0.027 | 0.072 | +| Test 12: DELETE without an index | 0.01 | 0.023 | 0.007 | +| Test 13: DELETE with an index | 0.017 | 0.021 | 0.019 | +| Test 14: A big INSERT after a big DELETE | 0.017 | 0.021 | 0.048 | +| Test 15: A big DELETE followed by many small INSERTs | 0.008 | 0.01 | 0.067 | +| Test 16: DROP TABLE | 0.001 | 0.003 | 0.004 | + +## Run the benchmarks yourself + +We have a hosted version of the benchmark runners that you can run yourself: + +- Benchmark using the SQLite benchmark suite +- Benchmark round-trim-time for CRUD queries + +Additionally, to run the native baseline, checkout the [PGlite monorepo](https://github.com/electric-sql/pglite), then: + +```sh +cd ./packages/benchmark +pnpm install +npx tsx baseline.ts +``` diff --git a/docs/components/HeroImage.vue b/docs/components/HeroImage.vue new file mode 100644 index 00000000..86ebc487 --- /dev/null +++ b/docs/components/HeroImage.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/docs/components/Repl.vue b/docs/components/Repl.vue new file mode 100644 index 00000000..a2b4741e --- /dev/null +++ b/docs/components/Repl.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/docs/docs/about.md b/docs/docs/about.md new file mode 100644 index 00000000..74f8a0b1 --- /dev/null +++ b/docs/docs/about.md @@ -0,0 +1,37 @@ +# What is PGlite + +PGlite is a [WASM](https://webassembly.org/) Postgres build packaged into a TypeScript/JavaScript client library, that enables you to run Postgres in the browser, [Node.js](https://nodejs.org/) and [Bun](https://bun.sh/), with no need to install any other dependencies. It's under 3mb Gzipped, and has support for many [Postgres extensions](../extensions/), including [pgvector](../extensions/#pgvector). + +Getting started with PGlite is simple: just install and import the NPM package, then create your embedded database: + +```js +import { PGlite } from '@electric-sql/pglite' + +const db = new PGlite() +await db.query("select 'Hello world' as message;") +// -> { rows: [ { message: "Hello world" } ] } +``` + +It can be used as an ephemeral in-memory database, or with persistence either to the file system (Node/Bun), or IndexedDB (browser). + +Unlike previous "Postgres in the browser" projects, PGlite does not use a Linux virtual machine - it is simply Postgres in WASM. + +It's being developed by [ElectricSQL](https://electric-sql.com/) for our use case of embedding into applications, either locally or at the edge, allowing users to sync a subset of their server-side Postgres database. + +However, there are many more use cases for PGlite beyond its use as an embedded application database: + +- **Unit and CI testing**
+ PGlite is very fast to start and tear down. It's perfect for unit tests - you can have a unique fresh Postgres for each test. + +- **Local development**
+ You can use PGlite as an alternative to a full local Postgres for development; simplifying your development environments. + +- **Remote development, or local web containers**
+ As PGlite is so lightweight it can be easily embedded into remote containerised development environments, or in-browser [web containers](https://webcontainers.io). + +- **On-device or edge AI and RAG**
+ PGlite has full support for [pgvector](../extensions/#pgvector), enabling a local or edge retrieval augmented generation (RAG) workflow. + +We are very keen to establish PGlite both as an open source, and open contribution, project, working to build a community around it, so as to develop its capabilities for all use cases. + +Read more in our [getting started guide](./index.md). diff --git a/docs/docs/api.md b/docs/docs/api.md new file mode 100644 index 00000000..6a79ae61 --- /dev/null +++ b/docs/docs/api.md @@ -0,0 +1,325 @@ +--- +outline: [2, 3] +--- + +# PGlite API + +## Main Constructor + +The main constructor is imported as: + +```ts +import { PGlite } from '@electric-sql/pglite' +``` + +The preferred way to create a PGlite instance is with the `PGlite.create()` static method that returns a promise, resolving to the new PGlite instance. + +`await PGlite.create(dataDir: string, options: PGliteOptions)`
+`await PGlite.create(options: PGliteOptions)` + +There are a couple of advantages to using the static method: + +- This awaits the [`.waitReady`](#waitready) promise, ensuring that the database has been fully initialised. +- When using TypeScript and extensions, the returned PGlite instance will have the extensions namespace on its type. This is not possible with the standard constructor due to TypesScript limitations. + +A new PGlite instance can also be created using the `new PGlite()` constructor. + +`new PGlite(dataDir: string, options: PGliteOptions)`
+`new PGlite(options: PGliteOptions)` + +#### `dataDir` + +Path to the directory for storing the Postgres database. You can provide a URI scheme for various storage backends: + +- `file://` or unprefixed
+ File system storage, available in Node and Bun. +- `idb://`
+ [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) storage, available in the browser. +- `memory://`
+ In-memory ephemeral storage, available in all platforms. + +#### `options` + +- `dataDir: string`
+ The directory in which to store the Postgres database when not provided as the first argument. +- `debug: 1-5`
+ the Postgres debug level. Logs are sent to the console. +- `relaxedDurability: boolean`
+ Under relaxed durability mode, PGlite will not wait for flushes to storage to complete after each query before returning results. This is particularly useful when using the IndexedDB file system. +- `fs: Filesystem`
+ The alternative to providing a dataDir with a filesystem prefix is to initialise a `Filesystem` yourself and provide it here. See [Filesystems](./filesystems.md) +- `loadDataDir: Blob | File`
+ A tarball of a PGlite `datadir` to load when the database starts. This should be a tarball produced from the related [`.dumpDataDir()`](#dumpdatadir) method. +- `extensions: Extensions`
+ An object containing the extensions you wish to load. + +#### `options.extensions` + +PGlite and Postgres extensions are loaded into a PGLite instance on start, and can include both a WASM build of a Postgres extension and/or a PGlite client plugin. + +The `options.extensions` parameter is an object of `namespace: extension` parings. The namespace is used to expose the PGlite client plugin included in the extension. An example of this is the [live queries](./live-queries.md) extension. + +```ts +import { PGlite } from '@electric-sql/pglite' +import { live } from '@electric-sql/pglite/live' +import { vector } from '@electric-sql/pglite/vector' + +const pg = await PGlite.create({ + extensions: { + live, // Live query extension, is a PGlite client plugin + vector, // Postgres pgvector extension + }, +}) + +// The `live` namespace is added by the use of the +// `live` key in the `extensions` object. +pg.live.query('...') +``` + +For information on how to develop a PGlite extension see [Extension Development](../extensions/development.md). + +## Methods + +### query + +`.query(query: string, params?: any[], options?: QueryOptions): Promise>` + +Execute a single statement, optionally with parameters. + +Uses the _extended query_ Postgres wire protocol. + +Returns single [result object](#results-t-objects). + +##### Example + +```ts +await pg.query('INSERT INTO test (name) VALUES ($1);', ['test']) +// { affectedRows: 1 }, +``` + +##### Query Options + +The `query` and `exec` methods take an optional `options` objects with the following parameters: + +- `rowMode: "object" | "array"`
+ The returned row object type, either an object of `fieldName: value` mappings or an array of positional values. Defaults to `"object"`. +- `parsers: ParserOptions`
+ An object of type `{[[pgType: number]: (value: string) => any;]}` mapping Postgres data type IDs to parser functions. + For convenience, the `pglite` package exports a constant for most common Postgres types: + + ```ts + import { types } from '@electric-sql/pglite' + await pg.query( + ` + SELECT * FROM test WHERE name = $1; + `, + ['test'], + { + rowMode: 'array', + parsers: { + [types.TEXT]: (value) => value.toUpperCase(), + }, + }, + ) + ``` + +- `blob: Blob | File`
+ Attach a `Blob` or `File` object to the query that can used with a `COPY FROM` command by using the virtual `/dev/blob` device, see [importing and exporting](#dev-blob). + +### exec + +`.exec(query: string, options?: QueryOptions): Promise>` + +Execute one or more statements. _(note that parameters are not supported)_ + +This is useful for applying database migrations, or running multi-statement SQL that doesn't use parameters. + +Uses the _simple query_ Postgres wire protocol. + +Returns array of [result objects](#results-t-objects); one for each statement. + +##### Example + +```ts +await pg.exec(` + CREATE TABLE IF NOT EXISTS test ( + id SERIAL PRIMARY KEY, + name TEXT + ); + INSERT INTO test (name) VALUES ('test'); + SELECT * FROM test; +`) +// [ +// { affectedRows: 0 }, +// { affectedRows: 1 }, +// { +// rows: [ +// { id: 1, name: 'test' } +// ] +// affectedRows: 0, +// fields: [ +// { name: 'id', dataTypeID: '23' }, +// { name: 'name', dataTypeID: '25' }, +// ] +// } +// ] +``` + +### transaction + +`.transaction(callback: (tx: Transaction) => Promise)` + +To start an interactive transaction, pass a callback to the transaction method. It is passed a `Transaction` object which can be used to perform operations within the transaction. + +The transaction will be committed when the promise returned from your callback resolves, and automatically rolled back if the promise is rejected. + +##### `Transaction` objects + +- `tx.query(query: string, params?: any[], options?: QueryOptions): Promise>`
+ The same as the main [`.query` method](#querytquery-string-params-any-promiseresultst). +- `tx.exec(query: string, options?: QueryOptions): Promise>`
+ The same as the main [`.exec` method](#execquery-string-promisearrayresults). +- `tx.rollback()`
+ Rollback and close the current transaction. + +##### Example + +```ts +await pg.transaction(async (tx) => { + await tx.query( + 'INSERT INTO test (name) VALUES ('$1');', + [ 'test' ] + ); + return await ts.query('SELECT * FROM test;'); +}); +``` + +### close + +`.close(): Promise` + +Close the database, ensuring it is shut down cleanly. + +### listen + +`.listen(channel: string, callback: (payload: string) => void): Promise` + +Subscribe to a [pg_notify](https://www.postgresql.org/docs/current/sql-notify.html) channel. The callback will receive the payload from the notification. + +Returns an unsubscribe function to unsubscribe from the channel. + +##### Example + +```ts +const unsub = await pg.listen('test', (payload) => { + console.log('Received:', payload) +}) +await pg.query("NOTIFY test, 'Hello, world!'") +``` + +### unlisten + +`.unlisten(channel: string, callback?: (payload: string) => void): Promise` + +Unsubscribe from the channel. If a callback is provided it removes only that callback from the subscription. When no callback is provided, it unsubscribes all callbacks for the channel. + +### onNotification + +`onNotification(callback: (channel: string, payload: string) => void): () => void` + +Add an event handler for all notifications received from Postgres. + +**Note:** This does not subscribe to the notification; you will need to manually subscribe with `LISTEN channel_name`. + +### offNotification + +`offNotification(callback: (channel: string, payload: string) => void): void` + +Remove an event handler for all notifications received from Postgres. + +### dumpDataDir + +`dumpDataDir(): Promise` + +Dump the Postgres `datadir` to a Gzipped tarball. + +This can then be used in combination with the [`loadDataDir`](#options) option when starting PGlite to load a dumped database from storage. + +::: tip NOTE + +The datadir dump may not be compatible with other Postgres versions; it is only designed for importing back into PGlite. + +::: + +## Properties + +### ready + +`.ready` _`boolean (read only)`_ + +Whether the database is ready to accept queries. + +### closed + +`.closed` _`boolean (read only)`_ + +Whether the database is closed and no longer accepting queries. + +### waitReady + +`.waitReady` _`Promise`_ + +Promise that resolves when the database is ready to use. + +::: tip NOTE + +Query methods will wait for the `waitReady` promise to resolve if called before the database has fully initialised, and so it is not necessary to wait for it explicitly. + +::: + +## `Results` Objects + +Result objects have the following properties: + +- `rows: Row[]`
+ The rows retuned by the query. + +- `affectedRows?: number`
+ Count of the rows affected by the query. Note, this is _not_ the count of rows returned, it is the number or rows in the database changed by the query. + +- `fields: { name: string; dataTypeID: number }[]`
+ Field name and Postgres data type ID for each field returned. + +- `blob?: Blob`
+ A `Blob` containing the data written to the virtual `/dev/blob/` device by a `COPY TO` command. See [/dev/blob](#dev-blob). + +## `Row` Objects + +Rows objects are a key / value mapping for each row returned by the query. + +The `.query()` method can take a TypeScript type describing the expected shape of the returned rows. + +::: tip NOTE + +These types are not validated at run time, the result is only cast to the provided type. + +::: + +## /dev/blob + +PGlite has support for importing and exporting via the SQL `COPY TO/FROM` command by using a virtual `/dev/blob` device. + +To import a file, pass the `File` or `Blob` in the query options as `blob`, and copy from the `/dev/blob` device. + +```ts +await pg.query("COPY my_table FROM '/dev/blob';", [], { + blob: MyBlob, +}) +``` + +To export a table or query to a file, you just need to write to the `/dev/blob` device; the file will be returned as `blob` on the query results: + +```ts +const ret = await pg.query("COPY my_table TO '/dev/blob';") +// ret.blob is a `Blob` object with the data from the copy. +``` diff --git a/docs/docs/filesystems.md b/docs/docs/filesystems.md new file mode 100644 index 00000000..9c3be146 --- /dev/null +++ b/docs/docs/filesystems.md @@ -0,0 +1,119 @@ +# Filesystems + +PGlite has a virtual file system layer that allows it to run in environments that don't traditionally have filesystem access. + +PGlite VFSs are under active development, and we plan to extend the range of options in future, as well as make it easy for users to create their own filesystems. + +We would recommend using the IndexedDB VFS in the browser at the current time as the OPFS VFS is not supported by Safari. + +## In-memory FS + +The in-memory FS is the default when starting PGlite, and it is available on all platforms. All files are kept in memory and there is no persistance, other than calling [`pg.dumpDataDir()`](./api.md#dumpdatadir) and then using the [`loadDataDir`](./api.md#options) option at start. + +To use the in-memory FS you can use one of these methods: + +- Don't provide a `dataDir` option + ```ts + const pg = new PGlite() + ``` +- Set the `dataDir` to `memory://` + ```ts + const pg = new PGlite('memory://') + ``` +- Import and pass the FS explicitly + ```ts + import { MemoryFS } from '@electric-sql/pglite' + const pg = new PGlite({ + fs: new MemoryFS(), + }) + ``` + +### Platform Support + +| Node | Bun | Chrome | Safari | Firefox | +| ---- | --- | ------ | ------ | ------- | +| ✓ | ✓ | ✓ | ✓ | ✓ | + +## Node FS + +The Node FS uses the Node.js file system API to implement a VFS for PGLite. It is available in both Node and Bun. + +To use the Node FS you can use one of these methods: + +- Set the `dataDir` to a directory on your filesystem + ```ts + const pg = new PGlite('./path/to/datadir/') + ``` +- Import and pass the FS explicitly + ```ts + import { NodeFS } from '@electric-sql/pglite' + const pg = new PGlite({ + fs: new NodeFS('./path/to/datadir/'), + }) + ``` + +#### Platform Support + +| Node | Bun | Chrome | Safari | Firefox | +| ---- | --- | ------ | ------ | ------- | +| ✓ | ✓ | | | | + +## IndexedDB FS + +The [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) FS persists the database to IndexedDB in the browser. It's a layer over the in-memory filesystem, loading all files for the database into memory on start, and flushing them to IndexedDB after each query if they have changed. + +To use the IndexedDB FS you can use one of these methods: + +- Set the `dataDir` with a `idb://` prefix, the database will be stored in an IndexedDB named with the path provided + ```ts + const pg = new PGlite('idb://my-database') + ``` +- Import and pass the FS explicitly + ```ts + import { IdbFs } from '@electric-sql/pglite' + const pg = new PGlite({ + fs: new IdbFs('my-database'), + }) + ``` + +The IndexedDB filesystem works at the file level, storing whole files (Postgres has a single file per table or index) as blobs in IndexedDB. Flushing whole files can take a few milliseconds after each query. To aid in building responsive apps we provide a `relaxedDurability` mode that can be [configured when starting](./api.md#options) PGlite. Under this mode, the results of a query are returned immediately, and the flush to IndexedDB is scheduled to occur asynchronously afterwards. Typically, this is immediately after the query returns with no delay. + +### Platform Support + +| Node | Bun | Chrome | Safari | Firefox | +| ---- | --- | ------ | ------ | ------- | +| | | ✓ | ✓ | ✓ | + +## OPFS AHP FS + +The OPFS AHP filesystem is built on top of the [Origin Private Filesystem](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system) in the browser and uses an "access handle pool". It is only available when PGlite is run in a Web Worker, this could be any worker you configure. We provide a [Multi Tab Worker](./multi-tab-worker.md) to aid in using PGlite from multiple tabs in the browser. + +To use the OPFS AHP FS you can use one of these methods: + +- Set the `dataDir` to a directory within the origins OPFS + ```ts + const pg = new PGlite('opfs-ahp://path/to/datadir/') + ``` +- Import and pass the FS explicitly + ```ts + import { OpfsAhpFS } from '@electric-sql/pglite/opfs-ahp' + const pg = new PGlite({ + fs: new OpfsAhpFS('./path/to/datadir/'), + }) + ``` + +### Platform Support + +| Node | Bun | Chrome | Safari | Firefox | +| ---- | --- | ------ | ------ | ------- | +| | | ✓ | | ✓ | + +Unfortunately, Safari appears to have a limit of 252 open sync access handles, this prevents this VFS from working due to a standard Postgres install consisting of over 300 files. + +### What is an "access handle pool"? + +The Origin Private Filesystem API provides both asynchronous and synchronous methods, but the synchronous methods are limited to read, write and flush. You are unable to traverse the filesystem or open files synchronously. PGlite is a fully synchronous WASM build of Postgres and unable to call async APIs while handling a query. While it is possible to build an async WASM Postgres using [Asyncify](https://emscripten.org/docs/porting/asyncify.html), it adds significant overhead in both file size and performance. + +To overcome these limitations, and to provide a fully synchronous file system to PGlite on top of OPFS, we use something called an "access handle pool". When you first start PGlite we open a pool of OPFS access handles with randomised file names; these are then allocated to files as needed. After each query, a pool maintenance job is scheduled that maintains its size. When you inspect the OPFS directory where the database is stored, you will not see the normal Postgres directory layout, but rather a pool of files and a state file containing the directory tree mapping along with file metadata. + +The PGlite OPFS AHP FS is inspired by the [wa-sqlite](https://github.com/rhashimoto/wa-sqlite) access handle pool file system by [Roy Hashimoto](https://github.com/rhashimoto). diff --git a/docs/docs/framework-hooks.md b/docs/docs/framework-hooks.md new file mode 100644 index 00000000..8c992c5a --- /dev/null +++ b/docs/docs/framework-hooks.md @@ -0,0 +1,3 @@ +# Framework Hooks + +_TODO_ diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 00000000..f933a8d8 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,139 @@ +# Getting started with PGlite + +PGlite can be used in both Node/Bun or the browser, and with any JavaScript framework. + +## Install and start in Node/Bun + +Install into your project: + +::: code-group + +```bash [npm] +npm install @electric-sql/pglite +``` + +```bash [pnpm] +pnpm install @electric-sql/pglite +``` + +```bash [yarn] +yarn add @electric-sql/pglite +``` + +```bash [bun] +bun install @electric-sql/pglite +``` + +::: + +To use the in-memory Postgres: + +```js +import { PGlite } from '@electric-sql/pglite' + +const db = new PGlite() +``` + +or to persist to the native filesystem: + +```js +const db = new PGlite('./path/to/pgdata') +``` + +## Install and start in the browser + +It can be installed and imported using your usual package manager: + +```js +import { PGlite } from '@electric-sql/pglite' +``` + +or using a CDN such as JSDeliver: + +```js +import { PGlite } from 'https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js' +``` + +Then for an in-memory Postgres: + +```js +const db = new PGlite() +``` + +or to persist the database to IndexedDB: + +```js +const db = new PGlite('idb://my-pgdata') +``` + +## Making a query + +There are two methods for querying the database, `.query` and `.exec`. The former supports parameters, while the latter supports multiple statements. + +First, let's create a table and insert some test data using the `.exec` method: + +```js +await db.exec(` + CREATE TABLE IF NOT EXISTS todo ( + id SERIAL PRIMARY KEY, + task TEXT, + done BOOLEAN DEFAULT false + ); + INSERT INTO todo (task, done) VALUES ('Install PGlite from NPM', true); + INSERT INTO todo (task, done) VALUES ('Load PGlite', true); + INSERT INTO todo (task, done) VALUES ('Create a table', true); + INSERT INTO todo (task, done) VALUES ('Insert some data', true); + INSERT INTO todo (task) VALUES ('Update a task'); +`) +``` + +The `.exec` method is perfect for migrations and batch inserts with raw SQL. + +Now, let's retrieve an item using `.query` method: + +```js +const ret = await db.query(` + SELECT * from todo WHERE id = 1; +`) +console.log(ret.rows)[ + // Output: + { + id: 1, + task: 'Install PGlite from NPM', + done: false, + } +] +``` + +## Using parametrised queries + +When working with user supplied values, it's always best to use parametrised queries; these are supported on the `.query` method. + +We can use this to update a task: + +```js +const ret = await db.query( + 'UPDATE todo SET task = $2, done = $3 WHERE id = $1', + [5, 'Update a task using parametrised queries', true], +) +``` + +## What next? + +- To learn more about [querying](./api.md#query) and [transactions](./api.md#transaction) along with the other methods and options available, you can read the main [PGlite API documentation](./api.md). + +- There is also a [live-query extension](./live-queries.md) that enables reactive queries to update a UI when the underlying database changes. + +- PGlite has a number of built-in [virtual file systems](./filesystems.md) to provide persistance for your database. + +- There are [framework hooks](./framework-hooks.md) to make working with PGlite within React and Vue much easier with less boilerplate. + +- As PGlite only has a single exclusive connection to the database, we provide a [multi-tab worker](./multi-tab-worker.md) to enable sharing a PGlite instance between multiple browser tabs. + +- There is a [REPL component](./repl.md) that can be easily embedded into a web-app to aid in debugging and development, or as part of a database application itself. + +- We maintain a [list of ORMs and query builders](./orm-support.md) that support PGlite. + +- PGlite supports both Postgres extensions and PGlite Plugins via its [extensions API](./api.md#options-extensions), and there is a list of [supported extensions](../extensions/). + +- We have a [page of examples](../examples.md) that you can open to test out PGlite in the browser. diff --git a/docs/docs/live-queries.md b/docs/docs/live-queries.md new file mode 100644 index 00000000..3c90dcff --- /dev/null +++ b/docs/docs/live-queries.md @@ -0,0 +1,131 @@ +# Live Queries + +The "live" extension enables you to subscribe to a query and receive updated results when the underlying tables change. + +To use the extension, it needs to be added to the PGlite instance when creating it: + +```ts +import { PGlite } from '@electric-sql/pglite' +import { live } from '@electric-sql/pglite/live' + +const pg = await PGlite.create({ + extensions: { + live, + }, +}) +``` + +There are three methods on the `live` namespace: + +- `live.query()` for basic live queries. With less machinery in PGlite, it's quicker for small results sets and narrow rows. +- `live.incrementalQuery()` for incremental queries. It materialises the full result set on each update from only the changes emitted by the `live.changes` API. Perfect for feeding into React, and with good performance for large result sets and wide rows. +- `live.changes()` a lower level API that emits the changes (insert/update/delete) that can then be mapped to mutations in a UI or other datastore. + +## live.query + +`live.query()` + +This is very similar to a standard query, but takes an additional callback that receives the results whenever they change: + +```js +const ret = pg.live.query('SELECT * FROM test ORDER BY rand;', [], (res) => { + // res is the same as a standard query result object +}) +``` + +The returned value from the call is an object with this interface: + +```ts +interface LiveQueryReturn { + initialResults: Results + unsubscribe: () => Promise + refresh: () => Promise +} +``` + +- `initialResults` is the initial results set (also sent to the callback) +- `unsubscribe` allows you to unsubscribe from the live query +- `refresh` allows you to force a refresh of the query with the updated results sent to the callback + +Internally it watches the tables that the query depends on, and reruns the query whenever they are changed. + +## live.incrementalQuery + +`live.incrementalQuery()` + +Similar to above, but maintains a temporary table of the previous state inside of Postgres. When the tables it depends on change, the query is re-run and diffed with the last state. Only the changes from the last version of the query are copied from WASM into JS. + +It requires an additional `key` argument - the name of a column (often a primary key) on which to key the diff. + +```ts +const ret = pg.live.incrementalQuery( + 'SELECT * FROM test ORDER BY rand;', + [], + 'id', + (res) => { + // res is the same as a standard query result object + }, +) +``` + +The returned value is of the same type as the `query` method above. + +## live.changes + +`live.changes()` + +A lower-level API which is the backend for the `incrementalQuery`, it emits the changes that have occurred. It requires a `key` on which to compare row differences: + +```ts +const ret = pg.live.changes( + 'SELECT * FROM test ORDER BY rand;', + [], + 'id', + (res) => { + // res is a change result object + }, +) +``` + +The returned value from the call is defined by this interface: + +```ts +interface LiveChangesReturn { + fields: { name: string; dataTypeID: number }[] + initialChanges: Array> + unsubscribe: () => Promise + refresh: () => Promise +} +``` + +The results passed to the callback are an array of `Change` objects: + +```ts +type ChangeInsert = { + __changed_columns__: string[] + __op__: 'INSERT' + __after__: number +} & T + +type ChangeDelete = { + __changed_columns__: string[] + __op__: 'DELETE' + __after__: undefined +} & T + +type ChangeUpdate = { + __changed_columns__: string[] + __op__: 'UPDATE' + __after__: number +} & T + +type Change = ChangeInsert | ChangeDelete | ChangeUpdate +``` + +Each `Change` includes the new values along with: + +- `__changed_columns__` the column names that were changed. +- `__op__` the operation that is required to update the state (`INSERT`, `UPDATE`, `DELETE`). +- `__after__` the `key` of the row after which _this_ row should be positioned; it will be included in `__changed_columns__` if it has been changed. This allows for very efficient moves within an ordered set of results. + +This API can be used to implement very efficient in-place DOM updates. diff --git a/docs/docs/multi-tab-worker.md b/docs/docs/multi-tab-worker.md new file mode 100644 index 00000000..f64a9b9e --- /dev/null +++ b/docs/docs/multi-tab-worker.md @@ -0,0 +1,153 @@ +# Multi-tab Worker + +It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. Additionally, as PGlite is single connection only, you may want to proxy multiple browser tabs to a single PGlite instance. + +To aid in this, we provide a `PGliteWorker` with the same API as the standard PGlite, and a `worker` wrapper that exposes a PGlite instance to other tabs. + +## Using PGliteWorker + +First, you need to create a js file for your worker instance. You use the `worker` wrapper with an `init` option that returns a PGlite instance to start that database and expose it to all tabs: + +```js +// my-pglite-worker.js +import { PGlite } from '@electric-sql/pglite' +import { worker } from '@electric-sql/pglite/worker' + +worker({ + async init() { + // Create and return a PGlite instance + return new PGlite() + }, +}) +``` + +Then connect the `PGliteWorker` to your new worker process in your main script: + +```js +import { PGliteWorker } from '@electric-sql/pglite/worker' + +const pg = new PGliteWorker( + new Worker(new URL('./my-pglite-worker.js', import.meta.url), { + type: 'module', + }), +) + +// `pg` has the same interface as a standard PGlite interface +``` + +Internally, this starts a worker for each tab, but then runs an a election to nominate one as the leader. Only the leader then starts PGlite by calling the `init` function, and handles all queries. When the leader tab is closed, a new election is run, and a new PGlite instance is started. + +In addition to having all the standard methods of the [`PGlite` interface](./api.md), `PGliteWorker` also has the following methods and properties: + +- `onLeaderChange(callback: () => void)`
+ This allows you to subscribe to a notification when the leader worker is changed. It returns an unsubscribe function. +- `offLeaderChange(callback: () => void)`
+ This allows you to unsubscribe from the leader change notification. +- `isLeader: bool` + A boolean property indicating if this instance is the leader. + +## Passing options to a worker + +`PGliteWorker` takes an optional second parameter `options`; this can include any standard [PGlite options](./api.md#options) along with these additional options: + +- `id: string`
+ This is an optional `id` to group your PGlite workers. The leader election is run between all `PGliteWorker`s with the same `id`.
+ If not provided, the url to the worker is concatenated with the `dataDir` option to create an id. +- `meta: any`
+ Any additional metadata you would like to pass to the worker process `init` function. + +The `worker()` wrapper takes a single options argument, with a single `init` property. `init` is a function that takes any options passed to `PGliteWorker`, excluding extensions, and returns a `PGlite` instance. You can use the options passed to decide how to configure your instance: + +```js +// my-pglite-worker.js +import { PGlite } from '@electric-sql/pglite' +import { worker } from '@electric-sql/pglite/worker' + +worker({ + async init(options) { + const meta = options.meta + // Do something with additional metadata. + // or even run your own code in the leader along side the PGlite + return new PGlite({ + dataDir: options.dataDir, + }) + }, +}) + +// my-app.js +import { PGliteWorker } from '@electric-sql/pglite/worker' + +const pg = new PGliteWorker( + new Worker(new URL('./my-pglite-worker.js', import.meta.url), { + type: 'module', + }), + { + dataDir: 'idb://my-db', + meta: { + // additional metadata passed to `init` + }, + }, +) +``` + +## Extension support + +`PGliteWorker` has support for both Postgres extensions and PGlite plugins using the normal [extension api](./api.md#optionsextensions). + +Any extension can be used by the PGlite instance inside the worker, however the extensions namespace is not exposed on a connecting `PGliteWorker` on the main thread. + +```js +// my-pglite-worker.js +import { PGlite } from '@electric-sql/pglite' +import { worker } from '@electric-sql/pglite/worker' +import { vector } from '@electric-sql/pglite/vector' + +worker({ + async init() { + return new PGlite({ + extensions: { + vector, + }, + }) + }, +}) +``` + +Extensions that only use the PGlite plugin interface, such as live queries, can be used on the main thread with `PGliteWorker` to expose their functionality; this is done by providing a standard options object as a second argument to the `PGliteWorker` constructor: + +```js +import { PGliteWorker } from '@electric-sql/pglite/worker' +import { live } from '@electric-sql/pglite/live' + +const pg = new PGliteWorker( + new Worker(new URL('./my-pglite-worker.js', import.meta.url), { + type: 'module', + }), + { + extensions: { + live, + }, + }, +) +``` + +`PGliteWorker` also has a `create` static method that resolves to a new instance when it is fully initiated. This also adds the correct types for any extensions to the `PGliteWorker` instance: + +```ts +import { PGliteWorker } from '@electric-sql/pglite/worker' +import { live } from '@electric-sql/pglite/live' + +const pg = await PGliteWorker.create( + new Worker(new URL('./my-pglite-worker.js', import.meta.url), { + type: 'module', + }), + { + extensions: { + live, + }, + }, +) + +// TypeScript is aware of the `pg.live` namespace: +pg.live.query(/* ... */) +``` diff --git a/docs/docs/orm-support.md b/docs/docs/orm-support.md new file mode 100644 index 00000000..301f677a --- /dev/null +++ b/docs/docs/orm-support.md @@ -0,0 +1,28 @@ +# ORM Support + +## Drizzle + +[Drizzle](https://orm.drizzle.team) is a TypeScript ORM with support for many databases, including PGlite. Features include: + +- A declarative relational query API +- An SQL-like query builder API +- Migrations + +To use PGlite with Drizzle, wrap you PGlite instance with a `drizzle()` call: + +```sh +npm i drizzle-orm @electric-sql/pglite +npm i -D drizzle-kit +``` + +```ts +import { PGlite } from '@electric-sql/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; + +const client = new PGlite(); +const db = drizzle(client); + +await db.select().from(...); +``` + +See the [Drizzle documentation](https://orm.drizzle.team/docs/get-started-postgresql#pglite) for more details. diff --git a/docs/docs/repl.md b/docs/docs/repl.md new file mode 100644 index 00000000..89bdad01 --- /dev/null +++ b/docs/docs/repl.md @@ -0,0 +1,122 @@ +--- +outline: [2, 3] +--- + + + +# PGlite REPL Component + +A REPL, or terminal, for use in the browser with PGlite, allowing you to have an interactive session with your WASM Postgres in the page. + +This is the REPL with a full PGlite Postgres embedded in the page: + + + + + +## Features: + +- Available as both a [React.js](#react-component) component and a [Web Component](#web-component) +- [CodeMirror](https://codemirror.net) for input editing +- Auto complete, including table and column names from the database +- Input history (up and down keys) +- `\d` PSQL commands (via [psql-describe](https://www.npmjs.com/package/psql-describe)) + +## React Component + +```bash +npm install @electric-sql/pglite-repl +``` + +then to include in a page: + +```tsx +import { PGlite } from '@electric-sql/pglite' +import { Repl } from '@electric-sql/pglite-repl' + +function MyComponent() { + const pg = new PGlite() + + return ( + <> + + + ) +} +``` + +The props for the `` component are described by this interface: + +```ts +// The theme to use, auto is auto-switching based on the system +type ReplTheme = 'light' | 'dark' | 'auto' + +interface ReplProps { + pg: PGlite // PGlite db instance + border?: boolean // Outer border on the component, defaults to false + lightTheme?: Extension + darkTheme?: Extension + theme?: ReplTheme // Defaults to "auto" +} +``` + +The `lightTheme` and `darkTheme` should be instances of a [React CodeMirror](https://uiwjs.github.io/react-codemirror/) theme. + +## Web Component + +Although the PGlite REPL is built with React, it's also available as a web component for easy inclusion in any page or any other framework. + +```html + + + + + + +``` + +### With Vue.js + +The REPL Web Component can be used with Vue.js: + +```vue + + +``` + +You will also need to configure Vue to ignore the `pglite-` prefix: + +```ts +app.config.compilerOptions.isCustomElement = (tag) => { + return tag.startsWith('pglite-') +} +``` + +See the [Vue docs for more details](https://vuejs.org/api/application.html#app-config-compileroptions-iscustomelement). diff --git a/docs/eslint.config.js b/docs/eslint.config.js new file mode 100644 index 00000000..3554f9fe --- /dev/null +++ b/docs/eslint.config.js @@ -0,0 +1,15 @@ +import globals from 'globals' +import rootConfig from '../eslint.config.js' + +export default [ + ...rootConfig, + { ignores: ['.vitepress/dist/**/*', '.vitepress/cache/**/*'] }, + { + files: ['**/*.js'], + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, +] diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000..f4930dd5 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,39 @@ +# Examples + +We have a number of examples showing how to use PGlite along with its capabilities: + +- Basic
+ A basic example showing how to initiate a PGlite database and perform queries using `.exec()` + +- Query Params
+ Aa example showing how to perform parametrised queries using `.query()` method. + +- Copy
+ An example showing to use the `COPY` command with the PGlite `/dev/blob` device. + +- Dump Data Dir
+ Example of the `db.dumpDataDir()` method to save a tarball of a database. + +- Live Query
+ Reactivity example using the live query extensions `.live.query()` method. + +- Live Changes
+ Reactivity example using the live query extensions `.live.changes()` method. + +- Live Incremental Query
+ Reactivity example using the live query extensions `.live.incrementalQuery()` method. + +- Notify and Listen
+ Example showing the use of the `NOTIFY` and `LISTEN` Postgres commands via the PGlite `.listen()` API. + +- OPFS VFS
+ An example demonstrating the [OPFS Access Handle Pool VFS](./docs/filesystems.md#opfs-ahp-fs). + +- PL/PGSQL
+ Demonstration of PGlite's support for Postgres's built-in SQL procedural language extension "PL/PGSQL". + +- Extension: `pgvector`
+ An example showing how to use [pgvector](https://github.com/pgvector/pgvector) with PGlite. + +- Multi Tab Worker
+ Demonstration of the multi tab worker, enabling multiple browser tabs to share a PGlite database. diff --git a/docs/extensions/development.md b/docs/extensions/development.md new file mode 100644 index 00000000..a708a64b --- /dev/null +++ b/docs/extensions/development.md @@ -0,0 +1,62 @@ +# Extension Development + +PGlite has support for both Postgres extensions, and has its own plugin API that allows a developer to augment a PGlite instance with an additional API. + +## Extension API + +::: warning +The extension API is not yet stable and may change in a future release. +::: + +PGlite extensions are an object with the following interface: + +```ts +export interface Extension { + name: string + setup: ExtensionSetup +} + +export type ExtensionSetup = ( + pg: PGliteInterface, + emscriptenOpts: any, + clientOnly?: boolean, +) => Promise + +export interface ExtensionSetupResult { + emscriptenOpts?: any + namespaceObj?: any + bundlePath?: URL + init?: () => Promise + close?: () => Promise +} +``` + +`name` is the human readable name of the extension. + +`setup` is a function that receives the following parameters, and returns a promise that resolves to an object conforming to `ExtensionSetupResult`: + +- `pg`
+ The [PGlite](../docs/api.md) instance that the extension is being added to +- `emscriptenOpts`
+ The options currently configured to pass to the [Emscrption Module factory](https://emscripten.org/docs/api_reference/module.html), including the [Emscript FS](https://emscripten.org/docs/api_reference/Filesystem-API.html). +- `clientOnly`
+ A boolean indicating if this instance of the extension is "client only", meaning that it is on the main thread and doesn't have direct access to the underlying WASM as it is running in a worker. When true, `emscriptenOpts` and `bundlePath` should not re returned as they will have no effect. + +The returned object has these properties - all are optional: + +- `emscriptenOpts`
+ Any augmented or altered configuration to pass to the [Emscrption Module factory](https://emscripten.org/docs/api_reference/module.html). +- `namespaceObj`
+ An object to add as a namespace to the PGlite instance; this can provide access to additional methods or properties that your extension would like to expose. +- `bundlePath`
+ The path to the Postgres extension tarball - see [Building Postgres Extensions](#building-postgres-extensions) +- `init`
+ An initialisation function that will be run after the PGlite instance and Postgres runtime has started, but before the instance is marked as ready for external usage. You can use this to perform any initialisation your extension needs to perform on the database at startup. +- `close`
+ A function that will be called when the user calls `close()` on their PGlite instance; this is called before the database has been shut down. + +An example of a PGlite extension that augments the PGlite instance is the [live query extension](../docs/live-queries.md). + +## Building Postgres Extensions + +We are still working on documentation and examples showing how to build Postgres extensions for use with PGlite. Please check back soon, or reach out on [Discord](https://discord.com/channels/933657521581858818/1212676471588520006) if you would like to try building a particular extension for PGlite. diff --git a/docs/extensions/extensions.data.ts b/docs/extensions/extensions.data.ts new file mode 100644 index 00000000..36da5d30 --- /dev/null +++ b/docs/extensions/extensions.data.ts @@ -0,0 +1,396 @@ +const baseExtensions: Extension[] = [ + { + name: 'pgvector', + description: ` + Open-source vector similarity search for Postgres. + + Store your vectors with the rest of your data. Supports: + - exact and approximate nearest neighbor search + - single-precision, half-precision, binary, and sparse vectors + - L2 distance, inner product, cosine distance, L1 distance, Hamming distance, and Jaccard distance + `, + featured: true, + repo: 'https://github.com/pgvector/pgvector', + tags: ['postgres extension'], + importPath: '@electric-sql/pglite/vector', + importName: 'vector', + core: true, + size: 43953, + }, + { + name: 'live', + description: ` + A reactive, or "live", query extension for PGlite that enables you to subscribe to a query + and receive updated results when the underlying tables change. + `, + featured: true, + repo: 'https://github.com/electric-sql/pglite/tree/main/packages/pglite/src/live', + docs: '/docs/live-queries', + tags: ['pglite plugin'], + importPath: '@electric-sql/pglite/live', + importName: 'live', + core: true, + size: 21766, + }, + { + name: 'adminpack', + description: ` + adminpack provides a number of support functions which pgAdmin and other + administration and management tools can use to provide additional functionality + `, + docs: 'https://www.postgresql.org/docs/current/adminpack.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/adminpack', + importName: 'adminpack', + core: true, + size: 4274, + }, + { + name: 'amcheck', + description: ` + The amcheck module provides functions that allow you to verify the logical + consistency of the structure of relations. + `, + docs: 'https://www.postgresql.org/docs/current/amcheck.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/amcheck', + importName: 'amcheck', + core: true, + size: 18815, + }, + { + name: 'auto_explain', + description: ` + The auto_explain module provides a means for logging execution plans of slow + statements automatically, without having to run EXPLAIN by hand. + `, + docs: 'https://www.postgresql.org/docs/current/auto-explain.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/auto_explain', + importName: 'auto_explain', + core: true, + size: 3125, + }, + { + name: 'bloom', + description: ` + bloom provides an index access method based on Bloom filters. + A Bloom filter is a space-efficient data structure that is used to test whether + an element is a member of a set. In the case of an index access method, it + allows fast exclusion of non-matching tuples via signatures whose size is + determined at index creation. + `, + repo: 'https://www.postgresql.org/docs/current/bloom.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/bloom', + importName: 'bloom', + core: true, + size: 6197, + }, + { + name: 'btree_gin', + description: ` + btree_gin provides GIN operator classes that implement B-tree equivalent + behavior for many built in data types. + `, + docs: 'https://www.postgresql.org/docs/current/btree-gin.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/btree_gin', + importName: 'btree_gin', + core: true, + size: 6347, + }, + { + name: 'btree_gist', + description: ` + btree_gist provides GiST operator classes that implement B-tree equivalent + behavior for many built in data types. + `, + docs: 'https://www.postgresql.org/docs/current/btree-gist.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/btree_gist', + importName: 'btree_gist', + core: true, + size: 24181, + }, + { + name: 'citext', + description: ` + citext provides a case-insensitive character string type, citext. Essentially, + it internally calls lower when comparing values. Otherwise, it behaves almost + the same as text. + `, + docs: 'https://www.postgresql.org/docs/current/citext.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/citext', + importName: 'citext', + core: true, + size: 4983, + }, + { + name: 'cube', + description: ` + cube provides a data type cube for representing multidimensional cubes. + `, + docs: 'https://www.postgresql.org/docs/current/cube.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/cube', + importName: 'cube', + core: true, + size: 15104, + }, + { + name: 'earthdistance', + description: ` + The earthdistance module provides tools for calculating great circle distances + on the surface of the Earth. + `, + docs: 'https://www.postgresql.org/docs/current/earthdistance.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/earthdistance', + importName: 'earthdistance', + core: true, + size: 2220, + }, + { + name: 'fuzzystrmatch', + description: ` + fuzzystrmatch provides functions to determine similarities and distance + between strings. + `, + docs: 'https://www.postgresql.org/docs/current/fuzzystrmatch.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/fuzzystrmatch', + importName: 'fuzzystrmatch', + core: true, + size: 12026, + }, + { + name: 'hstore', + description: ` + This module implements the hstore data type for storing sets of key/value pairs + within a single PostgreSQL value. This can be useful in various scenarios, + such as rows with many attributes that are rarely examined, or semi-structured + data. Keys and values are simply text strings. + `, + docs: 'https://www.postgresql.org/docs/current/hstore.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/hstore', + importName: 'hstore', + core: true, + size: 21380, + }, + { + name: 'isn', + description: ` + The isn module provides data types for the following international product + numbering standards: EAN13, UPC, ISBN (books), ISMN (music), and ISSN (serials). + `, + docs: 'https://www.postgresql.org/docs/current/isn.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/isn', + importName: 'isn', + core: true, + size: 31417, + }, + { + name: 'lo', + description: ` + The lo module provides support for managing Large Objects (also called LOs + or BLOBs). This includes a data type lo and a trigger lo_manage. + `, + docs: 'https://www.postgresql.org/docs/current/lo.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/lo', + importName: 'lo', + core: true, + size: 1822, + }, + { + name: 'ltree', + description: ` + This module implements a data type ltree for representing labels of data stored + in a hierarchical tree-like structure. Extensive facilities for searching through + label trees are provided. + `, + docs: 'https://www.postgresql.org/docs/current/ltree.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/ltree', + importName: 'ltree', + core: true, + size: 19553, + }, + { + name: 'pg_trgm', + description: ` + The pg_trgm module provides functions and operators for determining the similarity + of alphanumeric text based on trigram matching, as well as index operator classes + that support fast searching for similar strings. + `, + docs: 'https://www.postgresql.org/docs/current/pgtrgm.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/pg_trgm', + importName: 'pg_trgm', + core: true, + size: 16208, + }, + { + name: 'seg', + description: ` + This module implements a data type seg for representing line segments, or + floating point intervals. seg can represent uncertainty in the interval endpoints, + making it especially useful for representing laboratory measurements. + `, + docs: 'https://www.postgresql.org/docs/current/seg.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/seg', + importName: 'seg', + core: true, + size: 10426, + }, + { + name: 'tablefunc', + description: ` + The tablefunc module includes various functions that return tables (that is, + multiple rows). These functions are useful both in their own right and as + examples of how to write C functions that return multiple rows. + `, + docs: 'https://www.postgresql.org/docs/current/tablefunc.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/tablefunc', + importName: 'tablefunc', + core: true, + size: 5824, + }, + { + name: 'tcn', + description: ` + The tcn module provides a trigger function that notifies listeners of changes to + any table on which it is attached. It must be used as an AFTER trigger + FOR EACH ROW. + `, + docs: 'https://www.postgresql.org/docs/current/tcn.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/tcn', + importName: 'tcn', + core: true, + size: 1914, + }, + { + name: 'tsm_system_rows', + description: ` + The tsm_system_rows module provides the table sampling method SYSTEM_ROWS, which + can be used in the TABLESAMPLE clause of a SELECT command. + `, + docs: 'https://www.postgresql.org/docs/current/tsm-system-rows.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/tsm_system_rows', + importName: 'tsm_system_rows', + core: true, + size: 2048, + }, + { + name: 'tsm_system_time', + description: ` + The tsm_system_time module provides the table sampling method SYSTEM_TIME, which + can be used in the TABLESAMPLE clause of a SELECT command. + `, + docs: 'https://www.postgresql.org/docs/current/tsm-system-time.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/tsm_system_time', + importName: 'tsm_system_time', + size: 2099, + }, + { + name: 'uuid-ossp', + description: ` + The uuid-ossp module provides functions to generate universally unique + identifiers (UUIDs) using one of several standard algorithms. There are also + functions to produce certain special UUID constants. This module is only + necessary for special requirements beyond what is available in core PostgreSQL. + `, + docs: 'https://www.postgresql.org/docs/current/uuid-ossp.html', + tags: ['postgres extension', 'postgres/contrib'], + importPath: '@electric-sql/pglite/contrib/uuid_ossp', + importName: 'uuid_ossp', + size: 17936, + }, +] + +const tags = [ + 'postgres extension', + 'pglite plugin', + 'postgres/contrib', +] as const + +export type Tag = (typeof tags)[number] + +export interface Extension { + name: string + description: string + descriptionHtml?: string + repo?: string + homepage?: string + docs?: string + featured?: boolean + tags?: Tag[] + importPath?: string + importName?: string + core?: boolean + npmPackage?: string + size?: number +} + +export default { + async load() { + const { createMarkdownRenderer } = await import('vitepress') + const config = (await import('../.vitepress/config.mjs')).default + const dedent = (await import('dedent')).default + + const md = await createMarkdownRenderer(config.srcDir!, config.markdown) + + const extensions = baseExtensions.map((extension) => { + let descriptionHtml = extension.descriptionHtml + if (!descriptionHtml) { + let description = dedent(extension.description).trim() + if (extension.core) { + description += + '\n\n' + + dedent` + \`${extension.name}\` is included in the main PGlite package. + ` + } else if (extension.npmPackage) { + description += dedent` + + + ${'```'}sh + npm install ${extension.npmPackage} + ${'```'} + ` + } + if (extension.importName && extension.importPath) { + description += + '\n\n' + + dedent` + ${'```'}js + import { ${extension.importName} } from '${extension.importPath}'; + const pg = new PGlite({ + extensions: { ${extension.importName} } + }); + ${'```'} + ` + } + descriptionHtml = md.render(description) + } + return { + ...extension, + descriptionHtml, + } + }) + + return { + extensions, + tags, + } + }, +} diff --git a/docs/extensions/index.md b/docs/extensions/index.md new file mode 100644 index 00000000..a640bf41 --- /dev/null +++ b/docs/extensions/index.md @@ -0,0 +1,178 @@ + + + + +# PGlite Extensions + +PGlite supports both Postgres extensions, and has a plugin API to enable extensions to extend the public API of the PGlite interface. + +Below is a list of available extensions. + +
+ +
+ +
+ +

+ {{ ext.name }} + +

+ +
+ + +
+ {{ tag }} +
+ +
diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..8d0e8e23 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,114 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: 'PGlite' + text: 'Embeddable Postgres' + tagline: 'Run a full Postgres database locally in your app with reactivity and server sync' + actions: + - theme: brand + text: Getting Started + link: /docs/ + - theme: alt + text: About + link: /docs/about + - theme: alt + text: GitHub + link: https://github.com/electric-sql/pglite + - theme: alt + text: Discord + link: https://discord.com/channels/933657521581858818/1212676471588520006 + +features: + - title: Lightweight + details: A complete WASM build of Postgres that's under 3MB Gzipped. + - title: Extendable + details: Dynamic extension loading mechanism, including support for pgvector and PostGIS. + - title: Reactive + details: Built in support for data loading, synchronisation and live query primitives. +--- + + + + + + + +
+ +### Try PGlite Now + +This is a full PGlite Postgres running in your browser - it even includes pgvector! + +
+ + + + diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..fb31a38c --- /dev/null +++ b/docs/package.json @@ -0,0 +1,23 @@ +{ + "name": "docs", + "type": "module", + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview", + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview", + "lint": "eslint . --report-unused-disable-directives --max-warnings 0", + "format": "prettier --write .", + "typecheck": "tsc --noEmit", + "stylecheck": "pnpm lint && prettier --check ." + }, + "devDependencies": { + "vitepress": "^1.3.1" + }, + "dependencies": { + "@uiw/codemirror-theme-github": "^4.23.0", + "dedent": "^1.5.3" + } +} diff --git a/docs/public/img/benckmark/baseline.svg b/docs/public/img/benckmark/baseline.svg new file mode 100644 index 00000000..957ea03c --- /dev/null +++ b/docs/public/img/benckmark/baseline.svg @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test 1: 1000 INSERTs + Test 2: 25000 INSERTs in a + Test 3: 25000 INSERTs into + Test 4: 100 SELECTs without + Test 5: 100 SELECTs on a + Test 6: Creating an index + Test 7: 5000 SELECTs with + Test 8: 1000 UPDATEs + Test 9: 25000 UPDATEs with + Test 10: 25000 text UPDATEs + Test 11: INSERTs from a + Test 12: DELETE without an + Test 13: DELETE with an + Test 14: A big INSERT after a + Test 15: A big DELETE + Test 16: DROP TABLE + 0.000 + + 0.100 + 0.200 + 0.300 + 0.400 + 0.500 + SQLite In-Memory + SQLite On-Disk + Postgres + + + + + + + + + + diff --git a/docs/public/img/benckmark/rtt.svg b/docs/public/img/benckmark/rtt.svg new file mode 100644 index 00000000..20f928f2 --- /dev/null +++ b/docs/public/img/benckmark/rtt.svg @@ -0,0 +1,1080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test 1: insert + small row + + + Test 2: + select small + row + Test 3: + update small + row + Test 4: + delete small + row + + + Test 5: insert + 1kb row + + + Test 6: + select 1kb + row + Test 7: + update 1kb + row + Test 8: + delete 1kb + row + + + Test 9: insert + 10kb row + + + Test 10: + select 10kb + row + Test 11: + update 10kb + row + Test 12: + delete 10kb + row + + 0.000 + 5.000 + 10.000 + 15.000 + 20.000 + 25.000 + + PGlite Memory + PGlite IDB + PGlite IDB + relaxed durability + PGlite OPFS + AHP + PGlite OPFS + AHP + relaxed durability + SQLite Memory + SQLite IDB + SQLite IDB + relaxed durability + SQLite IDB + BatchAtomic + SQLite IDB + BatchAtomic + relaxed durability + SQLite OPFS + SQLite OPFS + AHP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/img/benckmark/sqlite-suite.svg b/docs/public/img/benckmark/sqlite-suite.svg new file mode 100644 index 00000000..296dc5c9 --- /dev/null +++ b/docs/public/img/benckmark/sqlite-suite.svg @@ -0,0 +1,1489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test 1: 1000 + INSERTs + Test 2: 25000 + INSERTs in a + transaction + Test 3: 25000 + INSERTs into + an indexed + Test 4: 100 + SELEC + Ts + without an index + Test 5: 100 + SELEC + T + s on a + string + Test 6: Creating + an index + Test 7: 5000 + SELECTs with + an index + Test 8: 1000 + UPDATEs + without an index + Test 9: 25000 + UPDATEs with + an index + Test 10: 25000 + text UPDATEs + with an index + Test 11: + INSERTs from + a SELECT + Test 12: + DELETE + without an index + Test 13: + DELETE with + an index + Test 14: A big + INSERT after a + big DELETE + Test 15: A big + DELETE + followed by + Test 16: DROP + T + ABLE + + 0.000 + 1.000 + 2.000 + 3.000 + + PGlite + Memory + PGlite + IDB FS + PGlite + IDB FS + relaxed durability + PGlite + OPFS + Access + Handle Pool + PGlite + OPFS + Access + Handle Pool + relaxed durability + wa-sqlite + Memory (sync) + wa-sqlite + Memory (async) + wa-sqlite + DB Minimal + wa-sqlite + IDB Minimal + relaxed durability + wa-sqlite + IDB Batch + Atomic + wa-sqlite + IDB Batch + Atomic + relaxed durability + wa-sqlite + OPFS + wa-sqlite + OPFS + Access + Handle Pool + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/img/brand/icon-light.svg b/docs/public/img/brand/icon-light.svg new file mode 100644 index 00000000..c648b0f6 --- /dev/null +++ b/docs/public/img/brand/icon-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/public/img/brand/icon.svg b/docs/public/img/brand/icon.svg new file mode 100644 index 00000000..48d31f5d --- /dev/null +++ b/docs/public/img/brand/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/img/brand/logo-light.svg b/docs/public/img/brand/logo-light.svg new file mode 100644 index 00000000..067d5828 --- /dev/null +++ b/docs/public/img/brand/logo-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/public/img/brand/logo.svg b/docs/public/img/brand/logo.svg new file mode 100644 index 00000000..78795c5f --- /dev/null +++ b/docs/public/img/brand/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/public/img/postgres-new.png b/docs/public/img/postgres-new.png new file mode 100644 index 00000000..936f0616 Binary files /dev/null and b/docs/public/img/postgres-new.png differ diff --git a/packages/benchmark/README.md b/packages/benchmark/README.md index 4ded5b8c..dbba0320 100644 --- a/packages/benchmark/README.md +++ b/packages/benchmark/README.md @@ -2,636 +2,17 @@ There are two sets of benchmarks, one testing [round trip time](#round-trip-time-benchmarks) for both PGlite and wa-sqlite, and [another](#pglite-results-from-wa-sqlite-benchmark-suite) based on the [wa-sqlite bechmarks](https://rhashimoto.github.io/wa-sqlite/demo/benchmarks.html). -To run, start a web server in this directory and open `index.html` for the benchmarks based on the wa-sqlite set, and `rtt.html` for the round trip time benchmarks. +To run, from this dir: -There is also a script `baseline.ts` that generates a set of native baseline results for the wa-sqlite benchmark suite, see [comparative native (none wasm) results below](#native-baseline). - -These results below were run on a M2 Macbook Air. - -These initial benchmarks show good performance, albeit not quite on par with wa-sqlite yet. We have many plans for improving upon these, including dropping the need for the [emscripten option `EMULATE_FUNCTION_POINTER_CASTS`](https://github.com/electric-sql/pglite/issues/56) and moving to [OPFS for persistance](https://github.com/electric-sql/pglite/issues/9). - -## Round trip time benchmarks: - -These tests run a series of inserts/updates/deletes to find the average time to execute the type of CRUD operations that are regularly used in an app. - -![](./images/rtt-in-memory.svg) - -![](./images/rtt-persisted.svg) - -
-Round trip time results - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestPGlite MemoryPGlite IDBSQLite MemorySQLite IDBSQLite OPFS
Test 1: insert small row0.05016.9050.0306.1563.315
Test 2: select small row0.0548.5910.0100.7050.639
Test 3: update small row0.0448.6740.0090.7050.811
Test 4: delete small row0.09717.0260.0243.8912.825
Test 5: insert 1kb row0.06517.0710.0596.9833.402
Test 6: select 1kb row0.0948.5890.0290.7110.661
Test 7: update 1kb row0.07017.0300.0160.7610.821
Test 8: delete 1kb row0.08117.0370.0346.8863.349
Test 9: insert 10kb row0.27517.0470.1079.2673.856
Test 10: select 10kb row0.1458.8240.0510.8320.681
Test 11: update 10kb row0.2458.8350.0710.7460.851
Test 12: delete 10kb row0.08117.0060.02111.2244.079
-
- -## wa-sqlite benchmark suite - -The wa-sqlite benchmark suite performs a number of large queries to test the performance of the sql engin. - -![](./images/wa-benchmark.svg) - -
- PGlite results - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestMemoryMemory (Unlogged Tables)Emscripten IndexedDB FS
Test 1: 1000 INSERTs0.0200.0130.054
Test 2: 25000 INSERTs in a transaction0.3220.2770.351
Test 2.1: 25000 INSERTs in single statement0.0920.0870.142
Test 3: 25000 INSERTs into an indexed table0.3360.3250.421
Test 3.1: 25000 INSERTs into an indexed table in single statement0.1510.1150.245
Test 4: 100 SELECTs without an index0.2000.2000.254
Test 5: 100 SELECTs on a string comparison0.4310.4310.457
Test 6: Creating an index0.0190.0180.069
Test 7: 5000 SELECTs with an index0.1550.1560.185
Test 8: 1000 UPDATEs without an index0.0870.0890.131
Test 9: 25000 UPDATEs with an index0.4990.4770.563
Test 10: 25000 text UPDATEs with an index0.6480.6200.810
Test 11: INSERTs from a SELECT0.0880.0750.151
Test 12: DELETE without an index0.0120.0110.068
Test 13: DELETE with an index0.0330.0110.138
Test 14: A big INSERT after a big DELETE0.0710.0590.139
Test 15: A big DELETE followed by many small INSERTs0.1320.1270.196
Test 16: DROP TABLE0.0020.0020.053
- -
- -
- WASM SQLite results - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestdefaultMemory (sync)Memory (async)MemoryAsyncIDBMinimalIDBMinimal relaxedIDBBatchAtomicIDBBatchAtomic relaxedOriginPrivateFileSystemAccessHandlePool
Test 1: 1000 INSERTs (very slow on IDB & OPFS)0.0640.0350.0510.0552.3842.5881.0940.93918.84724.67
Test 2: 25000 INSERTs in a transaction0.0820.0770.120.0980.140.1050.150.1070.1410.137
Test 3: 25000 INSERTs into an indexed table0.1120.10.1380.1380.230.1850.2280.1980.1740.143
Test 4: 100 SELECTs without an index0.1060.1040.170.1870.1850.2810.1850.2750.2850.103
Test 5: 100 SELECTs on a string comparison0.4540.4510.5460.5840.5490.5530.5460.5480.5450.452
Test 6: Creating an index0.0120.0120.0160.0180.0310.0240.0330.0240.1910.061
Test 7: 5000 SELECTs with an index0.0460.0420.0640.0640.060.0670.0710.0680.0610.044
Test 8: 1000 UPDATEs without an index0.0330.0320.0550.0580.0620.0570.0590.0560.0770.053
Test 9: 25000 UPDATEs with an index0.1310.1310.2110.220.3910.3640.2580.2190.2740.163
Test 10: 25000 text UPDATEs with an index0.1080.1010.1680.1770.3480.3620.2440.2670.230.132
Test 11: INSERTs from a SELECT0.0490.0470.0570.0620.3110.330.3470.3580.1710.102
Test 12: DELETE without an index0.0230.020.0230.0340.9150.9361.1481.1460.2220.094
Test 13: DELETE with an index0.0370.0380.0440.0490.2980.3650.1610.2170.310.065
Test 14: A big INSERT after a big DELETE0.0370.0360.0450.0470.2210.1690.2070.210.1750.084
Test 15: A big DELETE followed by many small INSERTs0.0290.0310.0430.0450.1380.1380.0830.1370.1890.058
Test 16: DROP TABLE0.0020.0030.0020.0040.0960.1630.0980.1440.610.077
- -
- -## Native baseline: - -All tests run with Node, [Better-SQLite3](https://www.npmjs.com/package/better-sqlite3) and [node-postgres](https://www.npmjs.com/package/pg) (via [embedded-postgres](https://github.com/leinelissen/embedded-postgres)) - -To run: - -``` -# in this dir -npm install -npx tsx baseline.ts +```sh +pnpm install +pnpm build +cd ./dist +python3 -m http.server ``` -![](./images/native.svg) +Then open `http://localhost:8000/index.html` for the benchmarks based on the wa-sqlite set, and `http://localhost:8000/rtt.html` for the round trip time benchmarks. -
-Native Results +There is also a script `baseline.ts` that generates a set of native baseline results for the wa-sqlite benchmark suite. This can be run with `npx tsx baseline.ts`. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestSQLite In-MemorySQLite On-DiskPostgres
Test 1: 1000 INSERTs0.0020.2880.007
Test 2: 25000 INSERTs in a transaction0.0220.0190.114
Test 2.1: 25000 INSERTs in single statement0.0190.020.053
Test 3: 25000 INSERTs into an indexed table0.0350.040.383
Test 3.1: 25000 INSERTs into an indexed table in single statement0.0250.0290.29
Test 4: 100 SELECTs without an index0.0760.0780.094
Test 5: 100 SELECTs on a string comparison0.2680.4290.259
Test 6: Creating an index0.0070.0110.01
Test 7: 5000 SELECTs with an index0.010.010.078
Test 8: 1000 UPDATEs without an index0.0180.0210.047
Test 9: 25000 UPDATEs with an index0.0470.0560.307
Test 10: 25000 text UPDATEs with an index0.0320.0410.416
Test 11: INSERTs from a SELECT0.0220.0270.072
Test 12: DELETE without an index0.010.0230.007
Test 13: DELETE with an index0.0170.0210.019
Test 14: A big INSERT after a big DELETE0.0170.0210.048
Test 15: A big DELETE followed by many small INSERTs0.0080.010.067
Test 16: DROP TABLE0.0010.0030.004
-
+There is a [writeup of the benchmarks in the docs](../../docs/benchmarks.md). diff --git a/packages/pglite/examples/dumpDataDir.html b/packages/pglite/examples/dump-data-dir.html similarity index 100% rename from packages/pglite/examples/dumpDataDir.html rename to packages/pglite/examples/dump-data-dir.html diff --git a/packages/pglite/examples/dumpDataDir.js b/packages/pglite/examples/dump-data-dir.js similarity index 100% rename from packages/pglite/examples/dumpDataDir.js rename to packages/pglite/examples/dump-data-dir.js diff --git a/packages/pglite/examples/index.html b/packages/pglite/examples/index.html deleted file mode 100644 index 43871ea3..00000000 --- a/packages/pglite/examples/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/pglite/examples/opfs.html b/packages/pglite/examples/opfs.html index cf3cb6a1..f8a0b209 100644 --- a/packages/pglite/examples/opfs.html +++ b/packages/pglite/examples/opfs.html @@ -1,49 +1,74 @@ + + + + PGlite Worker Example + + + + + +

PGlite OPFS Example

+
+
+

Worker Thread - opfs-worker.js

+
+

Main Thread

+
+
+
+ + diff --git a/packages/pglite/examples/styles.css b/packages/pglite/examples/styles.css index e076b01e..10aff29e 100644 --- a/packages/pglite/examples/styles.css +++ b/packages/pglite/examples/styles.css @@ -16,11 +16,15 @@ h1 { margin: 0 0 1rem; } -script, +h2 { + font-size: 1.1rem; + margin: 0 0 1rem; +} + +.script-plus-log :is(script, .script), #log { /* Show the content of the script tag */ display: block; - padding: 1rem; white-space: pre-wrap; width: 50%; font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", @@ -28,7 +32,22 @@ script, "Fira Mono", "Droid Sans Mono", "Courier New", monospace; } -script { +.script-plus-log > :is(script, .script) { + padding: 1rem; +} + +.script-plus-log > .scripts, +#log { + width: 50%; + padding: 1rem; +} + +.script-plus-log > .scripts > :is(script, .script) { + width: 100%; +} + +.script-plus-log > .scripts, +.script-plus-log > :is(script, .script) { background-color: #eee; } @@ -48,3 +67,15 @@ script:not([src]):first-line { border-radius: 0.5rem; overflow: hidden; } + +.scripts h2 { + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid #ddd; +} + +.scripts h2:first-child { + margin-top: 0; + padding-top: 0; + border-top: 0; +} diff --git a/packages/pglite/examples/utils.js b/packages/pglite/examples/utils.js index 1eeac31d..2d538d86 100644 --- a/packages/pglite/examples/utils.js +++ b/packages/pglite/examples/utils.js @@ -18,5 +18,10 @@ const code = await source; script.textContent = code; }); + document.body.querySelectorAll("div.script[rel]").forEach(async (el) => { + const source = fetch(el.getAttribute("rel")).then((res) => res.text()); + const code = await source; + el.textContent = code; + }); }); } diff --git a/packages/pglite/examples/worker.html b/packages/pglite/examples/worker.html index 8f6c56d6..15fd0fc2 100644 --- a/packages/pglite/examples/worker.html +++ b/packages/pglite/examples/worker.html @@ -38,6 +38,10 @@

PGlite Worker Example


 
+
+

Worker Thread - worker-process.js

+
+

Main Thread

+
diff --git a/packages/repl/package.json b/packages/repl/package.json index 3e4dac1e..cb0525a6 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -38,7 +38,9 @@ "@codemirror/lang-sql": "^6.6.4", "@codemirror/language": "^6.10.2", "@codemirror/view": "^6.28.1", + "@uiw/codemirror-theme-github": "^4.23.0", "@uiw/codemirror-theme-xcode": "^4.22.2", + "@uiw/codemirror-themes": "^4.23.0", "@uiw/react-codemirror": "^4.22.2", "psql-describe": "^0.1.5", "react": "^18.2.0", diff --git a/packages/repl/src-webcomponent/main.tsx b/packages/repl/src-webcomponent/main.tsx index f4ff602c..3985d5a3 100644 --- a/packages/repl/src-webcomponent/main.tsx +++ b/packages/repl/src-webcomponent/main.tsx @@ -1,5 +1,11 @@ import { createRoot } from 'react-dom/client' -import { Repl } from '../src/Repl' +import { + Repl, + defaultLightThemeInit, + defaultLightTheme, + defaultDarkThemeInit, + defaultDarkTheme, +} from '../src/Repl' import type { ReplProps, ReplTheme } from '../src/Repl' import type { PGlite } from '@electric-sql/pglite' import type { Extension } from '@uiw/react-codemirror' @@ -9,6 +15,13 @@ import css from '../src/Repl.css?raw' export type { ReplProps, ReplTheme } +export { + defaultLightThemeInit, + defaultLightTheme, + defaultDarkThemeInit, + defaultDarkTheme, +} + export class PGliteREPL extends HTMLElement { #mountPoint: HTMLDivElement #root: ReturnType @@ -19,6 +32,7 @@ export class PGliteREPL extends HTMLElement { constructor() { super() this.#mountPoint = document.createElement('div') + this.#mountPoint.classList.add('PGliteRepl-root') const shadowRoot = this.attachShadow({ mode: 'open' }) const style = document.createElement('style') style.textContent = css diff --git a/packages/repl/src/Repl.tsx b/packages/repl/src/Repl.tsx index 36d691bc..b92bb824 100644 --- a/packages/repl/src/Repl.tsx +++ b/packages/repl/src/Repl.tsx @@ -4,6 +4,7 @@ import CodeMirror, { type ReactCodeMirrorRef, type Extension, } from '@uiw/react-codemirror' +import { type CreateThemeOptions } from '@uiw/codemirror-themes' import { defaultKeymap } from '@codemirror/commands' import { keymap } from '@codemirror/view' import { PostgreSQL } from '@codemirror/lang-sql' @@ -12,7 +13,12 @@ import { makeSqlExt } from './sqlSupport' import type { Response } from './types' import { runQuery, getSchema } from './utils' import { ReplResponse } from './ReplResponse' -import { xcodeDark, xcodeLight } from '@uiw/codemirror-theme-xcode' +import { + githubDark, + githubDarkInit, + githubLight, + githubLightInit, +} from '@uiw/codemirror-theme-github' import './Repl.css' @@ -24,6 +30,13 @@ const baseKeymap = defaultKeymap.filter((key) => key.key !== 'Enter') export type ReplTheme = 'light' | 'dark' | 'auto' +type ThemeInit = (options?: Partial) => Extension + +export const defaultLightThemeInit: ThemeInit = githubLightInit +export const defaultLightTheme = githubLight +export const defaultDarkThemeInit: ThemeInit = githubDarkInit +export const defaultDarkTheme = githubDark + export interface ReplProps { pg: PGlite border?: boolean @@ -37,8 +50,8 @@ export interface ReplProps { export function Repl({ pg, border = false, - lightTheme = xcodeLight, - darkTheme = xcodeDark, + lightTheme = defaultLightTheme, + darkTheme = defaultDarkTheme, theme = 'auto', showTime = false, disableUpdateSchema = false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5538a19..058348d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,19 @@ importers: specifier: ^5.5.4 version: 5.5.4 + docs: + dependencies: + '@uiw/codemirror-theme-github': + specifier: ^4.23.0 + version: 4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1) + dedent: + specifier: ^1.5.3 + version: 1.5.3 + devDependencies: + vitepress: + specifier: ^1.3.1 + version: 1.3.2(@algolia/client-search@4.24.0)(search-insights@2.16.0)(typescript@5.5.4) + packages/benchmark: dependencies: wa-sqlite: @@ -173,9 +186,15 @@ importers: '@codemirror/view': specifier: ^6.28.1 version: 6.28.1 + '@uiw/codemirror-theme-github': + specifier: ^4.23.0 + version: 4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1) '@uiw/codemirror-theme-xcode': specifier: ^4.22.2 version: 4.22.2(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1) + '@uiw/codemirror-themes': + specifier: ^4.23.0 + version: 4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1) '@uiw/react-codemirror': specifier: ^4.22.2 version: 4.22.2(@babel/runtime@7.24.0)(@codemirror/autocomplete@6.16.3)(@codemirror/language@6.10.2)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.28.1)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1) @@ -228,6 +247,156 @@ importers: packages: + /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.16.0): + resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.16.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + dev: true + + /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.16.0): + resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + peerDependencies: + search-insights: '>= 1 < 3' + dependencies: + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + search-insights: 2.16.0 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + dev: true + + /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0): + resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + dependencies: + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + '@algolia/client-search': 4.24.0 + algoliasearch: 4.24.0 + dev: true + + /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0): + resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + dependencies: + '@algolia/client-search': 4.24.0 + algoliasearch: 4.24.0 + dev: true + + /@algolia/cache-browser-local-storage@4.24.0: + resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} + dependencies: + '@algolia/cache-common': 4.24.0 + dev: true + + /@algolia/cache-common@4.24.0: + resolution: {integrity: sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==} + dev: true + + /@algolia/cache-in-memory@4.24.0: + resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} + dependencies: + '@algolia/cache-common': 4.24.0 + dev: true + + /@algolia/client-account@4.24.0: + resolution: {integrity: sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-analytics@4.24.0: + resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-common@4.24.0: + resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} + dependencies: + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-personalization@4.24.0: + resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-search@4.24.0: + resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/logger-common@4.24.0: + resolution: {integrity: sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==} + dev: true + + /@algolia/logger-console@4.24.0: + resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} + dependencies: + '@algolia/logger-common': 4.24.0 + dev: true + + /@algolia/recommend@4.24.0: + resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/requester-browser-xhr@4.24.0: + resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} + dependencies: + '@algolia/requester-common': 4.24.0 + dev: true + + /@algolia/requester-common@4.24.0: + resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} + dev: true + + /@algolia/requester-node-http@4.24.0: + resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} + dependencies: + '@algolia/requester-common': 4.24.0 + dev: true + + /@algolia/transporter@4.24.0: + resolution: {integrity: sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==} + dependencies: + '@algolia/cache-common': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + dev: true + /@ampproject/remapping@2.3.0: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -340,14 +509,14 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.2 dev: true /@babel/helper-hoist-variables@7.24.7: resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.25.2 dev: true /@babel/helper-member-expression-to-functions@7.24.8: @@ -538,7 +707,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.3 '@babel/types': 7.24.7 dev: true @@ -561,7 +730,7 @@ packages: '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.3 '@babel/types': 7.24.7 debug: 4.3.6 globals: 11.12.0 @@ -686,6 +855,49 @@ packages: w3c-keyname: 2.2.8 dev: false + /@docsearch/css@3.6.1: + resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} + dev: true + + /@docsearch/js@3.6.1(@algolia/client-search@4.24.0)(search-insights@2.16.0): + resolution: {integrity: sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==} + dependencies: + '@docsearch/react': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.16.0) + preact: 10.23.1 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + dev: true + + /@docsearch/react@3.6.1(@algolia/client-search@4.24.0)(search-insights@2.16.0): + resolution: {integrity: sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + dependencies: + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.16.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + '@docsearch/css': 3.6.1 + algoliasearch: 4.24.0 + search-insights: 2.16.0 + transitivePeerDependencies: + - '@algolia/client-search' + dev: true + /@embedded-postgres/darwin-arm64@15.5.1-beta.11: resolution: {integrity: sha512-5m96qe7TFR/wzL05fyl1TRKfm+I73gIdDea+vXh60MQzUUfX9FXSiR8id6TI4aRhomUrd/l8hLTq8E2ymTCIFw==} engines: {node: '>=16'} @@ -2148,6 +2360,18 @@ packages: - '@types/node' dev: true + /@shikijs/core@1.12.1: + resolution: {integrity: sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==} + dependencies: + '@types/hast': 3.0.4 + dev: true + + /@shikijs/transformers@1.12.1: + resolution: {integrity: sha512-zOpj/S2thBvnJV4Ty3EE8aRs/VqCbV+lgtEYeBRkPxTW22uLADEIZq0qjt5W2Rfy2KSu29e73nRyzp4PefjUTg==} + dependencies: + shiki: 1.12.1 + dev: true + /@sindresorhus/merge-streams@2.3.0: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -2217,7 +2441,7 @@ packages: /@types/babel__template@7.4.4: resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.3 '@babel/types': 7.24.7 dev: true @@ -2241,6 +2465,27 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.2 + dev: true + + /@types/linkify-it@5.0.0: + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + dev: true + + /@types/markdown-it@14.1.2: + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + dev: true + + /@types/mdurl@2.0.0: + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + dev: true + /@types/node-fetch@2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: @@ -2271,6 +2516,14 @@ packages: csstype: 3.1.3 dev: true + /@types/unist@3.0.2: + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + dev: true + + /@types/web-bluetooth@0.0.20: + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + dev: true + /@typescript-eslint/eslint-plugin@7.13.1(@typescript-eslint/parser@7.13.1)(eslint@8.57.0)(typescript@5.5.4): resolution: {integrity: sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -2312,7 +2565,7 @@ packages: '@typescript-eslint/types': 7.13.1 '@typescript-eslint/typescript-estree': 7.13.1(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.13.1 - debug: 4.3.4 + debug: 4.3.6 eslint: 8.57.0 typescript: 5.5.4 transitivePeerDependencies: @@ -2496,6 +2749,16 @@ packages: '@codemirror/view': 6.28.1 dev: false + /@uiw/codemirror-theme-github@4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1): + resolution: {integrity: sha512-1pJ9V7LQXoojfgYXgI4yn8CfaYBm9HS919xC32/rs81Wl1lhYEOhiYRmNcpnJQDu9ZMgO8ebPMgAVU21z/C76g==} + dependencies: + '@uiw/codemirror-themes': 4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + dev: false + /@uiw/codemirror-theme-xcode@4.22.2(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1): resolution: {integrity: sha512-pHOeWHMK7lmxGjrwBnvaIsFppMaKjDbZ6fgWCItWUFI+JeO4/Orrn+q0r1tRbsbRAMPaVuxl+SUZUhPGuo8GCw==} dependencies: @@ -2518,6 +2781,18 @@ packages: '@codemirror/view': 6.28.1 dev: false + /@uiw/codemirror-themes@4.23.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.1): + resolution: {integrity: sha512-9fiji9xooZyBQozR1i6iTr56YP7j/Dr/VgsNWbqf5Szv+g+4WM1iZuiDGwNXmFMWX8gbkDzp6ASE21VCPSofWw==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + dependencies: + '@codemirror/language': 6.10.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.28.1 + dev: false + /@uiw/react-codemirror@4.22.2(@babel/runtime@7.24.0)(@codemirror/autocomplete@6.16.3)(@codemirror/language@6.10.2)(@codemirror/lint@6.8.1)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.28.1)(codemirror@6.0.1)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-okCSl+WJG63gRx8Fdz7v0C6RakBQnbb3pHhuzIgDB+fwhipgFodSnu2n9oOsQesJ5YQ7mSOcKMgX0JEsu4nnfQ==} peerDependencies: @@ -2582,11 +2857,22 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.3.1 + vite: 5.3.1(@types/node@20.11.19)(terser@5.31.1) transitivePeerDependencies: - supports-color dev: true + /@vitejs/plugin-vue@5.1.2(vite@5.4.0)(vue@3.4.36): + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + dependencies: + vite: 5.4.0 + vue: 3.4.36(typescript@5.5.4) + dev: true + /@vitest/expect@2.0.5: resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} dependencies: @@ -2678,6 +2964,16 @@ packages: source-map-js: 1.2.0 dev: true + /@vue/compiler-core@3.4.36: + resolution: {integrity: sha512-qBkndgpwFKdupmOPoiS10i7oFdN7a+4UNDlezD0GlQ1kuA1pNrscg9g12HnB5E8hrWSuEftRsbJhL1HI2zpJhg==} + dependencies: + '@babel/parser': 7.25.3 + '@vue/shared': 3.4.36 + entities: 5.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + dev: true + /@vue/compiler-dom@3.4.29: resolution: {integrity: sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==} dependencies: @@ -2685,6 +2981,34 @@ packages: '@vue/shared': 3.4.29 dev: true + /@vue/compiler-dom@3.4.36: + resolution: {integrity: sha512-eEIjy4GwwZTFon/Y+WO8tRRNGqylaRlA79T1RLhUpkOzJ7EtZkkb8MurNfkqY6x6Qiu0R7ESspEF7GkPR/4yYg==} + dependencies: + '@vue/compiler-core': 3.4.36 + '@vue/shared': 3.4.36 + dev: true + + /@vue/compiler-sfc@3.4.36: + resolution: {integrity: sha512-rhuHu7qztt/rNH90dXPTzhB7hLQT2OC4s4GrPVqmzVgPY4XBlfWmcWzn4bIPEWNImt0CjO7kfHAf/1UXOtx3vw==} + dependencies: + '@babel/parser': 7.25.3 + '@vue/compiler-core': 3.4.36 + '@vue/compiler-dom': 3.4.36 + '@vue/compiler-ssr': 3.4.36 + '@vue/shared': 3.4.36 + estree-walker: 2.0.2 + magic-string: 0.30.10 + postcss: 8.4.41 + source-map-js: 1.2.0 + dev: true + + /@vue/compiler-ssr@3.4.36: + resolution: {integrity: sha512-Wt1zyheF0zVvRJyhY74uxQbnkXV2Le/JPOrAxooR4rFYKC7cFr+cRqW6RU3cM/bsTy7sdZ83IDuy/gLPSfPGng==} + dependencies: + '@vue/compiler-dom': 3.4.36 + '@vue/shared': 3.4.36 + dev: true + /@vue/compiler-vue2@2.7.16: resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} dependencies: @@ -2692,6 +3016,30 @@ packages: he: 1.2.0 dev: true + /@vue/devtools-api@7.3.7: + resolution: {integrity: sha512-kvjQ6nmsqTp7SrmpwI2G0MgbC4ys0bPsgQirHXJM8y1m7siQ5RnWQUHJVfyUrHNguCySW1cevAdIw87zrPTl9g==} + dependencies: + '@vue/devtools-kit': 7.3.7 + dev: true + + /@vue/devtools-kit@7.3.7: + resolution: {integrity: sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==} + dependencies: + '@vue/devtools-shared': 7.3.7 + birpc: 0.2.17 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.1 + dev: true + + /@vue/devtools-shared@7.3.7: + resolution: {integrity: sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==} + dependencies: + rfdc: 1.4.1 + dev: true + /@vue/language-core@2.0.29(typescript@5.5.4): resolution: {integrity: sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==} peerDependencies: @@ -2711,10 +3059,121 @@ packages: typescript: 5.5.4 dev: true + /@vue/reactivity@3.4.36: + resolution: {integrity: sha512-wN1aoCwSoqrt1yt8wO0gc13QaC+Vk1o6AoSt584YHNnz6TGDhh1NCMUYgAnvp4HEIkLdGsaC1bvu/P+wpoDEXw==} + dependencies: + '@vue/shared': 3.4.36 + dev: true + + /@vue/runtime-core@3.4.36: + resolution: {integrity: sha512-9+TR14LAVEerZWLOm/N/sG2DVYhrH2bKgFrbH/FVt/Q8Jdw4OtdcGMRC6Tx8VAo0DA1eqAqrZaX0fbOaOxxZ4A==} + dependencies: + '@vue/reactivity': 3.4.36 + '@vue/shared': 3.4.36 + dev: true + + /@vue/runtime-dom@3.4.36: + resolution: {integrity: sha512-2Qe2fKkLxgZBVvHrG0QMNLL4bsx7Ae88pyXebY2WnQYABpOnGYvA+axMbcF9QwM4yxnsv+aELbC0eiNVns7mGw==} + dependencies: + '@vue/reactivity': 3.4.36 + '@vue/runtime-core': 3.4.36 + '@vue/shared': 3.4.36 + csstype: 3.1.3 + dev: true + + /@vue/server-renderer@3.4.36(vue@3.4.36): + resolution: {integrity: sha512-2XW90Rq8+Y7S1EIsAuubZVLm0gCU8HYb5mRAruFdwfC3XSOU5/YKePz29csFzsch8hXaY5UHh7ZMddmi1XTJEA==} + peerDependencies: + vue: 3.4.36 + dependencies: + '@vue/compiler-ssr': 3.4.36 + '@vue/shared': 3.4.36 + vue: 3.4.36(typescript@5.5.4) + dev: true + /@vue/shared@3.4.29: resolution: {integrity: sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==} dev: true + /@vue/shared@3.4.36: + resolution: {integrity: sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==} + dev: true + + /@vueuse/core@10.11.0(vue@3.4.36): + resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.0 + '@vueuse/shared': 10.11.0(vue@3.4.36) + vue-demi: 0.14.10(vue@3.4.36) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/integrations@10.11.0(focus-trap@7.5.4)(vue@3.4.36): + resolution: {integrity: sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^4 + drauu: ^0.3 + focus-trap: ^7 + fuse.js: ^6 + idb-keyval: ^6 + jwt-decode: ^3 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^6 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + dependencies: + '@vueuse/core': 10.11.0(vue@3.4.36) + '@vueuse/shared': 10.11.0(vue@3.4.36) + focus-trap: 7.5.4 + vue-demi: 0.14.10(vue@3.4.36) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/metadata@10.11.0: + resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==} + dev: true + + /@vueuse/shared@10.11.0(vue@3.4.36): + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} + dependencies: + vue-demi: 0.14.10(vue@3.4.36) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + /@zeit/schemas@2.36.0: resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} dev: true @@ -2839,6 +3298,26 @@ packages: uri-js: 4.4.1 dev: true + /algoliasearch@4.24.0: + resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-account': 4.24.0 + '@algolia/client-analytics': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-personalization': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/recommend': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: @@ -3077,6 +3556,10 @@ packages: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} dev: true + /birpc@0.2.17: + resolution: {integrity: sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==} + dev: true + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -3507,6 +3990,13 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.16 + dev: true + /corser@2.0.1: resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} engines: {node: '>= 0.4.0'} @@ -3626,6 +4116,15 @@ packages: mimic-response: 3.1.0 dev: true + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: false + /deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3740,6 +4239,11 @@ packages: engines: {node: '>=0.12'} dev: true + /entities@5.0.0: + resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} + engines: {node: '>=0.12'} + dev: true + /es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -4296,6 +4800,12 @@ packages: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true + /focus-trap@7.5.4: + resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + dependencies: + tabbable: 6.2.0 + dev: true + /follow-redirects@1.15.5: resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} engines: {node: '>=4.0'} @@ -4581,6 +5091,10 @@ packages: hermes-estree: 0.20.1 dev: true + /hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + dev: true + /html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} @@ -4832,6 +5346,11 @@ packages: engines: {node: '>=18'} dev: true + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -5072,6 +5591,10 @@ packages: semver: 6.3.1 dev: true + /mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + dev: true + /matcher@5.0.0: resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5214,6 +5737,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true + /minisearch@7.1.0: + resolution: {integrity: sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==} + dev: true + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -5222,6 +5749,10 @@ packages: yallist: 4.0.0 dev: true + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: true + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true @@ -5527,6 +6058,10 @@ packages: engines: {node: '>= 14.16'} dev: true + /perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + dev: true + /pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} requiresBuild: true @@ -5693,6 +6228,15 @@ packages: source-map-js: 1.2.0 dev: true + /postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + dev: true + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -5715,6 +6259,10 @@ packages: xtend: 4.0.2 dev: true + /preact@10.23.1: + resolution: {integrity: sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==} + dev: true + /prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -5931,6 +6479,10 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: true + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -6034,6 +6586,10 @@ packages: dependencies: loose-envify: 1.4.0 + /search-insights@2.16.0: + resolution: {integrity: sha512-6ukNTOkN2OvJ8SJRmWionpn39OHOov1rx72kyGDYk60CaGrDfmT8wXYzgKLW9VFk+dVVXlUmWQVvrkRvx/x3Mg==} + dev: true + /secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} dev: true @@ -6131,6 +6687,13 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true + /shiki@1.12.1: + resolution: {integrity: sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==} + dependencies: + '@shikijs/core': 1.12.1 + '@types/hast': 3.0.4 + dev: true + /short-unique-id@5.2.0: resolution: {integrity: sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==} hasBin: true @@ -6217,6 +6780,11 @@ packages: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} dev: true + /speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + dev: true + /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -6335,6 +6903,13 @@ packages: ts-interface-checker: 0.1.13 dev: true + /superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + dependencies: + copy-anything: 3.0.5 + dev: true + /supertap@3.0.1: resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6375,6 +6950,10 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: true + /tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} dependencies: @@ -6895,6 +7474,101 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.4.0: + resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.19.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitepress@1.3.2(@algolia/client-search@4.24.0)(search-insights@2.16.0)(typescript@5.5.4): + resolution: {integrity: sha512-6gvecsCuR6b1Cid4w19KQiQ02qkpgzFRqiG0v1ZBekGkrZCzsxdDD5y4WH82HRXAOhU4iZIpzA1CsWqs719rqA==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + dependencies: + '@docsearch/css': 3.6.1 + '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.16.0) + '@shikijs/core': 1.12.1 + '@shikijs/transformers': 1.12.1 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.1.2(vite@5.4.0)(vue@3.4.36) + '@vue/devtools-api': 7.3.7 + '@vue/shared': 3.4.36 + '@vueuse/core': 10.11.0(vue@3.4.36) + '@vueuse/integrations': 10.11.0(focus-trap@7.5.4)(vue@3.4.36) + focus-trap: 7.5.4 + mark.js: 8.11.1 + minisearch: 7.1.0 + shiki: 1.12.1 + vite: 5.4.0 + vue: 3.4.36(typescript@5.5.4) + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + dev: true + /vitest@2.0.5(jsdom@24.1.1): resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6954,6 +7628,21 @@ packages: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} dev: true + /vue-demi@0.14.10(vue@3.4.36): + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.4.36(typescript@5.5.4) + dev: true + /vue-tsc@2.0.29(typescript@5.5.4): resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==} hasBin: true @@ -6966,6 +7655,22 @@ packages: typescript: 5.5.4 dev: true + /vue@3.4.36(typescript@5.5.4): + resolution: {integrity: sha512-mIFvbLgjODfx3Iy1SrxOsiPpDb8Bo3EU+87ioimOZzZTOp15IEdAels70IjBOLO3ZFlLW5AhdwY4dWbXVQKYow==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.36 + '@vue/compiler-sfc': 3.4.36 + '@vue/runtime-dom': 3.4.36 + '@vue/server-renderer': 3.4.36(vue@3.4.36) + '@vue/shared': 3.4.36 + typescript: 5.5.4 + dev: true + /w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} dev: false diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 18ec407e..59a60bdc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - 'packages/*' + - 'docs'