diff --git a/README.md b/README.md
index 858cc46..2b94b73 100644
--- a/README.md
+++ b/README.md
@@ -67,10 +67,11 @@ Created 'example.png' with 8 frames.
# Code Examples
-- [spliting QRs](bbqr/split.py)
-- [joining QRs](bbqr/join.py)
-- [binary to internal encoding](bbqr/utils.py)
-- [wrapper CLI](bbqr/cli.py)
+- Splitting QRs: [Python](python/bbqr/split.py), [JS](js/src/split.ts)
+- Joining QRs: [Python](python/bbqr/join.py), [JS](js/src/join.ts)
+- Binary to internal encoding: [Python](python/bbqr/utils.py), [JS](js/src/utils.ts)
+- Wrapper CLI: [Python](python/bbqr/cli.py)
+- [Example of using the JS implementation](https://bbqr.org/js-demo.html)
# License
diff --git a/js-demo.html b/js-demo.html
new file mode 100644
index 0000000..04567c4
--- /dev/null
+++ b/js-demo.html
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+ BBQr JS Demo
+
+
+
+
+
+
+
+
+
+
+BBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB QQQQQQQQQ
+B::::::::::::::::B B::::::::::::::::B QQ:::::::::QQ
+B::::::BBBBBB:::::B B::::::BBBBBB:::::B QQ:::::::::::::QQ
+BB:::::B B:::::BBB:::::B B:::::BQ:::::::QQQ:::::::Q
+ B::::B B:::::B B::::B B:::::BQ::::::O Q::::::Q rrrrr rrrrrrrrr
+ B::::B B:::::B B::::B B:::::BQ:::::O Q:::::Q r::::rrr:::::::::r
+ B::::BBBBBB:::::B B::::BBBBBB:::::B Q:::::O Q:::::Q r:::::::::::::::::r
+ B:::::::::::::BB B:::::::::::::BB Q:::::O Q:::::Q rr::::::rrrrr::::::r
+ B::::BBBBBB:::::B B::::BBBBBB:::::B Q:::::O Q:::::Q r:::::r r:::::r
+ B::::B B:::::B B::::B B:::::BQ:::::O Q:::::Q r:::::r rrrrrrr
+ B::::B B:::::B B::::B B:::::BQ:::::O QQQQ:::::Q r:::::r
+ B::::B B:::::B B::::B B:::::BQ::::::O Q::::::::Q r:::::r
+BB:::::BBBBBB::::::BBB:::::BBBBBB::::::BQ:::::::QQ::::::::Q r:::::r
+B:::::::::::::::::B B:::::::::::::::::B QQ::::::::::::::Q r:::::r
+B::::::::::::::::B B::::::::::::::::B QQ:::::::::::Q r:::::r
+BBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBB QQQQQQQQ::::QQ rrrrrrr
+ Q:::::Q
+ QQQQQQ
+
+
+
+
+ This page makes use of the
+ BBQr JavaScript library
+ to apply BBQr encoding to PSBTs, raw Bitcoin transactions, or other types of data. Visit the
+ BBQr home page for more information.
+
+
+ How to use?
+
+
+ Option A: Drag and drop a file anywhere on this page.
+ Option B: Paste the contents of a file into the textbox below.
+
+
+
+ Note: PSBTs and Bitcoin transactions are supported in raw binary format as well as
+ encoded as Base64 or HEX.
+
+
+
+
+
+
+ BBQr created
+
+
+
+
+
+ Error!
+
+
+
+
+
+ Generating BBQr, please wait...
+
+
+
+
+
+
+
+
+
+
diff --git a/js/.gitignore b/js/.gitignore
new file mode 100644
index 0000000..f06235c
--- /dev/null
+++ b/js/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist
diff --git a/js/README.md b/js/README.md
new file mode 100644
index 0000000..a43991f
--- /dev/null
+++ b/js/README.md
@@ -0,0 +1,122 @@
+# BBQr JavaScript / TypeScript implementation
+
+This is an implementation of [BBQr - Better Bitcoin QR](https://bbqr.org) for the browser, written in TypeScript.
+
+## Installation
+
+Install with npm (or your package manager of choice):
+
+```sh
+npm install bbqr
+```
+
+## Usage
+
+### Low-level splitting/joining
+
+```js
+import { joinQRs, splitQRs } from 'bbqr';
+
+// Create a Uint8Array containing the raw bytes of a PSBT, transaction or other data
+const input = new Uint8Array([
+ 112,
+ 115,
+ 98,
+ 116,
+ 255, // ... rest of PSBT bytes
+]);
+
+// ...
+
+const fileType = 'P'; // 'P' is for PSBT
+
+const splitResult = splitQRs(input, fileType, {
+ // these are optional - default values are shown
+ encoding: 'Z', // Zlib compressed base32 encoding
+ minSplit: 1, // minimum number of parts to return
+ maxSplit: 1295, // maximum number of parts to return
+ minVersion: 5, // minimum QR code version
+ maxVersion: 40, // maximum QR code version
+});
+
+// the QR code version chosen for best efficiency
+console.log(splitResult.version);
+
+// the actual encoding used - could be '2' (uncompressed base32) if the 'Z' option didn't provide a smaller result
+console.log(splitResult.encoding);
+
+// the QR code parts
+console.log(splitResult.parts);
+
+// now we do this in reverse and get back the bytes
+
+const reassembled = joinQRs(splitResult.parts);
+
+console.log(reassembled.fileType === fileType); // true
+console.log(reassembled.encoding === splitResult.encoding); // true
+console.log(reassembled.raw.every((byte, i) => byte === input[i])); // true
+```
+
+### Detecting the File Type
+
+In the previous example, we provided the raw bytes and specified the File Type to use. You can also
+detect the file type by calling `detectFileType` with a `Uint8Array`, a `File` object or a `string` (the contents of
+a text file).
+
+This is especially useful for PSBTs and Bitcoin transactions as `detectFileType` can also detect these
+in HEX and Base64 format.
+
+If a PSBT or transaction is not detected, the file type will be:
+
+- `J` for a text file that can be successfully parsed as JSON.
+- `U` for all other text files.
+- `B` for all other binary files.
+
+For other supported type codes, you should do the conersion to `Uint8Array` not rely on `detectFileType`.
+
+```js
+import { detectFileType } from 'bbqr';
+
+// hex representation of a Bitcoin transaction
+const contents =
+ '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000';
+
+const detected = await detectFileType(contents);
+
+// 'T' for transaction
+console.log(detected.fileType);
+
+// Uint8Array of the transaction bytes
+console.log(detected.raw);
+```
+
+### Generating QR Code Images
+
+The result from `splitQRs` can be passed to `renderQRImage` to generate (animated) PNG images of
+the QR codes. The result is an `ArrayBuffer` which can easily be converted to a suitable format.
+
+```js
+import { renderQRImage } from 'bbqr';
+
+// get an ArrayBuffer containing the PNG image data
+const imgBuffer = await renderQRImage(splitResult.parts, splitResult.version, {
+ // optional settings - values here are the defaults
+ frameDelay: 250,
+ randomizeOrder: false,
+});
+
+// convert to data URL for display
+const base64String = btoa(String.fromCharCode(...new Uint8Array(imgBuffer)));
+const dataUrl = `data:image/png;base64,${base64String}`;
+
+document.body.innerHTML += ` `;
+```
+
+## Developing
+
+This library is built with [Vite](https://vitejs.dev).
+
+- The `index.html` and `demo.ts` files are for local development.
+- Start the Vite dev server: `npm run dev`.
+- Start the Vitest tests in watch mode: `npm run test:watch`
+- Building for production: `npm run build`
diff --git a/js/demo.ts b/js/demo.ts
new file mode 100644
index 0000000..c6e2616
--- /dev/null
+++ b/js/demo.ts
@@ -0,0 +1,96 @@
+// code only for the demo page, not part of the library
+
+import { detectFileType, renderQRImage, splitQRs } from './src/main';
+
+const resultEl = document.querySelector('#result')!;
+const inputEl = document.querySelector('#text-input')!;
+
+function clearPrevious() {
+ const existingImgs = resultEl.querySelectorAll('img');
+
+ existingImgs.forEach((img) => {
+ // remove references to any old images
+ URL.revokeObjectURL(img.src);
+ });
+
+ resultEl.innerHTML = '';
+}
+
+let busy = false;
+
+async function handleFileOrTextInput(input: File | string) {
+ if (busy) {
+ return;
+ }
+
+ busy = true;
+
+ try {
+ clearPrevious();
+
+ let resultMsg = '';
+
+ const { raw, fileType } = await detectFileType(input);
+
+ resultMsg += `Detected file type: ${fileType} `;
+
+ const { parts, version } = splitQRs(raw, fileType, { encoding: 'Z' });
+
+ const imgBuf = await renderQRImage(parts, version);
+
+ if (parts.length === 1) {
+ resultMsg += `A single QR version ${version} will be needed.`;
+ } else {
+ resultMsg += `Need ${parts.length} QRs of version ${version}.`;
+ }
+
+ resultEl.innerHTML = `${resultMsg}
`;
+
+ const url = URL.createObjectURL(new Blob([imgBuf], { type: 'image/png' }));
+
+ resultEl.innerHTML += ` `;
+ } finally {
+ busy = false;
+ }
+}
+
+document.addEventListener('dragover', (e) => {
+ // prevent browser from opening the file when dropped
+ e.preventDefault();
+});
+
+document.addEventListener('drop', (e) => {
+ e.preventDefault();
+
+ if (!e.dataTransfer) {
+ return;
+ }
+
+ const files: File[] = [];
+
+ for (const item of e.dataTransfer.items) {
+ if (item.kind === 'file') {
+ const file = item.getAsFile();
+
+ if (file) {
+ files.push(file);
+ }
+ }
+ }
+
+ if (files.length > 1) {
+ throw new Error('Only one file at a time, please.');
+ } else if (files.length === 1) {
+ inputEl.value = '';
+ handleFileOrTextInput(files[0]);
+ }
+});
+
+// detect paste in textarea
+inputEl.addEventListener('paste', (e) => {
+ const text = e.clipboardData?.getData('text');
+
+ if (text) {
+ handleFileOrTextInput(text);
+ }
+});
diff --git a/js/index.html b/js/index.html
new file mode 100644
index 0000000..79a8884
--- /dev/null
+++ b/js/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+ BBQr JS
+
+
+
+
+
+ Drop a file into this window or paste contents below.
+
+
+
+
+
+
+
+
diff --git a/js/package-lock.json b/js/package-lock.json
new file mode 100644
index 0000000..1dab204
--- /dev/null
+++ b/js/package-lock.json
@@ -0,0 +1,2607 @@
+{
+ "name": "bbqr",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "bbqr",
+ "version": "0.0.1",
+ "devDependencies": {
+ "@scure/base": "^1.1.5",
+ "@types/pako": "^2.0.3",
+ "@types/qrcode": "^1.5.5",
+ "@types/upng-js": "^2.1.5",
+ "pako": "^2.1.0",
+ "qrcode": "^1.5.3",
+ "typescript": "^5.3.3",
+ "upng-js": "^2.1.0",
+ "vite": "^5.1.3",
+ "vite-plugin-dts": "^3.7.2",
+ "vitest": "^1.3.1"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
+ "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+ "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+ "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+ "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+ "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+ "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+ "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+ "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+ "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+ "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+ "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+ "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+ "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+ "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+ "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+ "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+ "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+ "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+ "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+ "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+ "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+ "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true
+ },
+ "node_modules/@microsoft/api-extractor": {
+ "version": "7.39.0",
+ "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.39.0.tgz",
+ "integrity": "sha512-PuXxzadgnvp+wdeZFPonssRAj/EW4Gm4s75TXzPk09h3wJ8RS3x7typf95B4vwZRrPTQBGopdUl+/vHvlPdAcg==",
+ "dev": true,
+ "dependencies": {
+ "@microsoft/api-extractor-model": "7.28.3",
+ "@microsoft/tsdoc": "0.14.2",
+ "@microsoft/tsdoc-config": "~0.16.1",
+ "@rushstack/node-core-library": "3.62.0",
+ "@rushstack/rig-package": "0.5.1",
+ "@rushstack/ts-command-line": "4.17.1",
+ "colors": "~1.2.1",
+ "lodash": "~4.17.15",
+ "resolve": "~1.22.1",
+ "semver": "~7.5.4",
+ "source-map": "~0.6.1",
+ "typescript": "5.3.3"
+ },
+ "bin": {
+ "api-extractor": "bin/api-extractor"
+ }
+ },
+ "node_modules/@microsoft/api-extractor-model": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.3.tgz",
+ "integrity": "sha512-wT/kB2oDbdZXITyDh2SQLzaWwTOFbV326fP0pUwNW00WeliARs0qjmXBWmGWardEzp2U3/axkO3Lboqun6vrig==",
+ "dev": true,
+ "dependencies": {
+ "@microsoft/tsdoc": "0.14.2",
+ "@microsoft/tsdoc-config": "~0.16.1",
+ "@rushstack/node-core-library": "3.62.0"
+ }
+ },
+ "node_modules/@microsoft/tsdoc": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
+ "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==",
+ "dev": true
+ },
+ "node_modules/@microsoft/tsdoc-config": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz",
+ "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==",
+ "dev": true,
+ "dependencies": {
+ "@microsoft/tsdoc": "0.14.2",
+ "ajv": "~6.12.6",
+ "jju": "~1.4.0",
+ "resolve": "~1.19.0"
+ }
+ },
+ "node_modules/@microsoft/tsdoc-config/node_modules/resolve": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
+ "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.1.0",
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+ "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
+ "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
+ "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
+ "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
+ "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
+ "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
+ "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
+ "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
+ "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
+ "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
+ "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
+ "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
+ "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
+ "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rushstack/node-core-library": {
+ "version": "3.62.0",
+ "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.62.0.tgz",
+ "integrity": "sha512-88aJn2h8UpSvdwuDXBv1/v1heM6GnBf3RjEy6ZPP7UnzHNCqOHA2Ut+ScYUbXcqIdfew9JlTAe3g+cnX9xQ/Aw==",
+ "dev": true,
+ "dependencies": {
+ "colors": "~1.2.1",
+ "fs-extra": "~7.0.1",
+ "import-lazy": "~4.0.0",
+ "jju": "~1.4.0",
+ "resolve": "~1.22.1",
+ "semver": "~7.5.4",
+ "z-schema": "~5.0.2"
+ },
+ "peerDependencies": {
+ "@types/node": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rushstack/rig-package": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.1.tgz",
+ "integrity": "sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==",
+ "dev": true,
+ "dependencies": {
+ "resolve": "~1.22.1",
+ "strip-json-comments": "~3.1.1"
+ }
+ },
+ "node_modules/@rushstack/ts-command-line": {
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.1.tgz",
+ "integrity": "sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==",
+ "dev": true,
+ "dependencies": {
+ "@types/argparse": "1.0.38",
+ "argparse": "~1.0.9",
+ "colors": "~1.2.1",
+ "string-argv": "~0.3.1"
+ }
+ },
+ "node_modules/@scure/base": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz",
+ "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@types/argparse": {
+ "version": "1.0.38",
+ "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
+ "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==",
+ "dev": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.11.19",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
+ "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/pako": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz",
+ "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==",
+ "dev": true
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
+ "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/upng-js": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@types/upng-js/-/upng-js-2.1.5.tgz",
+ "integrity": "sha512-CzXg1lcCcWzrmYmke9BLbBPzb2DpdC1bXuXf0BtK3Bygvsozslei8S1bheDI1QUfZzZpMeQI5fywfnMj4CxocQ==",
+ "dev": true
+ },
+ "node_modules/@vitest/expect": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
+ "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/spy": "1.3.1",
+ "@vitest/utils": "1.3.1",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
+ "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/utils": "1.3.1",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
+ "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
+ "dev": true,
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
+ "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
+ "dev": true,
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
+ "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
+ "dev": true,
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz",
+ "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==",
+ "dev": true,
+ "dependencies": {
+ "@volar/source-map": "1.11.1"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz",
+ "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==",
+ "dev": true,
+ "dependencies": {
+ "muggle-string": "^0.3.1"
+ }
+ },
+ "node_modules/@volar/typescript": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz",
+ "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==",
+ "dev": true,
+ "dependencies": {
+ "@volar/language-core": "1.11.1",
+ "path-browserify": "^1.0.1"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
+ "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.23.9",
+ "@vue/shared": "3.4.19",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
+ "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
+ "dev": true,
+ "dependencies": {
+ "@vue/compiler-core": "3.4.19",
+ "@vue/shared": "3.4.19"
+ }
+ },
+ "node_modules/@vue/language-core": {
+ "version": "1.8.27",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz",
+ "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==",
+ "dev": true,
+ "dependencies": {
+ "@volar/language-core": "~1.11.1",
+ "@volar/source-map": "~1.11.1",
+ "@vue/compiler-dom": "^3.3.0",
+ "@vue/shared": "^3.3.0",
+ "computeds": "^0.0.1",
+ "minimatch": "^9.0.3",
+ "muggle-string": "^0.3.1",
+ "path-browserify": "^1.0.1",
+ "vue-template-compiler": "^2.7.14"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
+ "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chai": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
+ "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
+ "dev": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/colors": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz",
+ "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/computeds": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz",
+ "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/encode-utf8": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+ "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+ "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.19.12",
+ "@esbuild/android-arm": "0.19.12",
+ "@esbuild/android-arm64": "0.19.12",
+ "@esbuild/android-x64": "0.19.12",
+ "@esbuild/darwin-arm64": "0.19.12",
+ "@esbuild/darwin-x64": "0.19.12",
+ "@esbuild/freebsd-arm64": "0.19.12",
+ "@esbuild/freebsd-x64": "0.19.12",
+ "@esbuild/linux-arm": "0.19.12",
+ "@esbuild/linux-arm64": "0.19.12",
+ "@esbuild/linux-ia32": "0.19.12",
+ "@esbuild/linux-loong64": "0.19.12",
+ "@esbuild/linux-mips64el": "0.19.12",
+ "@esbuild/linux-ppc64": "0.19.12",
+ "@esbuild/linux-riscv64": "0.19.12",
+ "@esbuild/linux-s390x": "0.19.12",
+ "@esbuild/linux-x64": "0.19.12",
+ "@esbuild/netbsd-x64": "0.19.12",
+ "@esbuild/openbsd-x64": "0.19.12",
+ "@esbuild/sunos-x64": "0.19.12",
+ "@esbuild/win32-arm64": "0.19.12",
+ "@esbuild/win32-ia32": "0.19.12",
+ "@esbuild/win32-x64": "0.19.12"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/hasown": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+ "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/import-lazy": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
+ "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jju": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
+ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
+ "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/jsonc-parser": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
+ "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
+ "dev": true
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kolorist": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
+ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+ "dev": true
+ },
+ "node_modules/local-pkg": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
+ "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+ "dev": true,
+ "dependencies": {
+ "mlly": "^1.4.2",
+ "pkg-types": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "dev": true
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "dev": true
+ },
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
+ "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+ "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.0.tgz",
+ "integrity": "sha512-YOvg9hfYQmnaB56Yb+KrJE2u0Yzz5zR+sLejEvF4fzwzV1Al6hkf2vyHTwqCRyv0hCi9rVCqVoXpyYevQIRwLQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.3",
+ "pathe": "^1.1.2",
+ "pkg-types": "^1.0.3",
+ "ufo": "^1.3.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/muggle-string": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz",
+ "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "dev": true
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
+ "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+ "dev": true,
+ "dependencies": {
+ "jsonc-parser": "^3.2.0",
+ "mlly": "^1.2.0",
+ "pathe": "^1.1.0"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
+ "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
+ "dev": true,
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "encode-utf8": "^1.0.3",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
+ "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.12.0",
+ "@rollup/rollup-android-arm64": "4.12.0",
+ "@rollup/rollup-darwin-arm64": "4.12.0",
+ "@rollup/rollup-darwin-x64": "4.12.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.12.0",
+ "@rollup/rollup-linux-arm64-musl": "4.12.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-musl": "4.12.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.12.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.12.0",
+ "@rollup/rollup-win32-x64-msvc": "4.12.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true
+ },
+ "node_modules/std-env": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
+ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
+ "dev": true
+ },
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz",
+ "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==",
+ "dev": true,
+ "dependencies": {
+ "js-tokens": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
+ "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==",
+ "dev": true
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz",
+ "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
+ "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==",
+ "dev": true
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/upng-js": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/upng-js/-/upng-js-2.1.0.tgz",
+ "integrity": "sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g==",
+ "dev": true,
+ "dependencies": {
+ "pako": "^1.0.5"
+ }
+ },
+ "node_modules/upng-js/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.11.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
+ "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
+ "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.19.3",
+ "postcss": "^8.4.35",
+ "rollup": "^4.2.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "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
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
+ "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
+ "dev": true,
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-plugin-dts": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-3.7.2.tgz",
+ "integrity": "sha512-kg//1nDA01b8rufJf4TsvYN8LMkdwv0oBYpiQi6nRwpHyue+wTlhrBiqgipdFpMnW1oOYv6ywmzE5B0vg6vSEA==",
+ "dev": true,
+ "dependencies": {
+ "@microsoft/api-extractor": "7.39.0",
+ "@rollup/pluginutils": "^5.1.0",
+ "@vue/language-core": "^1.8.26",
+ "debug": "^4.3.4",
+ "kolorist": "^1.8.0",
+ "vue-tsc": "^1.8.26"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "typescript": "*",
+ "vite": "*"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
+ "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "1.3.1",
+ "@vitest/runner": "1.3.1",
+ "@vitest/snapshot": "1.3.1",
+ "@vitest/spy": "1.3.1",
+ "@vitest/utils": "1.3.1",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.2",
+ "vite": "^5.0.0",
+ "vite-node": "1.3.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.3.1",
+ "@vitest/ui": "1.3.1",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-template-compiler": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
+ "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
+ "dev": true,
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "1.8.27",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz",
+ "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==",
+ "dev": true,
+ "dependencies": {
+ "@volar/typescript": "~1.11.1",
+ "@vue/language-core": "1.8.27",
+ "semver": "^7.5.4"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "dev": true
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
+ "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "dev": true,
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/z-schema": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
+ "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
+ "dev": true,
+ "dependencies": {
+ "lodash.get": "^4.4.2",
+ "lodash.isequal": "^4.5.0",
+ "validator": "^13.7.0"
+ },
+ "bin": {
+ "z-schema": "bin/z-schema"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "commander": "^9.4.1"
+ }
+ }
+ }
+}
diff --git a/js/package.json b/js/package.json
new file mode 100644
index 0000000..c8e3e9e
--- /dev/null
+++ b/js/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "bbqr",
+ "private": false,
+ "version": "1.0.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/coinkite/BBQr.git",
+ "directory": "js"
+ },
+ "type": "module",
+ "files": [
+ "dist"
+ ],
+ "module": "./dist/bbqr.js",
+ "browser": "./dist/bbqr.iife.js",
+ "types": "./dist/main.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/bbqr.js",
+ "types": "./dist/main.d.ts"
+ }
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "devDependencies": {
+ "@scure/base": "^1.1.5",
+ "@types/pako": "^2.0.3",
+ "@types/qrcode": "^1.5.5",
+ "@types/upng-js": "^2.1.5",
+ "pako": "^2.1.0",
+ "qrcode": "^1.5.3",
+ "typescript": "^5.3.3",
+ "upng-js": "^2.1.0",
+ "vite": "^5.1.3",
+ "vite-plugin-dts": "^3.7.2",
+ "vitest": "^1.3.1"
+ }
+}
diff --git a/js/src/consts.ts b/js/src/consts.ts
new file mode 100644
index 0000000..27fffdb
--- /dev/null
+++ b/js/src/consts.ts
@@ -0,0 +1,273 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * Constants and fixed values.
+ */
+
+// Fixed-length header
+export const HEADER_LEN = 8;
+
+export const FILETYPE_NAMES = {
+ P: 'PSBT',
+ T: 'Transaction',
+ J: 'JSON',
+ U: 'Unicode Text',
+ X: 'Executable',
+ B: 'Binary',
+} as const;
+
+export const FILETYPES = new Set(Object.keys(FILETYPE_NAMES));
+
+export const ENCODING_NAMES = {
+ H: 'HEX',
+ Z: 'Zlib compressed',
+ '2': 'Base32',
+} as const;
+
+export const ENCODINGS = new Set(Object.keys(ENCODING_NAMES));
+
+// taken from: https://github.com/mnooner256/pyqrcode/blob/674a77b5eaf850d063f518bd90c243ee34ad6b5d/pyqrcode/tables.py#L84
+export const QR_DATA_CAPACITY = {
+ 1: {
+ L: { 0: 152, 1: 41, 2: 25, 4: 17, 8: 10 },
+ M: { 0: 128, 1: 34, 2: 20, 4: 14, 8: 8 },
+ Q: { 0: 104, 1: 27, 2: 16, 4: 11, 8: 7 },
+ H: { 0: 72, 1: 17, 2: 10, 4: 7, 8: 4 },
+ },
+ 2: {
+ L: { 0: 272, 1: 77, 2: 47, 4: 32, 8: 20 },
+ M: { 0: 224, 1: 63, 2: 38, 4: 26, 8: 16 },
+ Q: { 0: 176, 1: 48, 2: 29, 4: 20, 8: 12 },
+ H: { 0: 128, 1: 34, 2: 20, 4: 14, 8: 8 },
+ },
+ 3: {
+ L: { 0: 440, 1: 127, 2: 77, 4: 53, 8: 32 },
+ M: { 0: 352, 1: 101, 2: 61, 4: 42, 8: 26 },
+ Q: { 0: 272, 1: 77, 2: 47, 4: 32, 8: 20 },
+ H: { 0: 208, 1: 58, 2: 35, 4: 24, 8: 15 },
+ },
+ 4: {
+ L: { 0: 640, 1: 187, 2: 114, 4: 78, 8: 48 },
+ M: { 0: 512, 1: 149, 2: 90, 4: 62, 8: 38 },
+ Q: { 0: 384, 1: 111, 2: 67, 4: 46, 8: 28 },
+ H: { 0: 288, 1: 82, 2: 50, 4: 34, 8: 21 },
+ },
+ 5: {
+ L: { 0: 864, 1: 255, 2: 154, 4: 106, 8: 65 },
+ M: { 0: 688, 1: 202, 2: 122, 4: 84, 8: 52 },
+ Q: { 0: 496, 1: 144, 2: 87, 4: 60, 8: 37 },
+ H: { 0: 368, 1: 106, 2: 64, 4: 44, 8: 27 },
+ },
+ 6: {
+ L: { 0: 1088, 1: 322, 2: 195, 4: 134, 8: 82 },
+ M: { 0: 864, 1: 255, 2: 154, 4: 106, 8: 65 },
+ Q: { 0: 608, 1: 178, 2: 108, 4: 74, 8: 45 },
+ H: { 0: 480, 1: 139, 2: 84, 4: 58, 8: 36 },
+ },
+ 7: {
+ L: { 0: 1248, 1: 370, 2: 224, 4: 154, 8: 95 },
+ M: { 0: 992, 1: 293, 2: 178, 4: 122, 8: 75 },
+ Q: { 0: 704, 1: 207, 2: 125, 4: 86, 8: 53 },
+ H: { 0: 528, 1: 154, 2: 93, 4: 64, 8: 39 },
+ },
+ 8: {
+ L: { 0: 1552, 1: 461, 2: 279, 4: 192, 8: 118 },
+ M: { 0: 1232, 1: 365, 2: 221, 4: 152, 8: 93 },
+ Q: { 0: 880, 1: 259, 2: 157, 4: 108, 8: 66 },
+ H: { 0: 688, 1: 202, 2: 122, 4: 84, 8: 52 },
+ },
+ 9: {
+ L: { 0: 1856, 1: 552, 2: 335, 4: 230, 8: 141 },
+ M: { 0: 1456, 1: 432, 2: 262, 4: 180, 8: 111 },
+ Q: { 0: 1056, 1: 312, 2: 189, 4: 130, 8: 80 },
+ H: { 0: 800, 1: 235, 2: 143, 4: 98, 8: 60 },
+ },
+ 10: {
+ L: { 0: 2192, 1: 652, 2: 395, 4: 271, 8: 167 },
+ M: { 0: 1728, 1: 513, 2: 311, 4: 213, 8: 131 },
+ Q: { 0: 1232, 1: 364, 2: 221, 4: 151, 8: 93 },
+ H: { 0: 976, 1: 288, 2: 174, 4: 119, 8: 74 },
+ },
+ 11: {
+ L: { 0: 2592, 1: 772, 2: 468, 4: 321, 8: 198 },
+ M: { 0: 2032, 1: 604, 2: 366, 4: 251, 8: 155 },
+ Q: { 0: 1440, 1: 427, 2: 259, 4: 177, 8: 109 },
+ H: { 0: 1120, 1: 331, 2: 200, 4: 137, 8: 85 },
+ },
+ 12: {
+ L: { 0: 2960, 1: 883, 2: 535, 4: 367, 8: 226 },
+ M: { 0: 2320, 1: 691, 2: 419, 4: 287, 8: 177 },
+ Q: { 0: 1648, 1: 489, 2: 296, 4: 203, 8: 125 },
+ H: { 0: 1264, 1: 374, 2: 227, 4: 155, 8: 96 },
+ },
+ 13: {
+ L: { 0: 3424, 1: 1022, 2: 619, 4: 425, 8: 262 },
+ M: { 0: 2672, 1: 796, 2: 483, 4: 331, 8: 204 },
+ Q: { 0: 1952, 1: 580, 2: 352, 4: 241, 8: 149 },
+ H: { 0: 1440, 1: 427, 2: 259, 4: 177, 8: 109 },
+ },
+ 14: {
+ L: { 0: 3688, 1: 1101, 2: 667, 4: 458, 8: 282 },
+ M: { 0: 2920, 1: 871, 2: 528, 4: 362, 8: 223 },
+ Q: { 0: 2088, 1: 621, 2: 376, 4: 258, 8: 159 },
+ H: { 0: 1576, 1: 468, 2: 283, 4: 194, 8: 120 },
+ },
+ 15: {
+ L: { 0: 4184, 1: 1250, 2: 758, 4: 520, 8: 320 },
+ M: { 0: 3320, 1: 991, 2: 600, 4: 412, 8: 254 },
+ Q: { 0: 2360, 1: 703, 2: 426, 4: 292, 8: 180 },
+ H: { 0: 1784, 1: 530, 2: 321, 4: 220, 8: 136 },
+ },
+ 16: {
+ L: { 0: 4712, 1: 1408, 2: 854, 4: 586, 8: 361 },
+ M: { 0: 3624, 1: 1082, 2: 656, 4: 450, 8: 277 },
+ Q: { 0: 2600, 1: 775, 2: 470, 4: 322, 8: 198 },
+ H: { 0: 2024, 1: 602, 2: 365, 4: 250, 8: 154 },
+ },
+ 17: {
+ L: { 0: 5176, 1: 1548, 2: 938, 4: 644, 8: 397 },
+ M: { 0: 4056, 1: 1212, 2: 734, 4: 504, 8: 310 },
+ Q: { 0: 2936, 1: 876, 2: 531, 4: 364, 8: 224 },
+ H: { 0: 2264, 1: 674, 2: 408, 4: 280, 8: 173 },
+ },
+ 18: {
+ L: { 0: 5768, 1: 1725, 2: 1046, 4: 718, 8: 442 },
+ M: { 0: 4504, 1: 1346, 2: 816, 4: 560, 8: 345 },
+ Q: { 0: 3176, 1: 948, 2: 574, 4: 394, 8: 243 },
+ H: { 0: 2504, 1: 746, 2: 452, 4: 310, 8: 191 },
+ },
+ 19: {
+ L: { 0: 6360, 1: 1903, 2: 1153, 4: 792, 8: 488 },
+ M: { 0: 5016, 1: 1500, 2: 909, 4: 624, 8: 384 },
+ Q: { 0: 3560, 1: 1063, 2: 644, 4: 442, 8: 272 },
+ H: { 0: 2728, 1: 813, 2: 493, 4: 338, 8: 208 },
+ },
+ 20: {
+ L: { 0: 6888, 1: 2061, 2: 1249, 4: 858, 8: 528 },
+ M: { 0: 5352, 1: 1600, 2: 970, 4: 666, 8: 410 },
+ Q: { 0: 3880, 1: 1159, 2: 702, 4: 482, 8: 297 },
+ H: { 0: 3080, 1: 919, 2: 557, 4: 382, 8: 235 },
+ },
+ 21: {
+ L: { 0: 7456, 1: 2232, 2: 1352, 4: 929, 8: 572 },
+ M: { 0: 5712, 1: 1708, 2: 1035, 4: 711, 8: 438 },
+ Q: { 0: 4096, 1: 1224, 2: 742, 4: 509, 8: 314 },
+ H: { 0: 3248, 1: 969, 2: 587, 4: 403, 8: 248 },
+ },
+ 22: {
+ L: { 0: 8048, 1: 2409, 2: 1460, 4: 1003, 8: 618 },
+ M: { 0: 6256, 1: 1872, 2: 1134, 4: 779, 8: 480 },
+ Q: { 0: 4544, 1: 1358, 2: 823, 4: 565, 8: 348 },
+ H: { 0: 3536, 1: 1056, 2: 640, 4: 439, 8: 270 },
+ },
+ 23: {
+ L: { 0: 8752, 1: 2620, 2: 1588, 4: 1091, 8: 672 },
+ M: { 0: 6880, 1: 2059, 2: 1248, 4: 857, 8: 528 },
+ Q: { 0: 4912, 1: 1468, 2: 890, 4: 611, 8: 376 },
+ H: { 0: 3712, 1: 1108, 2: 672, 4: 461, 8: 284 },
+ },
+ 24: {
+ L: { 0: 9392, 1: 2812, 2: 1704, 4: 1171, 8: 721 },
+ M: { 0: 7312, 1: 2188, 2: 1326, 4: 911, 8: 561 },
+ Q: { 0: 5312, 1: 1588, 2: 963, 4: 661, 8: 407 },
+ H: { 0: 4112, 1: 1228, 2: 744, 4: 511, 8: 315 },
+ },
+ 25: {
+ L: { 0: 10208, 1: 3057, 2: 1853, 4: 1273, 8: 784 },
+ M: { 0: 8000, 1: 2395, 2: 1451, 4: 997, 8: 614 },
+ Q: { 0: 5744, 1: 1718, 2: 1041, 4: 715, 8: 440 },
+ H: { 0: 4304, 1: 1286, 2: 779, 4: 535, 8: 330 },
+ },
+ 26: {
+ L: { 0: 10960, 1: 3283, 2: 1990, 4: 1367, 8: 842 },
+ M: { 0: 8496, 1: 2544, 2: 1542, 4: 1059, 8: 652 },
+ Q: { 0: 6032, 1: 1804, 2: 1094, 4: 751, 8: 462 },
+ H: { 0: 4768, 1: 1425, 2: 864, 4: 593, 8: 365 },
+ },
+ 27: {
+ L: { 0: 11744, 1: 3514, 2: 2132, 4: 1465, 8: 902 },
+ M: { 0: 9024, 1: 2701, 2: 1637, 4: 1125, 8: 692 },
+ Q: { 0: 6464, 1: 1933, 2: 1172, 4: 805, 8: 496 },
+ H: { 0: 5024, 1: 1501, 2: 910, 4: 625, 8: 385 },
+ },
+ 28: {
+ L: { 0: 12248, 1: 3669, 2: 2223, 4: 1528, 8: 940 },
+ M: { 0: 9544, 1: 2857, 2: 1732, 4: 1190, 8: 732 },
+ Q: { 0: 6968, 1: 2085, 2: 1263, 4: 868, 8: 534 },
+ H: { 0: 5288, 1: 1581, 2: 958, 4: 658, 8: 405 },
+ },
+ 29: {
+ L: { 0: 13048, 1: 3909, 2: 2369, 4: 1628, 8: 1002 },
+ M: { 0: 10136, 1: 3035, 2: 1839, 4: 1264, 8: 778 },
+ Q: { 0: 7288, 1: 2181, 2: 1322, 4: 908, 8: 559 },
+ H: { 0: 5608, 1: 1677, 2: 1016, 4: 698, 8: 430 },
+ },
+ 30: {
+ L: { 0: 13880, 1: 4158, 2: 2520, 4: 1732, 8: 1066 },
+ M: { 0: 10984, 1: 3289, 2: 1994, 4: 1370, 8: 843 },
+ Q: { 0: 7880, 1: 2358, 2: 1429, 4: 982, 8: 604 },
+ H: { 0: 5960, 1: 1782, 2: 1080, 4: 742, 8: 457 },
+ },
+ 31: {
+ L: { 0: 14744, 1: 4417, 2: 2677, 4: 1840, 8: 1132 },
+ M: { 0: 11640, 1: 3486, 2: 2113, 4: 1452, 8: 894 },
+ Q: { 0: 8264, 1: 2473, 2: 1499, 4: 1030, 8: 634 },
+ H: { 0: 6344, 1: 1897, 2: 1150, 4: 790, 8: 486 },
+ },
+ 32: {
+ L: { 0: 15640, 1: 4686, 2: 2840, 4: 1952, 8: 1201 },
+ M: { 0: 12328, 1: 3693, 2: 2238, 4: 1538, 8: 947 },
+ Q: { 0: 8920, 1: 2670, 2: 1618, 4: 1112, 8: 684 },
+ H: { 0: 6760, 1: 2022, 2: 1226, 4: 842, 8: 518 },
+ },
+ 33: {
+ L: { 0: 16568, 1: 4965, 2: 3009, 4: 2068, 8: 1273 },
+ M: { 0: 13048, 1: 3909, 2: 2369, 4: 1628, 8: 1002 },
+ Q: { 0: 9368, 1: 2805, 2: 1700, 4: 1168, 8: 719 },
+ H: { 0: 7208, 1: 2157, 2: 1307, 4: 898, 8: 553 },
+ },
+ 34: {
+ L: { 0: 17528, 1: 5253, 2: 3183, 4: 2188, 8: 1347 },
+ M: { 0: 13800, 1: 4134, 2: 2506, 4: 1722, 8: 1060 },
+ Q: { 0: 9848, 1: 2949, 2: 1787, 4: 1228, 8: 756 },
+ H: { 0: 7688, 1: 2301, 2: 1394, 4: 958, 8: 590 },
+ },
+ 35: {
+ L: { 0: 18448, 1: 5529, 2: 3351, 4: 2303, 8: 1417 },
+ M: { 0: 14496, 1: 4343, 2: 2632, 4: 1809, 8: 1113 },
+ Q: { 0: 10288, 1: 3081, 2: 1867, 4: 1283, 8: 790 },
+ H: { 0: 7888, 1: 2361, 2: 1431, 4: 983, 8: 605 },
+ },
+ 36: {
+ L: { 0: 19472, 1: 5836, 2: 3537, 4: 2431, 8: 1496 },
+ M: { 0: 15312, 1: 4588, 2: 2780, 4: 1911, 8: 1176 },
+ Q: { 0: 10832, 1: 3244, 2: 1966, 4: 1351, 8: 832 },
+ H: { 0: 8432, 1: 2524, 2: 1530, 4: 1051, 8: 647 },
+ },
+ 37: {
+ L: { 0: 20528, 1: 6153, 2: 3729, 4: 2563, 8: 1577 },
+ M: { 0: 15936, 1: 4775, 2: 2894, 4: 1989, 8: 1224 },
+ Q: { 0: 11408, 1: 3417, 2: 2071, 4: 1423, 8: 876 },
+ H: { 0: 8768, 1: 2625, 2: 1591, 4: 1093, 8: 673 },
+ },
+ 38: {
+ L: { 0: 21616, 1: 6479, 2: 3927, 4: 2699, 8: 1661 },
+ M: { 0: 16816, 1: 5039, 2: 3054, 4: 2099, 8: 1292 },
+ Q: { 0: 12016, 1: 3599, 2: 2181, 4: 1499, 8: 923 },
+ H: { 0: 9136, 1: 2735, 2: 1658, 4: 1139, 8: 701 },
+ },
+ 39: {
+ L: { 0: 22496, 1: 6743, 2: 4087, 4: 2809, 8: 1729 },
+ M: { 0: 17728, 1: 5313, 2: 3220, 4: 2213, 8: 1362 },
+ Q: { 0: 12656, 1: 3791, 2: 2298, 4: 1579, 8: 972 },
+ H: { 0: 9776, 1: 2927, 2: 1774, 4: 1219, 8: 750 },
+ },
+ 40: {
+ L: { 0: 23648, 1: 7089, 2: 4296, 4: 2953, 8: 1817 },
+ M: { 0: 18672, 1: 5596, 2: 3391, 4: 2331, 8: 1435 },
+ Q: { 0: 13328, 1: 3993, 2: 2420, 4: 1663, 8: 1024 },
+ H: { 0: 10208, 1: 3057, 2: 1852, 4: 1273, 8: 784 },
+ },
+} as const;
+
+// EOF
diff --git a/js/src/image.ts b/js/src/image.ts
new file mode 100644
index 0000000..a1cee0c
--- /dev/null
+++ b/js/src/image.ts
@@ -0,0 +1,107 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * QR code image rendering.
+ */
+
+import QRCode from 'qrcode';
+import UPNG from 'upng-js';
+import { ImageOptions, Version } from './types';
+import { shuffled } from './utils';
+
+/**
+ * Generates QR code images from the given parts and renders them as (animated) PNG.
+ *
+ * @param parts The string parts to encode as QR codes.
+ * @param version The QR code version to use.
+ * @param options An optional ImageOptions object.
+ * @param options.frameDelay The delay between frames in the animated PNG in milliseconds. Defaults to 250.
+ * @param options.randomizeOrder Whether to randomize the order of the parts. Defaults to false.
+ * @returns A Promise that resolves to an ArrayBuffer containing the image data;
+ */
+export async function renderQRImage(
+ parts: string[],
+ version: Version,
+ options: ImageOptions = {}
+): Promise {
+ if (typeof window === 'undefined') {
+ throw new Error('makeImage is only available in a web browser environment.');
+ }
+
+ const frameDelay = options.frameDelay ?? 250;
+
+ if (options.randomizeOrder) {
+ parts = shuffled(parts);
+ }
+
+ const frames: ArrayBuffer[] = [];
+
+ let width = 0;
+ let height = 0;
+
+ // additional space for progress bar, if more than one part
+ const progressAreaHeight = parts.length > 1 ? 20 : 0;
+
+ // quiet zone
+ const margin = 4;
+
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+
+ const dataURL = await QRCode.toDataURL([{ data: part, mode: 'alphanumeric' }], {
+ errorCorrectionLevel: 'L',
+ version,
+ margin,
+ });
+
+ // Create an image and draw it onto the canvas to get its dimensions
+ const img = new Image();
+ img.src = dataURL;
+ await img.decode();
+
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (!ctx) {
+ throw new Error('Could not get 2d context for canvas element.');
+ }
+
+ canvas.width = img.width;
+ canvas.height = img.height + progressAreaHeight;
+
+ ctx.drawImage(img, 0, 0);
+
+ if (progressAreaHeight > 0) {
+ // fill area between QR and progress bar
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, img.height, canvas.width, progressAreaHeight);
+
+ const progressBarHeight = progressAreaHeight / 4;
+ const progressBarY = img.height + progressAreaHeight / 2; // Position the progress bar below the QR code
+ const segmentWidth = canvas.width / parts.length;
+
+ ctx.fillStyle = '#ccc';
+ ctx.fillRect(0, progressBarY, canvas.width, progressBarHeight);
+
+ ctx.fillStyle = '#000';
+
+ ctx.fillRect(segmentWidth * i, progressBarY, segmentWidth, progressBarHeight);
+ }
+
+ if (i === 0) {
+ width = canvas.width;
+ height = canvas.height;
+ } else if (canvas.width !== width || canvas.height !== height) {
+ throw new Error('QR codes must all be the same size');
+ }
+
+ const imgData = ctx.getImageData(0, 0, width, height).data;
+ frames.push(imgData.buffer);
+ }
+
+ const delays = parts.map(() => frameDelay);
+
+ return UPNG.encode(frames, width, height, 0, delays);
+}
+
+// EOF
diff --git a/js/src/join.ts b/js/src/join.ts
new file mode 100644
index 0000000..7efe609
--- /dev/null
+++ b/js/src/join.ts
@@ -0,0 +1,79 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * QR code decoding/joining.
+ */
+
+import { ENCODINGS, FILETYPES } from './consts';
+import { Encoding, FileType, JoinResult } from './types';
+import { decodeData } from './utils';
+
+/**
+ * Decodes and joins QR code parts back to binary data.
+ *
+ * @param parts Array of QR code parts
+ * @returns Object containing the file type, encoding, and raw binary data.
+ */
+export function joinQRs(parts: string[]): JoinResult {
+ const headers = new Set(parts.map((p) => p.slice(0, 6)));
+
+ if (headers.size !== 1) {
+ throw new Error('conflicting/variable filetype/encodings/sizes');
+ }
+
+ const header = [...headers][0];
+
+ if (header.slice(0, 2) !== 'B$') {
+ throw new Error('fixed header not found, expected B$');
+ }
+
+ if (!ENCODINGS.has(header[2])) {
+ throw new Error(`bad encoding: ${header[2]}`);
+ }
+ if (!FILETYPES.has(header[3])) {
+ throw new Error(`bad file type: ${header[3]}`);
+ }
+
+ const encoding = header[2] as Encoding;
+ const fileType = header[3] as FileType;
+
+ const numParts = parseInt(header.slice(4, 6), 36);
+
+ if (numParts < 1) {
+ throw new Error('zero parts?');
+ }
+
+ const data = new Map();
+
+ for (const p of parts) {
+ const idx = parseInt(p.slice(6, 8), 36);
+
+ if (idx >= numParts) {
+ throw new Error(`got part ${idx} but only expecting ${numParts}`);
+ }
+
+ if (data.has(idx) && data.get(idx) !== p.slice(8)) {
+ throw new Error(`Duplicate part 0x${idx.toString(16)} has wrong content`);
+ }
+
+ data.set(idx, p.slice(8));
+ }
+
+ const orderedParts = [];
+
+ for (let i = 0; i < numParts; i++) {
+ const p = data.get(i);
+
+ if (!p) {
+ throw new Error(`Part ${i} is missing`);
+ }
+
+ orderedParts.push(p);
+ }
+
+ const raw = decodeData(orderedParts, encoding);
+
+ return { fileType, encoding, raw };
+}
+
+// EOF
diff --git a/js/src/main.ts b/js/src/main.ts
new file mode 100644
index 0000000..8f7c187
--- /dev/null
+++ b/js/src/main.ts
@@ -0,0 +1,13 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * Main entry point for the library.
+ */
+
+import { renderQRImage } from './image.ts';
+import { joinQRs } from './join.ts';
+import { detectFileType, splitQRs } from './split.ts';
+
+export { detectFileType, joinQRs, renderQRImage, splitQRs };
+
+// EOF
diff --git a/js/src/split.ts b/js/src/split.ts
new file mode 100644
index 0000000..1b8c208
--- /dev/null
+++ b/js/src/split.ts
@@ -0,0 +1,206 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * Splitting of data and encoding as BBQr QR codes.
+ */
+
+import { FILETYPES, HEADER_LEN } from './consts';
+import { FileType, SplitOptions, SplitResult, Version } from './types';
+import {
+ base64ToBytes,
+ encodeData,
+ fileToBytes,
+ hexToBytes,
+ intToBase36,
+ looksLikePsbt,
+ validateSplitOptions,
+ versionToChars,
+} from './utils';
+
+function numQRNeeded(version: Version, length: number, splitMod: number) {
+ const baseCap = versionToChars(version) - HEADER_LEN;
+
+ // adjust capacity to be a multiple of splitMod
+ const adjustedCap = baseCap - (baseCap % splitMod);
+
+ const estimatedCount = Math.ceil(length / adjustedCap);
+
+ if (estimatedCount === 1) {
+ // if it fits in one QR, we're done
+ return { count: 1, perEach: length };
+ }
+
+ // the total capacity of our estimated count
+ // all but the last QR need to use adjusted capacity to ensure proper split
+ const estimatedCap = (estimatedCount - 1) * adjustedCap + baseCap;
+
+ return {
+ count: estimatedCap >= length ? estimatedCount : estimatedCount + 1,
+ perEach: adjustedCap,
+ };
+}
+
+function findBestVersion(length: number, splitMod: number, opts: Required) {
+ const options: { version: Version; count: number; perEach: number }[] = [];
+
+ for (let version = opts.minVersion; version <= opts.maxVersion; version++) {
+ const { count, perEach } = numQRNeeded(version, length, splitMod);
+
+ if (opts.minSplit <= count && count <= opts.maxSplit) {
+ options.push({ version, count, perEach });
+ }
+ }
+
+ if (!options.length) {
+ throw new Error('Cannot make it fit');
+ }
+
+ // pick smallest number of QR, lowest version
+ options.sort((a, b) => a.count - b.count || a.version - b.version);
+
+ return options[0];
+}
+
+/**
+ * Converts the input bytes into a series of QR codes, ensuring that the most efficient QR code
+ * version is used.
+ *
+ * NOTE: When the 'Z' (Zlib) encoding is selected, it is possible that the actual used encoding
+ * will be '2' (Base32) in case Zlib compression does not reduce the size of the output.
+ *
+ * @param raw The input bytes to split and encode.
+ * @param fileType The file type to use. Refer to BBQr spec.
+ *
+ * @param opts An optional SplitOptions object.
+ * @param opts.encoding The Encoding to use. Defaults to 'Z'.
+ * @param opts.minSplit The minimum number of QR codes to use. Defaults to 1.
+ * @param opts.maxSplit The maximum number of QR codes to use. Defaults to 1295.
+ * @param opts.minVersion The minimum QR code version to use. Defaults to 5.
+ * @param opts.maxVersion The maximum QR code version to use. Defaults to 40.
+ *
+ * @returns An object containing the version of the QR codes, their string parts, and the actual encoding used.
+ */
+export function splitQRs(
+ raw: Uint8Array,
+ fileType: FileType,
+ opts: SplitOptions = {}
+): SplitResult {
+ if (!FILETYPES.has(fileType)) {
+ throw new Error(`Invalid value for fileType: ${fileType}`);
+ }
+
+ const validatedOpts = validateSplitOptions(opts);
+
+ const { encoding: actualEncoding, encoded, splitMod } = encodeData(raw, validatedOpts.encoding);
+
+ const { version, count, perEach } = findBestVersion(encoded.length, splitMod, validatedOpts);
+
+ const parts: string[] = [];
+
+ for (let n = 0, offset = 0; offset < encoded.length; n++, offset += perEach) {
+ parts.push(
+ `B$${actualEncoding}${fileType}` +
+ intToBase36(count) +
+ intToBase36(n) +
+ encoded.slice(offset, offset + perEach)
+ );
+ }
+
+ return { version, parts, encoding: actualEncoding };
+}
+
+/**
+ * Takes a given given input (Uint8Array, File, or string) and detects its FileType.
+ * PSBTs and Bitcoin transactions are supported in raw binary, Base64, or hex format.
+ *
+ * @param input - The input to detect the FileType of.
+ * @returns A Promise that resolves to an object containing the FileType and raw data.
+ */
+export async function detectFileType(
+ input: File | Uint8Array | string
+): Promise<{ fileType: FileType; raw: Uint8Array }> {
+ // keep references to both raw and decoded versions of the input to run checks on
+ let raw: Uint8Array | undefined = undefined;
+ let decoded: string | undefined = undefined;
+
+ if (input instanceof File) {
+ // convert a File to Uint8Array so we have access to the raw bytes
+ input = await fileToBytes(input);
+ }
+
+ if (input instanceof Uint8Array) {
+ // we got binary, see if we recognize it
+ raw = input;
+
+ if (looksLikePsbt(input)) {
+ console.debug('Detected type "P" from binary input');
+ return { fileType: 'P', raw };
+ }
+
+ if (raw[0] === 0x01 || raw[0] === 0x02) {
+ console.debug('Detected type "T" from binary input');
+ return { fileType: 'T', raw };
+ }
+
+ // otherwise, try to decode as text (could be contents of a file)
+ try {
+ decoded = new TextDecoder('utf-8', { fatal: true }).decode(raw);
+ } catch (err) {
+ // not text, so fall back to generic binary
+
+ console.debug('Detected type "B" from binary input');
+ return { fileType: 'B', raw };
+ }
+ } else if (typeof input === 'string') {
+ decoded = input;
+ } else {
+ throw new Error('Invalid input - must be a File, Uint8Array or string');
+ }
+
+ const trimmed = decoded.trim();
+
+ if (/^70736274ff[0-9A-Fa-f]+$/.test(trimmed)) {
+ // PSBT in hex format
+ console.debug('Detected type "P" from hex input');
+
+ return { fileType: 'P', raw: hexToBytes(trimmed) };
+ }
+
+ if (/^0[1,2]000000[0-9A-Fa-f]+$/.test(trimmed)) {
+ // Transaction in hex format
+ console.debug('Detected type "T" from hex input');
+
+ return { fileType: 'T', raw: hexToBytes(trimmed) };
+ }
+
+ if (/^[A-Za-z0-9+/=]+$/.test(trimmed)) {
+ // looks like base64 - could be PSBT or transaction
+ const bytes = base64ToBytes(decoded);
+
+ if (looksLikePsbt(bytes)) {
+ console.debug('Detected type "P" from base64 input');
+ return { fileType: 'P', raw: bytes };
+ }
+
+ if (bytes[0] === 0x01 || bytes[0] === 0x02) {
+ console.debug('Detected type "T" from base64 input');
+ return { fileType: 'T', raw: bytes };
+ }
+ }
+
+ // ensure we have raw bytes for the next step
+ raw = raw ?? new TextEncoder().encode(decoded);
+
+ try {
+ JSON.parse(decoded);
+ console.debug('Detected type "J"');
+ return { fileType: 'J', raw };
+ } catch (err) {
+ // not JSON - fall back to generic Unicode
+
+ console.debug('Detected type "U"');
+ return { fileType: 'U', raw };
+ }
+}
+
+// EOF
diff --git a/js/src/types.ts b/js/src/types.ts
new file mode 100644
index 0000000..83f5b08
--- /dev/null
+++ b/js/src/types.ts
@@ -0,0 +1,38 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * Types
+ */
+
+import { ENCODING_NAMES, FILETYPE_NAMES, QR_DATA_CAPACITY } from './consts';
+
+export type FileType = keyof typeof FILETYPE_NAMES;
+export type Encoding = keyof typeof ENCODING_NAMES;
+export type Version = keyof typeof QR_DATA_CAPACITY;
+
+export type SplitOptions = {
+ encoding?: Encoding;
+ minSplit?: number;
+ maxSplit?: number;
+ minVersion?: Version;
+ maxVersion?: Version;
+};
+
+export type SplitResult = {
+ version: Version;
+ parts: string[];
+ encoding: Encoding;
+};
+
+export type JoinResult = {
+ fileType: FileType;
+ encoding: Encoding;
+ raw: Uint8Array;
+};
+
+export type ImageOptions = {
+ frameDelay?: number;
+ randomizeOrder?: boolean;
+};
+
+// EOF
diff --git a/js/src/utils.ts b/js/src/utils.ts
new file mode 100644
index 0000000..2e67e14
--- /dev/null
+++ b/js/src/utils.ts
@@ -0,0 +1,221 @@
+/**
+ * (c) Copyright 2024 by Coinkite Inc. This file is in the public domain.
+ *
+ * Helper/utility functions.
+ */
+
+import { base32 } from '@scure/base';
+import pako from 'pako';
+import { QR_DATA_CAPACITY } from './consts';
+import type { Encoding, SplitOptions, Version } from './types';
+
+export function hexToBytes(hex: string) {
+ // convert a hex string to a Uint8Array
+
+ const match = hex.match(/.{1,2}/g) ?? [];
+
+ return Uint8Array.from(match.map((byte) => parseInt(byte, 16)));
+}
+
+export function base64ToBytes(base64: string) {
+ // convert a base64 string to a Uint8Array
+
+ const binaryString = atob(base64);
+ const len = binaryString.length;
+ const bytes = new Uint8Array(len);
+
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+
+ return bytes;
+}
+
+export function intToBase36(n: number) {
+ // convert an integer 0-1295 to two digits of base 36 - 00-ZZ
+
+ if (n < 0 || n > 1295 || !Number.isInteger(n)) {
+ throw new Error('Out of range');
+ }
+
+ return n.toString(36).toUpperCase().padStart(2, '0');
+}
+
+export async function fileToBytes(file: File) {
+ // read a File's contents and return as a Uint8Array
+
+ const reader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ reader.onload = (e) => {
+ const result = e.target?.result;
+
+ if (result instanceof ArrayBuffer) {
+ resolve(new Uint8Array(result));
+ } else {
+ reject(new Error('FileReader result is not an ArrayBuffer'));
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+}
+
+function joinByteParts(parts: Uint8Array[]) {
+ // perf-optimized way to join Uint8Arrays
+
+ const length = parts.reduce((acc, bytes) => acc + bytes.length, 0);
+
+ const rv = new Uint8Array(length);
+
+ let offset = 0;
+ for (const bytes of parts) {
+ rv.set(bytes, offset);
+ offset += bytes.length;
+ }
+
+ return rv;
+}
+
+export function isValidVersion(v: number): v is Version {
+ // act as a TS type guard but also a runtime check
+
+ return v in QR_DATA_CAPACITY;
+}
+
+export function isValidSplit(s: number) {
+ return s >= 1 && s <= 1295;
+}
+
+export function validateSplitOptions(opts: SplitOptions) {
+ // ensure all split options are valid, filling in defaults as needed
+
+ const allOpts = {
+ minVersion: opts.minVersion ?? 5,
+ maxVersion: opts.maxVersion ?? 40,
+ minSplit: opts.minSplit ?? 1,
+ maxSplit: opts.maxSplit ?? 1295,
+ encoding: opts.encoding ?? 'Z',
+ } as const;
+
+ if (
+ allOpts.minVersion > allOpts.maxVersion ||
+ !isValidVersion(allOpts.minVersion) ||
+ !isValidVersion(allOpts.maxVersion)
+ ) {
+ throw new Error('min/max version out of range');
+ }
+
+ if (
+ !isValidSplit(allOpts.minSplit) ||
+ !isValidSplit(allOpts.maxSplit) ||
+ allOpts.minSplit > allOpts.maxSplit
+ ) {
+ throw new Error('min/max split out of range');
+ }
+
+ return allOpts;
+}
+
+export function looksLikePsbt(data: Uint8Array) {
+ try {
+ // 'psbt' + 0xff
+ return new Uint8Array([0x70, 0x73, 0x62, 0x74, 0xff]).every((b, i) => b === data[i]);
+ } catch (err) {
+ return false;
+ }
+}
+
+export function shuffled(arr: T[]): T[] {
+ // modern Fisher-Yates shuffle (https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm)
+
+ // create a copy so we don't mutate the original
+ arr = [...arr];
+
+ for (let i = arr.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ const temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+
+ return arr;
+}
+
+export function versionToChars(v: Version) {
+ // return number of **chars** that fit into indicated version QR
+ // - assumes L for ECC
+ // - assumes alnum encoding
+
+ if (!isValidVersion(v)) {
+ throw new Error('Invalid version');
+ }
+
+ const ecc = 'L';
+ const encoding = 2; // alnum
+
+ return QR_DATA_CAPACITY[v][ecc][encoding];
+}
+
+export function encodeData(raw: Uint8Array, encoding?: Encoding) {
+ // return new encoding (if we upgraded) and the
+ // characters after encoding (a string)
+ // - default is Zlib or if compression doesn't help, base32
+ // - returned data can be split, but must be done modX where X provided
+
+ encoding = encoding ?? 'Z';
+
+ if (encoding === 'H') {
+ return {
+ encoding,
+ encoded: raw
+ .reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), '')
+ .toUpperCase(),
+ splitMod: 2,
+ };
+ }
+
+ if (encoding === 'Z') {
+ // trial compression, but skip if it embiggens the data
+
+ const compressed = pako.deflate(raw, { windowBits: -10 });
+
+ if (compressed.length >= raw.length) {
+ encoding = '2';
+ } else {
+ encoding = 'Z';
+ raw = compressed;
+ }
+ }
+
+ return {
+ encoding,
+ // base32 without padding
+ encoded: base32.encode(raw).replace(/=*$/, ''),
+ splitMod: 8,
+ };
+}
+
+export function decodeData(parts: string[], encoding: Encoding) {
+ // decode the parts back into a Uint8Array
+
+ if (encoding === 'H') {
+ return joinByteParts(parts.map((p) => hexToBytes(p)));
+ }
+
+ const bytes = joinByteParts(
+ parts.map((p) => {
+ const padding = (8 - (p.length % 8)) % 8;
+
+ return base32.decode(p + '='.repeat(padding));
+ })
+ );
+
+ if (encoding === 'Z') {
+ return pako.inflate(bytes, { windowBits: -10 });
+ }
+
+ return bytes;
+}
+
+// EOF
diff --git a/js/tests/join.test.ts b/js/tests/join.test.ts
new file mode 100644
index 0000000..3c2983c
--- /dev/null
+++ b/js/tests/join.test.ts
@@ -0,0 +1,21 @@
+import fs from 'fs';
+import path from 'path';
+import { expect, test } from 'vitest';
+import { joinQRs } from '../src/join';
+
+test('Test real-scan.txt', async () => {
+ const lines = (
+ await fs.promises.readFile(path.join(__dirname, '../../test_data/real-scan.txt'), 'utf-8')
+ )
+ .split('\n')
+ .filter((l) => l.trim() !== '');
+
+ const { fileType, raw } = joinQRs(lines);
+
+ expect(fileType).toBe('U');
+
+ const decoded = new TextDecoder().decode(raw);
+
+ expect(decoded).toContain('Zlib compressed');
+ expect(decoded).toContain('PSBT');
+});
diff --git a/js/tests/loopback.test.ts b/js/tests/loopback.test.ts
new file mode 100644
index 0000000..6f1e4bb
--- /dev/null
+++ b/js/tests/loopback.test.ts
@@ -0,0 +1,175 @@
+import QRCode from 'qrcode';
+import { expect, test } from 'vitest';
+import { HEADER_LEN, QR_DATA_CAPACITY } from '../src/consts';
+import { joinQRs } from '../src/join';
+import { splitQRs } from '../src/split';
+import { Encoding, FileType, Version } from '../src/types';
+import { shuffled } from '../src/utils';
+
+// helper to create cartesian product of arrays akin to @pytest.mark.parametrize over multiple parameters
+// https://stackoverflow.com/a/43053803
+function cartesian(...a: any[][]) {
+ return a.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat())));
+}
+
+// for validating QRs
+function producesValidQRs(parts: string[], version: number) {
+ try {
+ for (const p of parts) {
+ QRCode.create([{ data: p, mode: 'alphanumeric' }], {
+ version,
+ errorCorrectionLevel: 'L',
+ });
+ }
+
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
+const LOOPBACK_CASES: [Encoding | undefined, number, Version, boolean, FileType][] = cartesian(
+ [undefined, 'H', '2', 'Z'], // encoding
+ [10, 100, 2000, 10_000, 50_000], // size
+ [11, 29, 40], // maxVersion
+ [true, false], // lowEntropy
+ ['P', 'T'] // fileType
+);
+
+test('Loopback', () => {
+ for (const [i, tc] of LOOPBACK_CASES.entries()) {
+ const [encoding, size, maxVersion, lowEntropy, fileType] = tc;
+
+ const data = new Uint8Array(size);
+ if (lowEntropy) {
+ data.fill(0x41);
+ } else {
+ crypto.getRandomValues(data);
+ }
+
+ const { version, parts } = splitQRs(data, fileType, { encoding, maxVersion });
+
+ expect(version).toBeLessThanOrEqual(maxVersion);
+
+ if (encoding && encoding !== parts[0][2]) {
+ // encoding can only change from 'Z' to '2' if Zlib doesn't provide a smaller result
+ expect(encoding).toBe('Z');
+ expect(parts[0][2]).toBe('2');
+ }
+
+ const decoded = joinQRs(parts);
+ expect(decoded.fileType).toBe(fileType);
+ expect(decoded.raw).toEqual(data);
+
+ const randomized = shuffled(parts);
+ const decoded2 = joinQRs(randomized);
+ expect(decoded2.fileType).toBe(fileType);
+ expect(decoded2.raw).toEqual(data);
+
+ // try to construct a few QRs. too slow to do for every case
+ if (i % 50 === 0) {
+ expect(producesValidQRs(parts, version)).toBe(true);
+ }
+ }
+});
+
+test('Minimum split', () => {
+ const data = new Uint8Array(10_000);
+ crypto.getRandomValues(data);
+
+ for (let i = 2; i < 10; i++) {
+ const { parts, version } = splitQRs(data, 'T', { encoding: '2', minSplit: i });
+
+ expect(parts.length).toBeGreaterThanOrEqual(i);
+
+ const decoded = joinQRs(parts);
+
+ expect(decoded.fileType).toBe('T');
+ expect(decoded.raw).toEqual(data);
+
+ expect(producesValidQRs(parts, version)).toBe(true);
+ }
+});
+
+const V27_EDGE_CASES: [Encoding, number, boolean][] = cartesian(
+ ['H', '2', 'Z'], // encoding
+ Array.from({ length: 20 }, (_, i) => i + 1060), // size
+ [true, false] // lowEntropy
+);
+
+test('Version 27 edge cases', () => {
+ for (const c of V27_EDGE_CASES) {
+ const [encoding, size, lowEntropy] = c;
+
+ const needVersion = 27;
+
+ const data = new Uint8Array(size);
+ if (lowEntropy) {
+ data.fill(0x41);
+ } else {
+ crypto.getRandomValues(data);
+ }
+
+ const { parts, version: actualVersion } = splitQRs(data, 'T', {
+ encoding,
+ maxSplit: 2,
+ minVersion: needVersion,
+ maxVersion: needVersion,
+ });
+
+ expect(actualVersion).toBe(needVersion);
+
+ if (encoding === 'H') {
+ expect(parts.length).toBe(size <= 1062 ? 1 : 2);
+ } else if (encoding === 'Z') {
+ expect(parts.length).toBe(1);
+ if (lowEntropy) {
+ expect(parts[0].length).toBeLessThan(100);
+ }
+ } else if (encoding === '2') {
+ expect(parts.length).toBe(1);
+ }
+
+ const decoded = joinQRs(parts);
+
+ expect(decoded.fileType).toBe('T');
+ expect(decoded.raw).toEqual(data);
+ }
+});
+
+test.each(['H', '2'] as const)(`Test max size for encoding %s`, (encoding) => {
+ const capacity = QR_DATA_CAPACITY[40]['L'][2] - HEADER_LEN;
+
+ let pktSize: number;
+
+ if (encoding === 'H') {
+ pktSize = Math.floor(capacity / 2);
+ } else if (encoding === '2') {
+ pktSize = Math.floor((capacity * 5) / 8);
+ } else {
+ throw new Error('unreachable');
+ }
+
+ const nparts = 1295;
+
+ const data = new Uint8Array(pktSize * nparts);
+
+ // cannot use crypto.getRandomValues(data) - exceeds max size
+
+ for (let i = 0; i < data.length; i++) {
+ data[i] = Math.floor(Math.random() * 256);
+ }
+
+ const { parts, version } = splitQRs(data, 'T', { encoding, minVersion: 40 });
+
+ expect(version).toBe(40);
+
+ expect(parts.length).toBe(nparts);
+
+ const decoded = joinQRs(parts);
+ expect(decoded.raw).toEqual(data);
+
+ // test a random part
+ const idx = Math.floor(Math.random() * parts.length);
+ expect(producesValidQRs([parts[idx]], version)).toBe(true);
+});
diff --git a/js/tests/utils.test.ts b/js/tests/utils.test.ts
new file mode 100644
index 0000000..b3e6696
--- /dev/null
+++ b/js/tests/utils.test.ts
@@ -0,0 +1,71 @@
+import fs from 'fs';
+import path from 'path';
+import { expect, test } from 'vitest';
+import { base64ToBytes, decodeData, encodeData, hexToBytes, intToBase36 } from '../src/utils';
+
+test('Decode hex string to bytes', () => {
+ const hex = '48656c6c6f20776f726c64'; // "Hello world"
+ const expected = new Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]);
+
+ expect(hexToBytes(hex)).toEqual(expected);
+});
+
+test('Decode base64 to binary', () => {
+ const b64 = 'SGVsbG8gd29ybGQ='; // "Hello world"
+ const expected = new Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]);
+
+ expect(base64ToBytes(b64)).toEqual(expected);
+});
+
+test('Encode integers 0 thru 1295 to base32', () => {
+ for (let i = 0; i <= 1295; i++) {
+ const s = intToBase36(i);
+
+ expect(s).toHaveLength(2);
+ expect(s.toUpperCase()).toBe(s);
+
+ expect(parseInt(s, 36)).toBe(i);
+ }
+
+ expect(() => intToBase36(-1)).toThrowError();
+ expect(() => intToBase36(1296)).toThrowError();
+});
+
+const dataFiles = [
+ '1in1000out.psbt',
+ '1in100out.psbt',
+ '1in10out.psbt',
+ '1in20out.psbt',
+ '1in2out.psbt',
+ 'devils-txn.txn',
+ 'finalized-by-ckcc.txn',
+ 'last.txn',
+ 'nfc-result.txn',
+ 'signed.txn',
+];
+
+test.each(dataFiles)('Encode, decode, measure Zlib compression: %s', async (file) => {
+ // need the conversion to Uint8Array because node's Buffer won't be equal
+ const raw = new Uint8Array(
+ await fs.promises.readFile(path.join(__dirname, '../../test_data', file))
+ );
+
+ const { encoded, encoding } = encodeData(raw, 'Z');
+
+ expect(encoding).toBe('Z');
+
+ const decoded = decodeData([encoded], 'Z');
+
+ expect(decoded).toEqual(raw);
+
+ // decode with base32 only and see how much smaller it is
+ const decodedCompressed = decodeData([encoded], '2');
+
+ expect(decodedCompressed.length).toBeLessThan(raw.length);
+
+ const ratio = 100 - (decodedCompressed.length * 100) / raw.length;
+
+ console.log(
+ `${file}: ${raw.length} => ${decodedCompressed.length}, ${ratio.toFixed(1)}% compression`
+ );
+});
diff --git a/js/tsconfig.json b/js/tsconfig.json
new file mode 100644
index 0000000..75abdef
--- /dev/null
+++ b/js/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/js/vite.config.ts b/js/vite.config.ts
new file mode 100644
index 0000000..1375cc3
--- /dev/null
+++ b/js/vite.config.ts
@@ -0,0 +1,14 @@
+import { resolve } from 'path';
+import { defineConfig } from 'vite';
+import dts from 'vite-plugin-dts';
+
+export default defineConfig({
+ build: {
+ lib: {
+ name: 'BBQr',
+ entry: resolve(__dirname, 'src/main.ts'),
+ formats: ['es', 'iife'],
+ },
+ },
+ plugins: [dts()],
+});
diff --git a/Makefile b/python/Makefile
similarity index 100%
rename from Makefile
rename to python/Makefile
diff --git a/bbqr/__init__.py b/python/bbqr/__init__.py
similarity index 100%
rename from bbqr/__init__.py
rename to python/bbqr/__init__.py
diff --git a/bbqr/cli.py b/python/bbqr/cli.py
similarity index 100%
rename from bbqr/cli.py
rename to python/bbqr/cli.py
diff --git a/bbqr/consts.py b/python/bbqr/consts.py
similarity index 100%
rename from bbqr/consts.py
rename to python/bbqr/consts.py
diff --git a/bbqr/join.py b/python/bbqr/join.py
similarity index 100%
rename from bbqr/join.py
rename to python/bbqr/join.py
diff --git a/bbqr/split.py b/python/bbqr/split.py
similarity index 100%
rename from bbqr/split.py
rename to python/bbqr/split.py
diff --git a/bbqr/tables.py b/python/bbqr/tables.py
similarity index 100%
rename from bbqr/tables.py
rename to python/bbqr/tables.py
diff --git a/bbqr/utils.py b/python/bbqr/utils.py
similarity index 100%
rename from bbqr/utils.py
rename to python/bbqr/utils.py
diff --git a/bbqr/version.py b/python/bbqr/version.py
similarity index 100%
rename from bbqr/version.py
rename to python/bbqr/version.py
diff --git a/requirements.txt b/python/requirements.txt
similarity index 100%
rename from requirements.txt
rename to python/requirements.txt
diff --git a/setup.py b/python/setup.py
similarity index 96%
rename from setup.py
rename to python/setup.py
index 8f0a33f..a1674c3 100644
--- a/setup.py
+++ b/python/setup.py
@@ -21,7 +21,7 @@
'pytest'
]
-with open("README.md", "r") as fh:
+with open("../README.md", "r") as fh:
long_description = fh.read()
# get the version w/o importing anything other parts which might need requirements
diff --git a/tests/context.py b/python/tests/context.py
similarity index 100%
rename from tests/context.py
rename to python/tests/context.py
diff --git a/tests/test_encoding.py b/python/tests/test_encoding.py
similarity index 75%
rename from tests/test_encoding.py
rename to python/tests/test_encoding.py
index c21cec0..e82b5c8 100644
--- a/tests/test_encoding.py
+++ b/python/tests/test_encoding.py
@@ -6,16 +6,16 @@
import pytest, os, pyqrcode
@pytest.mark.parametrize('fname', [
- 'data/1in1000out.psbt',
- 'data/1in100out.psbt',
- 'data/1in10out.psbt',
- 'data/1in20out.psbt',
- 'data/1in2out.psbt',
- 'data/devils-txn.txn',
- 'data/finalized-by-ckcc.txn',
- 'data/last.txn',
- 'data/nfc-result.txn',
- 'data/signed.txn',
+ '../test_data/1in1000out.psbt',
+ '../test_data/1in100out.psbt',
+ '../test_data/1in10out.psbt',
+ '../test_data/1in20out.psbt',
+ '../test_data/1in2out.psbt',
+ '../test_data/devils-txn.txn',
+ '../test_data/finalized-by-ckcc.txn',
+ '../test_data/last.txn',
+ '../test_data/nfc-result.txn',
+ '../test_data/signed.txn',
])
def test_compression(fname):
# more of a measurement than a test...
diff --git a/tests/test_join.py b/python/tests/test_join.py
similarity index 74%
rename from tests/test_join.py
rename to python/tests/test_join.py
index 4d4a161..11ac5e9 100644
--- a/tests/test_join.py
+++ b/python/tests/test_join.py
@@ -6,7 +6,7 @@
import pytest, os, pyqrcode
def test_real_scan():
- lines = [ln.strip() for ln in open('data/real-scan.txt', 'rt').readlines() if ln.strip()]
+ lines = [ln.strip() for ln in open('../test_data/real-scan.txt', 'rt').readlines() if ln.strip()]
file_type, data = bbqr.join_qrs(lines)
diff --git a/tests/test_loopback.py b/python/tests/test_loopback.py
similarity index 100%
rename from tests/test_loopback.py
rename to python/tests/test_loopback.py
diff --git a/tests/data/1in1000out.psbt b/test_data/1in1000out.psbt
similarity index 100%
rename from tests/data/1in1000out.psbt
rename to test_data/1in1000out.psbt
diff --git a/tests/data/1in100out.psbt b/test_data/1in100out.psbt
similarity index 100%
rename from tests/data/1in100out.psbt
rename to test_data/1in100out.psbt
diff --git a/tests/data/1in10out.psbt b/test_data/1in10out.psbt
similarity index 100%
rename from tests/data/1in10out.psbt
rename to test_data/1in10out.psbt
diff --git a/tests/data/1in20out.psbt b/test_data/1in20out.psbt
similarity index 100%
rename from tests/data/1in20out.psbt
rename to test_data/1in20out.psbt
diff --git a/tests/data/1in2out.psbt b/test_data/1in2out.psbt
similarity index 100%
rename from tests/data/1in2out.psbt
rename to test_data/1in2out.psbt
diff --git a/tests/data/devils-txn.txn b/test_data/devils-txn.txn
similarity index 100%
rename from tests/data/devils-txn.txn
rename to test_data/devils-txn.txn
diff --git a/tests/data/finalized-by-ckcc.txn b/test_data/finalized-by-ckcc.txn
similarity index 100%
rename from tests/data/finalized-by-ckcc.txn
rename to test_data/finalized-by-ckcc.txn
diff --git a/tests/data/last.txn b/test_data/last.txn
similarity index 100%
rename from tests/data/last.txn
rename to test_data/last.txn
diff --git a/tests/data/nfc-result.txn b/test_data/nfc-result.txn
similarity index 100%
rename from tests/data/nfc-result.txn
rename to test_data/nfc-result.txn
diff --git a/tests/data/real-scan.txt b/test_data/real-scan.txt
similarity index 100%
rename from tests/data/real-scan.txt
rename to test_data/real-scan.txt
diff --git a/tests/data/signed.txn b/test_data/signed.txn
similarity index 100%
rename from tests/data/signed.txn
rename to test_data/signed.txn