diff --git a/.github/workflows/build_pglite.yml b/.github/workflows/build_pglite.yml index eea7ce245..a7b0790f3 100644 --- a/.github/workflows/build_pglite.yml +++ b/.github/workflows/build_pglite.yml @@ -113,6 +113,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 1143d84b0..a59f5afa8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store -/node_modules +node_modules /packages/pglite/dist /packages/pglite/pgdata-test /packages/pglite/package-lock.json @@ -10,3 +10,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 2cd0bd72c..02d0db122 100755 --- a/cibuild.sh +++ b/cibuild.sh @@ -344,17 +344,17 @@ do demo-site) echo "==================== demo-site ==========================" echo " - - - -" > /tmp/web/index.html + + + + " > /tmp/web/demos.html mkdir -p /tmp/web/pglite mkdir -p /tmp/web/repl diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 000000000..d92037010 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,84 @@ +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: 'localhostLinks', + 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: 'Extensions', + items: [ + { text: 'Extensions Catalog', link: '/extensions/' }, + { text: 'Extension Development', link: '/extensions/development' } + ] + }, + { + text: 'Reference', + items: [ + { 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 000000000..87335793c --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,124 @@ +@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 000000000..40c1919bc --- /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 000000000..73c2c041c --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1 @@ +# Benchmarks \ No newline at end of file diff --git a/docs/components/HeroImage.vue b/docs/components/HeroImage.vue new file mode 100644 index 000000000..03f4d1880 --- /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 000000000..7b4485154 --- /dev/null +++ b/docs/components/Repl.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/docs/docs/about.md b/docs/docs/about.md new file mode 100644 index 000000000..4e72e5487 --- /dev/null +++ b/docs/docs/about.md @@ -0,0 +1 @@ +# What is PGlite \ No newline at end of file diff --git a/docs/docs/api.md b/docs/docs/api.md new file mode 100644 index 000000000..bed1cb709 --- /dev/null +++ b/docs/docs/api.md @@ -0,0 +1,324 @@ +--- +outline: [2, 3] +--- + +# PGlite API + +## Main Constructor + +`new PGlite(dataDir: string, options: PGliteOptions)`
+`new PGlite(options: PGliteOptions)` + +A new pglite instance is created using the `new PGlite()` constructor. + +This is imported as: + +```ts +import { PGlite } from "@electric-sql/pglite"; +``` + +`await PGlite.create(dataDir: string, options: PGliteOptions)`
+`await PGlite.create(options: PGliteOptions)` + +There is also an additional `PGlite.create()` static method that returns a Promise resolving to the new PGlite instance. There are a couple of advatanges to using the static method: + +- The Promise awaits the [`.waitReady`](#waitready) promise ensureing that database has fully initiated. +- When using TypeScript and extensions the returned PGlite instance will have the extensions namespace on it's type. This is not possible with the standard constructor. + + +#### `dataDir` + +Path to the directory to store the Postgres database. You can provide a url scheme for various storage backends: + +- `file://` or unprefixed
+ File system storage, available in Node and Bun. +- `idb://`
+ IndexedDB storage, available in the browser. +- `memory://`
+ In-memory ephemeral storage, available in all platforms. + +#### `options` + +- `dataDir: string`
+ The directory 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 initiate the 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 with 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` paramiter is an opbject of `namespace: extension` parings. The namespace if sued to expose the PGlite client plugin included in the extension. An example of this it 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, if 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-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 id to parser function. + For convenance the `pglite` package exports a const 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](#importing-and-exporting-with-copy-tofrom). + +### 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-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 is 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 have 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 gziped 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 + +Queries methods will wait for the `waitReady` promise to resolve if called before the database has fully initialised, and so it's 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](#devblob). + + +## `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 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 have to write to the `/dev/blob` device, the file will be retied 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 000000000..3ec59f386 --- /dev/null +++ b/docs/docs/filesystems.md @@ -0,0 +1,89 @@ +# Filesystems + +PGlite has a virtual file system layer that allows it to run in environments that don't traditionally have filesystem access. + +## In-memory FS + +The in-memory FS is the default when starting PGlite, and it 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() + }) + ``` + +## Node FS + +The Node FS uses the Node.js file system API to implement a VFS for PGLite. It is bailable 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/") + }) + ``` + +## IndexedDB FS + +The IndexedDB FS persistes 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. + +To use the IndexedDB FS you can use one of these methods: + +- Set the `dataDir` with a `idb://` prefix, the FS will use 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 hole files as blobs in IndexedDB. Flushing whole files can take a few milliseconds after each query, to aid in building resposive 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 imediatly, and the flush to IndexedDB is scheduled to happen asynchronous afterwards. Typically this is immediately after the query returns with no delay. + +## 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, however 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 with 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/") + }) + ``` + +### What is an "access handle pool"? + +The Origin Private Filesystem API provides both asynchronous ans synchronous methods, bit the synchronous are limited to read, write and flush. You are unable to travers 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 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 allocation to files as needed. After each query a poll maintenance job is scheduled that maintains the pool 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 that contains 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 000000000..56a1f0448 --- /dev/null +++ b/docs/docs/framework-hooks.md @@ -0,0 +1,3 @@ +# Framework Hooks + +*TODO* \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 000000000..1a2319dad --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,68 @@ +# Getting started with PGlite + +PGlite is a WASM Postgres build packaged into a TypeScript client library that enables you to run Postgres in the browser, Node.js and Bun, with no need to install any other dependencies. It is only 2.6mb gzipped. + +```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. + +## Node/Bun + +Install into your project: + +```bash +npm install @electric-sql/pglite +``` + +To use the in-memory Postgres: + +```js + +import { PGlite } from "@electric-sql/pglite"; + +const db = new PGlite(); +await db.query("select 'Hello world' as message;"); +// -> { rows: [ { message: "Hello world" } ] } +``` + +or to persist to the filesystem: + +```js + +const db = new PGlite("./path/to/pgdata"); +``` + +## 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() +await db.query("select 'Hello world' as message;") +// -> { rows: [ { message: "Hello world" } ] } +``` + +or to persist the database to indexedDB: + +```js +const db = new PGlite("idb://my-pgdata"); +``` \ No newline at end of file diff --git a/docs/docs/live-queries.md b/docs/docs/live-queries.md new file mode 100644 index 000000000..a96559070 --- /dev/null +++ b/docs/docs/live-queries.md @@ -0,0 +1,126 @@ +# Live Queries + +The "live" extension enables you to subscribe to a query and receve updated results when the underlying tables change. + +To use the extension it needs adding to the PGlite instance when creating it: + +```ts +import { PGlite } from "@electric-sql/pglite"; +import { live } from "@electric-sql/pglite/live"; + +const pg = new PGlite({ + extensions: { + live, + }, +}); +``` + +There are three methods on the `live` namespace: +- `live.query()` for basic live queries. With less machinery in PG 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 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` allow you to unsubscribe from the live query +- `refresh` allows you to force a refresh of the query + +Internally it watches for 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 inside of Postgres of the previous state. 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 PK) to key the diff on. + +```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 change that have happened. It requires a `key` to key the diff on: + +```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 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 columns names that were changes +- `__op__` the operation that is required to update the state (`INSERT`, `UPDATE`, `DELETE`) +- `__after__` the `key` of the row that this row should be after, it will be included in `__changed_columns__` if it has been changed. + +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 000000000..36869b307 --- /dev/null +++ b/docs/docs/multi-tab-worker.md @@ -0,0 +1,152 @@ +# 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, 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, initiate PGlite with the worker extension, and start it: + +```js +// my-pglite-worker.js +import { PGlite } from "@electric-sql/pglite"; +import { worker } from "@electric-sql/pglite/worker"; + +worker({ + async init() { + return new PGlite(); + }, +}); +``` + +Then connect the `PGliteWorker` to your new worker process in you 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 a leader election to nominate one as the leader. Only the leader then opens the PGlite and handles all queries. When the leader tab is closed, a new leader election is run and a new PGlite instance is started. + +In addition to having all the standrad 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 paramiter `options`, this can include any standard [PGlite options](./api.md#options) along with these addtional options: + +- `id: string`
+ This is an optional `id` to gide your PGlite worker group. 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 aditional 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 takes sed 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. + + 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 use by the PGlite instance inside the worker: + +```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 await for the `pg.live` namespace: +pg.live.query(/* ... */) +``` diff --git a/docs/docs/repl.md b/docs/docs/repl.md new file mode 100644 index 000000000..e980d1a03 --- /dev/null +++ b/docs/docs/repl.md @@ -0,0 +1,117 @@ +--- +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 embeded 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, its also available as a web component for easy inclusion in any page or other framework. + +```html + + + + + + +``` + +### With Vue.js + +The REPL Web Component can be used with Vue.js, and in fact thats how its embeded above. + +```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). \ No newline at end of file diff --git a/docs/extensions/development.md b/docs/extensions/development.md new file mode 100644 index 000000000..ec027b93e --- /dev/null +++ b/docs/extensions/development.md @@ -0,0 +1 @@ +# Extension Development \ No newline at end of file diff --git a/docs/extensions/index.md b/docs/extensions/index.md new file mode 100644 index 000000000..a0ed4d3cf --- /dev/null +++ b/docs/extensions/index.md @@ -0,0 +1 @@ +# PGlite Extensions \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..c083b6847 --- /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: 'WASM Postgres with reactivity and 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: Pure WASM build of Postgres using Emscripten that's less than 3MB Gzipped. + - title: Extensible + 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 000000000..254ae5784 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,14 @@ +{ + "name": "docs", + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + }, + "devDependencies": { + "vitepress": "^1.3.1" + }, + "dependencies": { + "@uiw/codemirror-theme-github": "^4.23.0" + } +} \ No newline at end of file diff --git a/docs/public/img/brand/icon-light.svg b/docs/public/img/brand/icon-light.svg new file mode 100644 index 000000000..c648b0f64 --- /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 000000000..48d31f5d7 --- /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 000000000..067d5828f --- /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 000000000..78795c5f2 --- /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 000000000..936f0616f Binary files /dev/null and b/docs/public/img/postgres-new.png differ diff --git a/packages/repl/package.json b/packages/repl/package.json index 3fa0abebe..d019a0719 100644 --- a/packages/repl/package.json +++ b/packages/repl/package.json @@ -36,7 +36,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 00070e349..cf2ccba82 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; @@ -101,7 +115,7 @@ export class PGliteREPL extends HTMLElement { ) : (
PGlite instance not provided
)} - , + ); } } diff --git a/packages/repl/src/Repl.tsx b/packages/repl/src/Repl.tsx index d223a7114..a41136ea5 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,7 @@ 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 +25,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 +45,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 44955bb4e..643d13d27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,11 +13,21 @@ importers: .: {} + 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) + devDependencies: + vitepress: + specifier: ^1.3.1 + version: 1.3.1(@algolia/client-search@4.24.0)(search-insights@2.15.0) + packages/benchmark: dependencies: wa-sqlite: specifier: github:rhashimoto/wa-sqlite - version: github.com/rhashimoto/wa-sqlite/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a + version: github.com/rhashimoto/wa-sqlite/f5092b5bce7b7edb6371ca53ec8b9ebf332d82c2 devDependencies: '@types/better-sqlite3': specifier: ^7.6.9 @@ -106,9 +116,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) @@ -167,6 +183,156 @@ importers: packages: + /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.15.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.15.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.15.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.15.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'} @@ -489,6 +655,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.15.0): + resolution: {integrity: sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==} + dependencies: + '@docsearch/react': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.15.0) + preact: 10.23.0 + 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.15.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.15.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.15.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'} @@ -1574,6 +1783,18 @@ packages: - '@types/node' dev: true + /@shikijs/core@1.11.1: + resolution: {integrity: sha512-Qsn8h15SWgv5TDRoDmiHNzdQO2BxDe86Yq6vIHf5T0cCvmfmccJKIzHtep8bQO9HMBZYCtCBzaXdd1MnxZBPSg==} + dependencies: + '@types/hast': 3.0.4 + dev: true + + /@shikijs/transformers@1.11.1: + resolution: {integrity: sha512-e6DUvZRylv+V8htF5q3ZuNyPaxJYQnsLyTd2S/K6ePs8t132NJS82LG2vARmtfSFP3I3CcBXfJ73FaCgI9kAMg==} + dependencies: + shiki: 1.11.1 + dev: true + /@sindresorhus/merge-streams@2.3.0: resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -1626,6 +1847,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: @@ -1656,6 +1898,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.3.3): resolution: {integrity: sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1803,6 +2053,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: @@ -1825,6 +2085,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: @@ -1894,6 +2166,17 @@ packages: - supports-color dev: true + /@vitejs/plugin-vue@5.1.0(vite@5.3.4)(vue@3.4.34): + resolution: {integrity: sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + dependencies: + vite: 5.3.4 + vue: 3.4.34 + dev: true + /@volar/language-core@1.11.1: resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} dependencies: @@ -1923,6 +2206,16 @@ packages: source-map-js: 1.2.0 dev: true + /@vue/compiler-core@3.4.34: + resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==} + dependencies: + '@babel/parser': 7.24.7 + '@vue/shared': 3.4.34 + entities: 4.5.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: @@ -1930,6 +2223,58 @@ packages: '@vue/shared': 3.4.29 dev: true + /@vue/compiler-dom@3.4.34: + resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==} + dependencies: + '@vue/compiler-core': 3.4.34 + '@vue/shared': 3.4.34 + dev: true + + /@vue/compiler-sfc@3.4.34: + resolution: {integrity: sha512-x6lm0UrM03jjDXTPZgD9Ad8bIVD1ifWNit2EaWQIZB5CULr46+FbLQ5RpK7AXtDHGjx9rmvC7QRCTjsiGkAwRw==} + dependencies: + '@babel/parser': 7.24.7 + '@vue/compiler-core': 3.4.34 + '@vue/compiler-dom': 3.4.34 + '@vue/compiler-ssr': 3.4.34 + '@vue/shared': 3.4.34 + estree-walker: 2.0.2 + magic-string: 0.30.10 + postcss: 8.4.40 + source-map-js: 1.2.0 + dev: true + + /@vue/compiler-ssr@3.4.34: + resolution: {integrity: sha512-8TDBcLaTrFm5rnF+Qm4BlliaopJgqJ28Nsrc80qazynm5aJO+Emu7y0RWw34L8dNnTRdcVBpWzJxhGYzsoVu4g==} + dependencies: + '@vue/compiler-dom': 3.4.34 + '@vue/shared': 3.4.34 + 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@1.8.27(typescript@5.3.3): resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} peerDependencies: @@ -1950,10 +2295,121 @@ packages: vue-template-compiler: 2.7.16 dev: true + /@vue/reactivity@3.4.34: + resolution: {integrity: sha512-ua+Lo+wBRlBEX9TtgPOShE2JwIO7p6BTZ7t1KZVPoaBRfqbC7N3c8Mpzicx173fXxx5VXeU6ykiHo7WgLzJQDA==} + dependencies: + '@vue/shared': 3.4.34 + dev: true + + /@vue/runtime-core@3.4.34: + resolution: {integrity: sha512-PXhkiRPwcPGJ1BnyBZFI96GfInCVskd0HPNIAZn7i3YOmLbtbTZpB7/kDTwC1W7IqdGPkTVC63IS7J2nZs4Ebg==} + dependencies: + '@vue/reactivity': 3.4.34 + '@vue/shared': 3.4.34 + dev: true + + /@vue/runtime-dom@3.4.34: + resolution: {integrity: sha512-dXqIe+RqFAK2Euak4UsvbIupalrhc67OuQKpD7HJ3W2fv8jlqvI7szfBCsAEcE8o/wyNpkloxB6J8viuF/E3gw==} + dependencies: + '@vue/reactivity': 3.4.34 + '@vue/runtime-core': 3.4.34 + '@vue/shared': 3.4.34 + csstype: 3.1.3 + dev: true + + /@vue/server-renderer@3.4.34(vue@3.4.34): + resolution: {integrity: sha512-GeyEUfMVRZMD/mZcNONEqg7MiU10QQ1DB3O/Qr6+8uXpbwdlmVgQ5Qs1/ZUAFX1X2UUtqMoGrDRbxdWfOJFT7Q==} + peerDependencies: + vue: 3.4.34 + dependencies: + '@vue/compiler-ssr': 3.4.34 + '@vue/shared': 3.4.34 + vue: 3.4.34 + dev: true + /@vue/shared@3.4.29: resolution: {integrity: sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==} dev: true + /@vue/shared@3.4.34: + resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==} + dev: true + + /@vueuse/core@10.11.0(vue@3.4.34): + 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.34) + vue-demi: 0.14.9(vue@3.4.34) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/integrations@10.11.0(focus-trap@7.5.4)(vue@3.4.34): + 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.34) + '@vueuse/shared': 10.11.0(vue@3.4.34) + focus-trap: 7.5.4 + vue-demi: 0.14.9(vue@3.4.34) + 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.34): + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} + dependencies: + vue-demi: 0.14.9(vue@3.4.34) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true @@ -2003,6 +2459,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-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2207,6 +2683,10 @@ packages: file-uri-to-path: 1.0.0 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: @@ -2542,6 +3022,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'} @@ -3046,6 +3533,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'} @@ -3302,6 +3795,10 @@ packages: hasBin: true 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'} @@ -3486,6 +3983,11 @@ packages: engines: {node: '>=18'} dev: true + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -3663,6 +4165,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} @@ -3781,6 +4287,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'} @@ -3789,6 +4299,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 @@ -4025,6 +4539,10 @@ packages: engines: {node: '>=12'} 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 @@ -4172,6 +4690,15 @@ packages: source-map-js: 1.2.0 dev: true + /postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + 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'} @@ -4194,6 +4721,10 @@ packages: xtend: 4.0.2 dev: true + /preact@10.23.0: + resolution: {integrity: sha512-Pox0jeY4q6PGkFB5AsXni+zHxxx/sAYFIFZzukW4nIpoJLRziRX0xC4WjZENlkSrDQvqVgZcaZzZ/NL8/A+H/w==} + dev: true + /prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -4364,6 +4895,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 @@ -4450,6 +4985,10 @@ packages: loose-envify: 1.4.0 dev: false + /search-insights@2.15.0: + resolution: {integrity: sha512-ch2sPCUDD4sbPQdknVl9ALSi9H7VyoeVbsxznYz6QV55jJ8CI3EtwpO1i84keN4+hF5IeHWIeGvc08530JkVXQ==} + dev: true + /secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} dev: true @@ -4514,6 +5053,13 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true + /shiki@1.11.1: + resolution: {integrity: sha512-VHD3Q0EBXaaa245jqayBe5zQyMQUdXBFjmGr9MpDaDpAKRMYn7Ff00DM5MLk26UyKjnml3yQ0O2HNX7PtYVNFQ==} + dependencies: + '@shikijs/core': 1.11.1 + '@types/hast': 3.0.4 + dev: true + /side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -4591,6 +5137,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'} @@ -4692,6 +5243,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} @@ -4728,6 +5286,10 @@ packages: engines: {node: '>= 0.4'} 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: @@ -5053,6 +5615,112 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.3.4: + resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} + 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: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.21.5 + postcss: 8.4.40 + rollup: 4.18.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitepress@1.3.1(@algolia/client-search@4.24.0)(search-insights@2.15.0): + resolution: {integrity: sha512-soZDpg2rRVJNIM/IYMNDPPr+zTHDA5RbLDHAxacRu+Q9iZ2GwSR0QSUlLs+aEZTkG0SOX1dc8RmUYwyuxK8dfQ==} + 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.15.0) + '@shikijs/core': 1.11.1 + '@shikijs/transformers': 1.11.1 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.1.0(vite@5.3.4)(vue@3.4.34) + '@vue/devtools-api': 7.3.7 + '@vue/shared': 3.4.34 + '@vueuse/core': 10.11.0(vue@3.4.34) + '@vueuse/integrations': 10.11.0(focus-trap@7.5.4)(vue@3.4.34) + focus-trap: 7.5.4 + mark.js: 8.11.1 + minisearch: 7.1.0 + shiki: 1.11.1 + vite: 5.3.4 + vue: 3.4.34 + 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 + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + dev: true + + /vue-demi@0.14.9(vue@3.4.34): + resolution: {integrity: sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==} + 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.34 + dev: true + /vue-template-compiler@2.7.16: resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} dependencies: @@ -5072,6 +5740,21 @@ packages: typescript: 5.3.3 dev: true + /vue@3.4.34: + resolution: {integrity: sha512-VZze05HWlA3ItreQ/ka7Sx7PoD0/3St8FEiSlSTVgb6l4hL+RjtP2/8g5WQBzZgyf8WG2f+g1bXzC7zggLhAJA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.34 + '@vue/compiler-sfc': 3.4.34 + '@vue/runtime-dom': 3.4.34 + '@vue/server-renderer': 3.4.34(vue@3.4.34) + '@vue/shared': 3.4.34 + dev: true + /w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} dev: false @@ -5218,8 +5901,8 @@ packages: commander: 9.5.0 dev: true - github.com/rhashimoto/wa-sqlite/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a: - resolution: {tarball: https://codeload.github.com/rhashimoto/wa-sqlite/tar.gz/f1ab3d5d795fb4156e0b8e20ce2f58c05dfee74a} + github.com/rhashimoto/wa-sqlite/f5092b5bce7b7edb6371ca53ec8b9ebf332d82c2: + resolution: {tarball: https://codeload.github.com/rhashimoto/wa-sqlite/tar.gz/f5092b5bce7b7edb6371ca53ec8b9ebf332d82c2} name: wa-sqlite - version: 1.0.0 + version: 1.0.1 dev: false diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 18ec407ef..59a60bdc2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - 'packages/*' + - 'docs'