From 0c95b9b2b1b57c1b4acea7353dc7243f01c56a77 Mon Sep 17 00:00:00 2001 From: Kuldeep Date: Mon, 5 Oct 2026 12:59:22 +0000 Subject: [PATCH] Feat/parse invitation --- .cspell.json | 4 +- package-lock.json | 651 ++++++++++---------- package.json | 4 +- source/cash-assembly/collect-evaluations.ts | 2 +- source/cash-assembly/evaluations.ts | 2 +- source/errors.ts | 32 + source/event-emitter.ts | 12 +- source/index.ts | 9 +- source/invitation/errors.ts | 46 ++ source/invitation/index.ts | 4 + source/invitation/parser.ts | 27 + source/invitation/schemas.ts | 241 ++++++++ source/invitation/serialization.ts | 46 ++ source/sse-session/sse-event-parser.ts | 20 +- source/template/errors.ts | 32 - source/template/index.ts | 7 + source/template/parser.ts | 3 +- source/template/schemas.ts | 4 +- test/invitation.test.ts | 248 ++++++++ 19 files changed, 1008 insertions(+), 386 deletions(-) create mode 100644 source/invitation/errors.ts create mode 100644 source/invitation/index.ts create mode 100644 source/invitation/parser.ts create mode 100644 source/invitation/schemas.ts create mode 100644 source/invitation/serialization.ts create mode 100644 source/template/index.ts create mode 100644 test/invitation.test.ts diff --git a/.cspell.json b/.cspell.json index 6302496..819caa7 100644 --- a/.cspell.json +++ b/.cspell.json @@ -17,7 +17,9 @@ "utxotokenamount", "outputtokenamount", "outputvalue", - "numequal" + "numequal", + "Prefault", + "cashtoken" ], "ignorePaths": ["source/template/xo-template.schema.json"] } diff --git a/package-lock.json b/package-lock.json index 5a203ee..46649c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@xo-cash/utils", - "version": "0.0.9", + "version": "0.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xo-cash/utils", - "version": "0.0.9", + "version": "0.0.10", "license": "MIT", "dependencies": { "@bitauth/libauth": "^3.1.0-next.8", "@xo-cash/primitives": "0.0.2", - "@xo-cash/types": "0.0.7", + "@xo-cash/types": "0.0.8", "zod": "^4.3.6" }, "devDependencies": { @@ -248,9 +248,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { @@ -539,9 +539,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-cpp": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-7.1.1.tgz", - "integrity": "sha512-VCGYOU9Ni7vsNp2DPlU79RqN5y2EWAkKzVox/32Wm6Qa8eIgwyQLT6oV66F+qYigPbQUrd1C8uKa4aAzcjfl1g==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-7.1.3.tgz", + "integrity": "sha512-2d2MSh+C+xzzdqjLwtt40TdXWDR/pK0q1Q9L5zC+1EPSCIZxlN7CHbhlXWpl0JqUSdy2xaWaWucwnoGznMTneA==", "dev": true, "license": "MIT" }, @@ -595,9 +595,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.14.tgz", - "integrity": "sha512-gu2cZTlUA6A23yZdRvMnXOPmVpRjF0Xef0O353xrvtlStc/czZzIz546R4zV7F3duehRlkglBWcxJ52sYxTbsg==", + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.15.tgz", + "integrity": "sha512-j2MUKPlzMeDy/mnb1EjmtkX8e6VzZdw60Qyf6vPlp7WZhUwwp2LBlKOWx7+hASjP6k4a0nDpmn77G+4upxvbZA==", "dev": true, "license": "MIT" }, @@ -609,23 +609,23 @@ "license": "MIT" }, "node_modules/@cspell/dict-en_us": { - "version": "4.4.38", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.38.tgz", - "integrity": "sha512-6AmHc3BV/paM9M2TKo/Spophp3CKXgR3Un9OekwUpfVLxVRcYd4EOZGexiVK8e7ugPNzfBFGXjO8bIr6pvW3Ow==", + "version": "4.4.40", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.40.tgz", + "integrity": "sha512-TGyOaWP1dD28GRHH2Lt04ciMZC8Z6UX+5kJsi04pbQHq6uRxv14/bBVCBeSsajUXRZsJYUqXyHaHB9O0ODuFuQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.2.1.tgz", - "integrity": "sha512-9pql9P+cfne3+UiR5IUwyLdlHShIckgo9o2F/HhkMIE9LMb8/15JHytufqJv76E2XBH1aW/AHUSRNwom5w9eDA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.2.3.tgz", + "integrity": "sha512-rwskWjznsDSZe3ck03BGUNT3UY9MjO1e4E9LJWACFzdDjkdM7uzro2/WfKvZTBVWVnRlzdRXPrtjkUZFI2A66A==", "dev": true, "license": "CC BY-SA 4.0" }, "node_modules/@cspell/dict-en-gb-mit": { - "version": "3.1.27", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.27.tgz", - "integrity": "sha512-vJjW5uQHok7nDoINLRk9G970CpQG8PIEysXambN8txaozx/Y+HZZeo1otI/waaBaAntWI/0YWfc0joTKrCQLPg==", + "version": "3.1.29", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.29.tgz", + "integrity": "sha512-mInExsCFZui0f9Vg/MToWfQZzwMfPgHX8BCYdZNdXWbYz+jITXcnvssbdUkui/DVbriZVJ74Zu9bv2jOEqTCNA==", "dev": true, "license": "MIT" }, @@ -665,23 +665,23 @@ "license": "MIT" }, "node_modules/@cspell/dict-gaming-terms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.2.tgz", - "integrity": "sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.3.tgz", + "integrity": "sha512-hLGzd/f+KX5B/5YxAXuQi4DYEfu6IKudtNFO/Nima3JLXD+8Xn+d0q6VdaBxkWVhGYlzow9o+dce+S9mNtgpMg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-git": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", - "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.1.tgz", + "integrity": "sha512-XvuGVYj9YEu4EzntqyhH8XlTRyy57C6j8oxkIbUQfR2UYzxZGAP77XHl7qY7eC/40r/haDwnBaccyFR6BRmUCg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-golang": { - "version": "6.0.28", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.28.tgz", - "integrity": "sha512-SlKnNJvdjKS+jqfvLavz7zFri69Nwi6OQTW9NGSoShWaG6ZTSGYByUNKKE393EHFT1nSYWgcD7rdHDKO68F8DQ==", + "version": "6.0.29", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.29.tgz", + "integrity": "sha512-EZCgfxrr5lPnYQLmSvB6YtpfoDFt2KKxAD4ppq2xGSIgDFXAF7Mv4doiXIFUQaynCBIEfAMl3f/uDfrIUo4fyQ==", "dev": true, "license": "MIT" }, @@ -783,9 +783,9 @@ } }, "node_modules/@cspell/dict-monkeyc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.1.1.tgz", - "integrity": "sha512-AJDsxnFCMDuteMyqWKGy+CkFl9gCKj8QRqTScO3QwjUs8UG+msHJDUGeJwSvzwG35w6RGD49ev8DVcgkJv/QDQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.1.2.tgz", + "integrity": "sha512-4VzwdSUVqyUVmAKAzMkVwVa7AkvSi1l1OHdQr58MWq4VR4NQDO8sVQSibd2PMxruXTkxJt9UwVQVHgaaVTiV1Q==", "dev": true, "license": "MIT" }, @@ -797,37 +797,37 @@ "license": "MIT" }, "node_modules/@cspell/dict-npm": { - "version": "5.2.49", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.49.tgz", - "integrity": "sha512-7NoDyLcuzQKVA7pNt5vdtqpok9XJyYPxeL7FGzUTbYWi7SuTCk3QbFhoMiYoHSCc6C5G+NCI9xxWHMZnDflXlA==", + "version": "5.2.52", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.52.tgz", + "integrity": "sha512-JttzSladJ4KVhfg9QiBM4os+wAKbet2F58wpbM5VJLnL8fAj3xwvBvSxdY64yrW6Y5wdSPHoTCLqXOKoyBJFnA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-php": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.2.tgz", - "integrity": "sha512-XjPOglaEG2sOyImpVtnmAAsXv7saJevoP5FBGT+JyiS9UclIpw01CkiZU0I0JJ08pdXh51SiPXxx0yzw/q2jdw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.3.tgz", + "integrity": "sha512-41oKzl7rLVrrVZA/Gs0ESnP6KliEX7c+IwaaNmOL3PAoHasFDVEmIFeENIzuDdM12UcHT+MlLkAm3Qw/lS/ERA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-powershell": { - "version": "5.0.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.16.tgz", - "integrity": "sha512-tDaC+lHpI9hjedyQ6foJWiM8b4a5FAqXJerYvH7q7lhOgFNaK+BYtvmG85jQnDLg0cgLZzSSVixauTvQ6+nf6w==", + "version": "5.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.17.tgz", + "integrity": "sha512-khOoIdBgKRk2scnr7888pf70yJ1Tr3RGdnYcAN0MJ2vtaNRKoOTCFc13gXUkNDHwEyhMI1h9oz3KsT180F2amw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.16.tgz", - "integrity": "sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.17.tgz", + "integrity": "sha512-Asdumgg3oZ4ki//b2a+beiVIsNZA5thim4t4v3lTDLY/6a8VAPmTjzC3Y2t0x7ywC/bzAhi1KW/fg8JlwHDgig==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-python": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.5.1.tgz", - "integrity": "sha512-rWMFJrmlSyMcGuIjB3/VHEj2gN9ou/AR/vyk3224w0TzMqDtjR+PtGM/NUo1Xd4APutUX4O95EpPz+0E7WO8Zg==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.5.2.tgz", + "integrity": "sha512-CFcTDGN/ycn45WXC5ZZ06pG09WbGKMpPm1rkMuqMP98/jwjw3iiK8c0izWRd8x027n1rOZ9kJ8f9Sd+bQ22HJg==", "dev": true, "license": "MIT", "dependencies": { @@ -842,9 +842,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-ruby": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.3.tgz", - "integrity": "sha512-XRc8xSKkn0mx8ES5TxbahG0qSiCGB71JzkgO9J1jWfOrNDTlDJ04glosWEQ5vuwweVXnT0H1q44/A66BZebH4w==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.4.tgz", + "integrity": "sha512-Slw1dLY/pW5liJ5Fle+MIPphIdleZkIM6STDoXCN7sy298GfbfT34sJTcSZcXzZJVXiCEAvlLp+s1YNr6DuWNg==", "dev": true, "license": "MIT" }, @@ -856,9 +856,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-scala": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.10.tgz", - "integrity": "sha512-OOmw2zt1N0ZmOj3zBIzF+qdk0SDXwaXpFtPa0yWvA8EsghnN1tEpHOMYMVFgGipAkBS1uhPbi00Gj6YkcW4cKA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.11.tgz", + "integrity": "sha512-+D59847Y61KZ0MBoXJzBMtOXfFhezEb6ShjvJlL7bnryyy6iBwAfozbz7OJAe+KrEpYm+GlVV7Wy4FmJSgxBag==", "dev": true, "license": "MIT" }, @@ -870,9 +870,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.4.3.tgz", - "integrity": "sha512-f5vD6tNKIt0S6z+eJzTJTlNqefTnNakN9frl5eBvitHOzY5joWDaYTA77dtQ8krh9YMqob03NoQqpjwh64imag==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.4.6.tgz", + "integrity": "sha512-r4Xw367hTosVCAQ884nPWhwcy3Jc6YWQFkW1XBdxFGdzdCltzuNf9qdJL1AwiTWMbiRnifIqKaXN0A38WCVEbQ==", "dev": true, "license": "MIT" }, @@ -1368,9 +1368,9 @@ } }, "node_modules/@quansync/fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", - "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==", "dev": true, "license": "MIT", "dependencies": { @@ -1381,9 +1381,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", - "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", "cpu": [ "arm" ], @@ -1746,9 +1746,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -2001,9 +2001,9 @@ "license": "MIT" }, "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", "dev": true, "license": "MIT" }, @@ -2049,9 +2049,9 @@ } }, "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.9.tgz", + "integrity": "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==", "dev": true, "license": "MIT", "dependencies": { @@ -2113,9 +2113,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "25.9.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.6.tgz", - "integrity": "sha512-JR6Q/PV5DKFvjrGFVqQJdeG0qvsqQQLDa3TzFrqVwhqRXqwNaxPo2KYCtCQXpOdIulCouKwT7a6in9nFthBAzw==", + "version": "25.9.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.9.tgz", + "integrity": "sha512-b4e2xxj/yMeT2hNlD6x7zNFiQK2Dlmkb9CQ6v3/1G2RezKN4YS2lcEv8lzGm1mhs4f2lM4yNAMI/zUDBZBBt7w==", "dev": true, "license": "MIT", "dependencies": { @@ -2130,18 +2130,18 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz", - "integrity": "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", + "integrity": "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/type-utils": "8.70.0", - "@typescript-eslint/utils": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/type-utils": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2154,23 +2154,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.70.0", + "@typescript-eslint/parser": "^8.71.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.0.tgz", - "integrity": "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.0.tgz", + "integrity": "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3" }, "engines": { @@ -2186,15 +2186,15 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", - "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz", + "integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.70.0", - "@typescript-eslint/types": "^8.70.0", + "@typescript-eslint/tsconfig-utils": "^8.71.0", + "@typescript-eslint/types": "^8.71.0", "debug": "^4.4.3" }, "engines": { @@ -2209,15 +2209,15 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", - "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz", + "integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0" + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2228,9 +2228,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", - "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz", + "integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==", "dev": true, "license": "MIT", "peer": true, @@ -2246,16 +2246,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz", - "integrity": "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.0.tgz", + "integrity": "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/utils": "8.70.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2272,9 +2272,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", - "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz", + "integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==", "dev": true, "license": "MIT", "peer": true, @@ -2287,17 +2287,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", - "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz", + "integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/project-service": "8.70.0", - "@typescript-eslint/tsconfig-utils": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", + "@typescript-eslint/project-service": "8.71.0", + "@typescript-eslint/tsconfig-utils": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2316,17 +2316,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", - "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz", + "integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0" + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2341,14 +2341,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", - "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz", + "integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/types": "8.71.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2969,9 +2969,9 @@ } }, "node_modules/@xo-cash/types": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@xo-cash/types/-/types-0.0.7.tgz", - "integrity": "sha512-xqcuFI9kl39JY0NcHZB+lHHE3W5jthEH2e/iQKVi9c7DbZ7TzC5jKMnY29udUlWxQeNZ0fnqW/ewDar7hrryRg==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@xo-cash/types/-/types-0.0.8.tgz", + "integrity": "sha512-7L9xARP0foT60aLz2pTzRMnbluF9F2lMqEgiq7GZ09DHPWOu4Rjt2r7OcTrjUxMDMCerh/iqX1PxcUkfGnBsfg==", "license": "MIT", "dependencies": { "@bitauth/libauth": "^3.1.0-next.8" @@ -3032,9 +3032,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", "engines": { @@ -3061,9 +3061,9 @@ } }, "node_modules/ansis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", - "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.4.0.tgz", + "integrity": "sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==", "dev": true, "license": "ISC", "engines": { @@ -3299,9 +3299,9 @@ } }, "node_modules/ast-kit/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", "dev": true, "license": "MIT", "engines": { @@ -3309,9 +3309,9 @@ } }, "node_modules/ast-kit/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", "dev": true, "license": "MIT", "engines": { @@ -3319,13 +3319,13 @@ } }, "node_modules/ast-kit/node_modules/@babel/parser": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.5.tgz", - "integrity": "sha512-51RXvQNFakaS0bTpYiGkxNbUVwkPO4kONv6EVLorZABxsx+KZ6Z7uSYvi/wmKS/+X+rfj9RvOw0/ZNh+cmI0Rw==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^8.0.5" + "@babel/types": "^8.0.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -3335,14 +3335,14 @@ } }, "node_modules/ast-kit/node_modules/@babel/types": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz", - "integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" }, "engines": { "node": "^22.18.0 || >=24.11.0" @@ -3357,9 +3357,9 @@ "peer": true }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", - "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.7.tgz", + "integrity": "sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==", "dev": true, "license": "MIT", "dependencies": { @@ -3429,9 +3429,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.11.22", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.22.tgz", - "integrity": "sha512-pWc4w51fBFd7mav43/zKRC+RI6f4yfzQoVlfvE8dECePyfkn1bzLp01Fj0QACcyCZyFhiEMyD2qScfKRWgWibA==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -3531,9 +3531,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3557,9 +3557,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", - "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -3578,11 +3578,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.11.20", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -3689,9 +3689,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "version": "1.0.30001814", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", + "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", "dev": true, "funding": [ { @@ -3711,9 +3711,9 @@ "peer": true }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -3734,9 +3734,9 @@ } }, "node_modules/chalk-template": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.2.tgz", - "integrity": "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.3.tgz", + "integrity": "sha512-KDt7oQ+LRF2VLFsTAZd76ZNCKQa8LuW7SZkNJMkriMzvlSNj6dI+S+f41mfK9oTtil49seHVyabtt1753sFL2w==", "dev": true, "license": "MIT", "dependencies": { @@ -4809,9 +4809,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.427", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", - "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "version": "1.5.444", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz", + "integrity": "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==", "dev": true, "license": "ISC", "peer": true @@ -4825,9 +4825,9 @@ "peer": true }, "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz", + "integrity": "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==", "dev": true, "license": "MIT", "engines": { @@ -5637,9 +5637,9 @@ } }, "node_modules/express": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", "dev": true, "license": "MIT", "dependencies": { @@ -5662,9 +5662,9 @@ "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", + "path-to-regexp": "~0.1.13", "proxy-addr": "~2.0.7", - "qs": "~6.15.1", + "qs": "~6.16.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -5708,9 +5708,9 @@ "license": "MIT" }, "node_modules/fast-equals": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.3.tgz", - "integrity": "sha512-IECu4C0fFZFoUC2cA2rDaNUuVIfWIZMwh3tY+ng924vOuD9OaUFFrcIrVwCKbV7TJDkGmrHfhwZ7R0AnHfVhyw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.1.0.tgz", + "integrity": "sha512-MDA0N5wXvmx0yR0xeOf20qVMbyaEzS2kuxhBrDrIT5w3Gap7bZ/24jtvOvbthbZFmf+7PCC8XCJKGpmhVtvbIg==", "dev": true, "license": "MIT", "engines": { @@ -6264,9 +6264,9 @@ } }, "node_modules/hookable": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", - "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.2.tgz", + "integrity": "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==", "dev": true, "license": "MIT" }, @@ -6322,9 +6322,9 @@ } }, "node_modules/ignore": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.9.tgz", - "integrity": "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", "dev": true, "license": "MIT", "peer": true, @@ -6560,14 +6560,14 @@ } }, "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "hasown": "^2.0.3" + "hasown": "^2.0.4" }, "engines": { "node": ">= 0.4" @@ -7578,9 +7578,9 @@ } }, "node_modules/markdown-it": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.1.tgz", - "integrity": "sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA==", + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.2.tgz", + "integrity": "sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==", "dev": true, "funding": [ { @@ -7827,9 +7827,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", - "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", "dev": true, "license": "MIT", "peer": true, @@ -8264,9 +8264,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { @@ -8293,9 +8293,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8304,6 +8304,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/punycode": { @@ -8624,9 +8628,9 @@ } }, "node_modules/rolldown-plugin-dts/node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", "dev": true, "license": "MIT", "engines": { @@ -9129,9 +9133,9 @@ "license": "ISC" }, "node_modules/smol-toml": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.8.0.tgz", - "integrity": "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", + "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -9152,9 +9156,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -9190,9 +9194,9 @@ } }, "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -9430,9 +9434,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", - "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -9648,20 +9652,19 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", "gopd": "^1.2.0", - "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -9744,17 +9747,17 @@ } }, "node_modules/typescript-eslint": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.0.tgz", - "integrity": "sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.0.tgz", + "integrity": "sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.70.0", - "@typescript-eslint/parser": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/utils": "8.70.0" + "@typescript-eslint/eslint-plugin": "8.71.0", + "@typescript-eslint/parser": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10281,16 +10284,16 @@ } }, "node_modules/vite": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", - "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", - "rolldown": "~1.2.6", + "rolldown": "~1.2.11", "tinyglobby": "^0.2.17" }, "bin": { @@ -10359,9 +10362,9 @@ } }, "node_modules/vite/node_modules/@oxc-project/types": { - "version": "0.149.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", - "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", "dev": true, "license": "MIT", "funding": { @@ -10369,9 +10372,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", - "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", "cpu": [ "arm64" ], @@ -10386,9 +10389,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", - "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", "cpu": [ "arm64" ], @@ -10403,9 +10406,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", - "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", "cpu": [ "x64" ], @@ -10420,9 +10423,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", - "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", "cpu": [ "x64" ], @@ -10437,9 +10440,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", - "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", "cpu": [ "arm" ], @@ -10454,9 +10457,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", - "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", "cpu": [ "arm64" ], @@ -10471,9 +10474,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", - "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", "cpu": [ "arm64" ], @@ -10488,9 +10491,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", - "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", "cpu": [ "ppc64" ], @@ -10505,9 +10508,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", - "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", "cpu": [ "s390x" ], @@ -10522,9 +10525,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", - "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", "cpu": [ "x64" ], @@ -10539,9 +10542,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", - "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", "cpu": [ "x64" ], @@ -10556,9 +10559,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", - "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", "cpu": [ "arm64" ], @@ -10573,9 +10576,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", - "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", "cpu": [ "arm64" ], @@ -10590,9 +10593,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", - "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", "cpu": [ "x64" ], @@ -10614,13 +10617,13 @@ "license": "MIT" }, "node_modules/vite/node_modules/rolldown": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", - "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.149.0", + "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -10630,21 +10633,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.8", - "@rolldown/binding-android-arm64": "1.2.8", - "@rolldown/binding-darwin-arm64": "1.2.8", - "@rolldown/binding-darwin-x64": "1.2.8", - "@rolldown/binding-freebsd-x64": "1.2.8", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", - "@rolldown/binding-linux-arm64-gnu": "1.2.8", - "@rolldown/binding-linux-arm64-musl": "1.2.8", - "@rolldown/binding-linux-ppc64-gnu": "1.2.8", - "@rolldown/binding-linux-s390x-gnu": "1.2.8", - "@rolldown/binding-linux-x64-gnu": "1.2.8", - "@rolldown/binding-linux-x64-musl": "1.2.8", - "@rolldown/binding-openharmony-arm64": "1.2.8", - "@rolldown/binding-win32-arm64-msvc": "1.2.8", - "@rolldown/binding-win32-x64-msvc": "1.2.8" + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/vitest": { @@ -10738,9 +10741,9 @@ } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.14.tgz", - "integrity": "sha512-EQyqJMi552E4ZTf46izQ4Fj6XquqxCySR3J5ZSD1SisMf6RfpeOWHxGBE8Gr6V0/3GHIGdAzDn8F8+1nTGCnoQ==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.15.tgz", + "integrity": "sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==", "dev": true, "license": "MIT" }, @@ -10838,9 +10841,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.22", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", - "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", "dev": true, "license": "MIT", "peer": true, @@ -10909,9 +10912,9 @@ "peer": true }, "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "dev": true, "license": "ISC", "bin": { @@ -10938,9 +10941,9 @@ } }, "node_modules/zod": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.2.tgz", - "integrity": "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index e3612f1..b2a1e83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xo-cash/utils", - "version": "0.0.9", + "version": "0.0.10", "description": "XO Cash utilities", "type": "module", "types": "./dist/index.d.mts", @@ -48,7 +48,7 @@ "dependencies": { "@bitauth/libauth": "^3.1.0-next.8", "@xo-cash/primitives": "0.0.2", - "@xo-cash/types": "0.0.7", + "@xo-cash/types": "0.0.8", "zod": "^4.3.6" }, "overrides": { diff --git a/source/cash-assembly/collect-evaluations.ts b/source/cash-assembly/collect-evaluations.ts index 8a3a3ad..7f92b0a 100644 --- a/source/cash-assembly/collect-evaluations.ts +++ b/source/cash-assembly/collect-evaluations.ts @@ -200,7 +200,7 @@ const collectFromParsedScript = (parameters: CollectFromParsedScriptParameters): * first seen. * A visited id is skipped on later encounters. Opcodes and literals are ignored. Unrelated scripts * in the map are not visited. An Identifier inside a nested Push is collected from that inner Push, - * for example `<$( OP_HASH160)>` collects `ownerKey.public_key`. + * for example `<$( OP_HASH160)>` collects `someKey.public_key`. * * @param {string} evaluation - CashAssembly evaluation or script id text to scan. * @param {CashAssemblyTemplateScripts} [templateScripts] - Optional template scripts map used to visit nested script ids. diff --git a/source/cash-assembly/evaluations.ts b/source/cash-assembly/evaluations.ts index 4d1bcc1..8856bcd 100644 --- a/source/cash-assembly/evaluations.ts +++ b/source/cash-assembly/evaluations.ts @@ -237,7 +237,7 @@ export const compileCashAssemblyEvaluations = (parameters: CompileCashAssemblyEv continue; } - // Libauth WalletData is the name before the first `.`. `ownerKey.public_key` registers as `ownerKey`. + // Libauth WalletData is the name before the first `.`. `someKey.public_key` registers as `someKey`. variables[resolveIdentifierBaseName(variableName)] = { type: 'WalletData' as const }; } diff --git a/source/errors.ts b/source/errors.ts index 35f2a33..7f3765a 100644 --- a/source/errors.ts +++ b/source/errors.ts @@ -1,3 +1,35 @@ +import type { $ZodIssue } from 'zod/v4/core'; + +/** + * Formats the Zod validation failures into a single string with one line each: "- : " and top level failures + * with no field path show as "(root)" for better readability. + * + * @param issues The Zod validation failures to format. + * @returns A human readable error string for better debugging. + */ +export const buildErrorDescription = (issues: $ZodIssue[]): string => { + // Initialize an empty array to store the formatted lines. + const lines: string[] = []; + + // Iterate over the issues and format them into a string. + for (const issue of issues) { + // Get the issue path. + const issuePath = issue.path.length > 0 ? issue.path.join('.') : '(root)'; + + // The prefix that Zod adds to messages. + const messagePrefix = 'Invalid input: '; + + // Remove the prefix for better readability. + const issueMessage = issue.message.startsWith(messagePrefix) ? issue.message.slice(messagePrefix.length) : issue.message; + + // Add the formatted line to the array. + lines.push(`- ${issuePath}: ${issueMessage}`); + } + + // Return the formatted string. + return `\n${lines.join('\n')}`; +}; + /** * Error thrown when a waitFor timeout is reached */ diff --git a/source/event-emitter.ts b/source/event-emitter.ts index df43e83..30e579e 100644 --- a/source/event-emitter.ts +++ b/source/event-emitter.ts @@ -49,10 +49,10 @@ export class EventEmitter { * @returns An off callback that can be called to stop listening for events. */ on(type: K, listener: Listener, debounceMilliseconds: number = 0): OffCallback { - const { cancel, listener: cancellableListener } = this.cancellable(listener); + const { cancel, listener: cancellableListener } = this.#cancellable(listener); // Create a wrapped listener so that the debounce can be applied. - const wrappedListener = debounceMilliseconds > 0 ? this.debounce(cancellableListener, debounceMilliseconds) : cancellableListener; + const wrappedListener = debounceMilliseconds > 0 ? this.#debounce(cancellableListener, debounceMilliseconds) : cancellableListener; // If the listeners map does not have the event type, create a new set. if (!this.#listeners.has(type)) { @@ -87,10 +87,10 @@ export class EventEmitter { }; // Create a cancellable listener. - const { cancel, listener: cancellableListener } = this.cancellable(wrappedListener); + const { cancel, listener: cancellableListener } = this.#cancellable(wrappedListener); // Create a debounced listener. - const debouncedListener = debounceMilliseconds > 0 ? this.debounce(cancellableListener, debounceMilliseconds) : cancellableListener; + const debouncedListener = debounceMilliseconds > 0 ? this.#debounce(cancellableListener, debounceMilliseconds) : cancellableListener; // If the listeners map does not have the event type, create a new set. if (!this.#listeners.has(type)) { @@ -243,7 +243,7 @@ export class EventEmitter { * @param wait - The wait time in milliseconds. * @returns The debounced function. */ - private debounce(func: Listener, wait: number): Listener { + #debounce(func: Listener, wait: number): Listener { // Create a timeout variable. let timeout: ReturnType; @@ -264,7 +264,7 @@ export class EventEmitter { * @param func - The function to make cancelable. * @returns The cancellable function with a cancel method. */ - private cancellable(func: Listener): { cancel: () => void; listener: Listener } { + #cancellable(func: Listener): { cancel: () => void; listener: Listener } { let cancelled = false; return { diff --git a/source/index.ts b/source/index.ts index 1b15e23..aebe049 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,14 +2,9 @@ export * from './errors.ts'; export * from './event-emitter.ts'; export * from './exponential-backoff/index.ts'; export * from './extended-json.ts'; +export * from './invitation/index.ts'; export * from './misc.ts'; export * from './script.ts'; export * from './sse-session/index.ts'; -export * from './template/errors.ts'; -export * from './template/identifier.ts'; -export * from './template/parser.ts'; -export * from './template/schemas.ts'; +export * from './template/index.ts'; export * from './cash-assembly/index.ts'; - -// Only exporting serializeTemplate as deserializeTemplate is only used internally and parseTemplate should be used instead. -export { serializeTemplate } from './template/serialization.ts'; diff --git a/source/invitation/errors.ts b/source/invitation/errors.ts new file mode 100644 index 0000000..0a48b11 --- /dev/null +++ b/source/invitation/errors.ts @@ -0,0 +1,46 @@ +/* eslint-disable max-classes-per-file */ + +/** + * Thrown when a value does not satisfy the XOInvitation schema. + */ +export class InvitationInvalidError extends Error { + constructor(details: string) { + const defaultMessage = `Invitation invalid, expected a value matching the XOInvitation type, but got: ${details}`; + super(defaultMessage); + this.name = 'InvitationInvalidError'; + } +} + +/** + * Thrown when invitation share JSON cannot be parsed. + */ +export class InvitationJsonMalformedError extends Error { + /** + * Parser failure forwarded from JSON parsing. + */ + readonly reason: string; + + constructor(reason: string) { + const defaultMessage = `Invitation JSON malformed: expected a valid JSON string, but got: ${reason}`; + super(defaultMessage); + this.name = 'InvitationJsonMalformedError'; + this.reason = reason; + } +} + +/** + * Thrown when invitation serialization fails. + */ +export class InvitationSerializationFailedError extends Error { + /** + * Serializer failure forwarded from the serialization path. + */ + readonly reason: string; + + constructor(reason: string) { + const defaultMessage = `Invitation serialization failed: ${reason}`; + super(defaultMessage); + this.name = 'InvitationSerializationFailedError'; + this.reason = reason; + } +} diff --git a/source/invitation/index.ts b/source/invitation/index.ts new file mode 100644 index 0000000..0092d77 --- /dev/null +++ b/source/invitation/index.ts @@ -0,0 +1,4 @@ +export * from './errors.ts'; +export * from './parser.ts'; +export * from './schemas.ts'; +export * from './serialization.ts'; diff --git a/source/invitation/parser.ts b/source/invitation/parser.ts new file mode 100644 index 0000000..60e3341 --- /dev/null +++ b/source/invitation/parser.ts @@ -0,0 +1,27 @@ +import type { XOInvitation } from '@xo-cash/types'; +import { buildErrorDescription } from '../errors.ts'; +import { InvitationInvalidError } from './errors.ts'; +import { xoInvitationSchema } from './schemas.ts'; + +/** + * Validates an untyped value against the XOInvitation schema and returns it as a typed invitation. + * + * The schema parser returns a rebuilt copy whose keys follow the schema order. Commit signatures cover the JSON text of + * each commit's data, and that text depends on key order, so a rebuilt copy no longer matches its signatures. Only the + * value that was received keeps the order it was signed with. + * + * The received value itself is returned, not a copy, so the key order its commit signatures were made over is kept. + * + * @param {unknown} invitationValue - Value to validate, such as the output of Extended JSON parsing. + * @returns {XOInvitation} - The validated invitation, as the same object that was passed in. + * @throws {@link InvitationInvalidError} - When any field is missing, has the wrong type, or is not part of the invitation type. + */ +export const parseInvitation = (invitationValue: unknown): XOInvitation => { + const parseResult = xoInvitationSchema.safeParse(invitationValue); + + if (parseResult.success === false) { + throw new InvitationInvalidError(buildErrorDescription(parseResult.error.issues)); + } + + return invitationValue as XOInvitation; +}; diff --git a/source/invitation/schemas.ts b/source/invitation/schemas.ts new file mode 100644 index 0000000..2fd99a1 --- /dev/null +++ b/source/invitation/schemas.ts @@ -0,0 +1,241 @@ +/* eslint-disable @stylistic/newline-per-chained-call */ +import type { XOInvitation } from '@xo-cash/types'; +import { z } from 'zod'; +import { satoshisSchema, uint8ArraySchema, xoTemplateNftCapabilitySchema } from '../template/schemas.ts'; + +/** + * Validation schema for a reference to an input or output in an earlier commit that a later item extends. + * + * ``` + * { + * "inputs|outputs": [ + * { + * "mergesWith": { "commitIdentifier": "...", "itemIndex": 0 } ← this schema + * } + * ] + * } + * ``` + */ +export const xoInvitationMergesWithReferenceSchema = z + .object({ + commitIdentifier: z.string().describe('The commit that holds the item being extended.'), + itemIndex: z.number().int().nonnegative().describe('The position of the extended item inside that commit.'), + }) + .strict(); + +/** + * Validation schema for the template action a commit is resolved against when it differs from the invitation root. + */ +export const xoInvitationActionReferenceSchema = z + .object({ + templateIdentifier: z.string().describe('The template that defines the action.'), + actionIdentifier: z.string().describe('The action inside that template.'), + }) + .strict(); + +// ============================================================ +// Commit data +// ============================================================ + +/** + * Validation schema for a variable value. Matches every value type an invitation variable can hold. + */ +export const xoInvitationVariableValueSchema = z + .union([ z.bigint(), z.boolean(), z.number(), z.string() ]) + .describe('A variable value expressed as a bigint, boolean, number, or string.'); + +/** + * Validation schema for a variable supplied by a commit. + */ +export const xoInvitationVariableSchema = z + .object({ + variableIdentifier: z.string().describe('The template variable this value is for.'), + value: xoInvitationVariableValueSchema, + roleIdentifier: z.string().exactOptional().describe('The role this variable belongs to.'), + mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), + }) + .strict(); + +/** + * Validation schema for a cashtoken. + */ +export const outputTokenSchema = z + .object({ + category: uint8ArraySchema.describe('The token category.'), + amount: z.bigint().describe('The fungible token amount.'), + nft: z + .object({ + capability: xoTemplateNftCapabilitySchema, + commitment: uint8ArraySchema.describe('The NFT commitment.'), + }) + .strict() + .exactOptional(), + }) + .strict(); + +/** + * Validation schema for a cashtoken supplied by a commit output. Every field is optional because a later commit may + * supply the fields an earlier commit left unset. + */ +export const xoInvitationTokenSchema = z + .object({ + amount: z.bigint().exactOptional().describe('The fungible token amount.'), + category: uint8ArraySchema.exactOptional().describe('The token category.'), + nft: z + .object({ + capability: xoTemplateNftCapabilitySchema.exactOptional(), + commitment: uint8ArraySchema.exactOptional().describe('The NFT commitment.'), + }) + .strict() + .exactOptional(), + }) + .strict(); + +/** + * Validation schema for a value one participant resolved from its own template key, such as a public key or a signature. + */ +export const xoInvitationResolutionSchema = z + .object({ + resolutionIdentifier: z.string().describe('The key operation this value resolves, such as someKey.public_key.'), + value: uint8ArraySchema.describe('The resolved bytes.'), + roleIdentifier: z.string().exactOptional().describe('The role whose key this value was resolved from.'), + }) + .strict(); + +/** + * Validation schema for a transaction input supplied by a commit. + */ +export const xoInvitationInputSchema = z + .object({ + outpointIndex: z.number().int().nonnegative().exactOptional().describe('The index of the spent output in its transaction.'), + outpointTransactionHash: uint8ArraySchema.exactOptional().describe('The hash of the transaction that holds the spent output.'), + sequenceNumber: z.number().int().nonnegative().exactOptional().describe('The sequence number of this input.'), + unlockingBytecode: uint8ArraySchema.exactOptional().describe('The script that unlocks the spent output.'), + inputIdentifier: z.string().exactOptional().describe('The template input this input fills.'), + transactionIndex: z.number().int().nonnegative().exactOptional().describe('The position this input must take in the final transaction.'), + roleIdentifier: z.string().exactOptional().describe('The role this input belongs to.'), + resolutions: z.array(xoInvitationResolutionSchema).exactOptional(), + mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), + valueSatoshis: satoshisSchema.exactOptional(), + token: outputTokenSchema.exactOptional(), + }) + .strict(); + +/** + * Validation schema for a transaction output supplied by a commit. + */ +export const xoInvitationOutputSchema = z + .object({ + lockingBytecode: uint8ArraySchema.exactOptional().describe('The locking bytecode of this output.'), + valueSatoshis: satoshisSchema.exactOptional(), + token: xoInvitationTokenSchema.exactOptional(), + outputIdentifier: z.string().exactOptional().describe('The template output this output fills.'), + transactionIndex: z.number().int().nonnegative().exactOptional().describe('The position this output must take in the final transaction.'), + secretIdentifier: z.string().exactOptional().describe('The secret used to derive this output.'), + roleIdentifier: z.string().exactOptional().describe('The role this output belongs to.'), + mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), + }) + .strict(); + +/** + * Validation schema for the data a commit supplies. + */ +export const xoInvitationCommitDataSchema = z + .object({ + transactionVersion: z.number().int().nonnegative().exactOptional().describe('The transaction version.'), + transactionLocktime: z.number().int().nonnegative().exactOptional().describe('The transaction locktime.'), + variables: z.array(xoInvitationVariableSchema).exactOptional(), + inputs: z.array(xoInvitationInputSchema).exactOptional(), + outputs: z.array(xoInvitationOutputSchema).exactOptional(), + resolutions: z.array(xoInvitationResolutionSchema).exactOptional(), + roleIdentifier: z.string().exactOptional().describe('The role the committing entity takes in the action with this commit.'), + }) + .strict(); + +/** + * Validation schema for one signed commit in an invitation. + */ +export const xoInvitationCommitSchema = z + .object({ + commitIdentifier: z.string().describe('The identifier of this commit.'), + // The type declares this key on every commit, but serialization drops it on the first commit where it is undefined. + // Prefault restores the missing key, in this case it's undefined. + previousCommitIdentifier: z.string().optional().prefault(undefined).describe('The commit this one follows. Undefined on the first commit.'), + entityIdentifier: z.string().describe('The entity that made this commit.'), + createdAtTimestamp: z.number().int().nonnegative().describe('When this commit was created, as a Unix timestamp in milliseconds.'), + actionReference: xoInvitationActionReferenceSchema.exactOptional(), + data: xoInvitationCommitDataSchema, + signature: z.string().describe('The signature over this commit.'), + expiresAtTimestamp: z.number().int().nonnegative().describe('When this commit expires, as a Unix timestamp in milliseconds.'), + memo: z.string().exactOptional().describe('A free text note attached to this commit.'), + }) + .strict(); + +// ============================================================ +// Results +// ============================================================ + +/** + * Validation schema for a transaction result that has been created but not broadcast. + */ +export const xoInvitationTransactionCreatedResultSchema = z + .object({ + transactionHash: z.string().describe('The hash of the created transaction.'), + createdAtTimestamp: z.number().int().nonnegative().describe('When the transaction was created, as a Unix timestamp in milliseconds.'), + createdByEntityIdentifier: z.string().describe('The entity that created the transaction.'), + createdBySignature: z.string().describe('The signature over the created result.'), + }) + .strict(); + +/** + * Validation schema for a transaction result that has been created and broadcast. + */ +export const xoInvitationTransactionBroadcastedResultSchema = xoInvitationTransactionCreatedResultSchema + .extend({ + broadcastedAtTimestamp: z.number().int().nonnegative().describe('When the transaction was broadcasted, as a Unix timestamp in milliseconds.'), + broadcastedByEntityIdentifier: z.string().describe('The entity that broadcasted the transaction.'), + broadcastedBySignature: z.string().describe('The signature over the broadcasted result.'), + }) + .strict(); + +/** + * Validation schema for a data result created on an invitation. + */ +export const xoInvitationCreatedResultSchema = z + .object({ + value: xoInvitationVariableValueSchema, + createdAtTimestamp: z.number().int().nonnegative().describe('When the value was created, as a Unix timestamp in milliseconds.'), + createdByEntityIdentifier: z.string().describe('The entity that created the value.'), + createdBySignature: z.string().describe('The signature over the created value.'), + }) + .strict(); + +/** + * Validation schema for the results created on an invitation. + */ +export const xoInvitationResultSchema = z + .object({ + transactions: z + .record(z.string(), z.union([ xoInvitationTransactionBroadcastedResultSchema, xoInvitationTransactionCreatedResultSchema ])) + .exactOptional(), + data: z.record(z.string(), xoInvitationCreatedResultSchema).exactOptional(), + }) + .strict(); + +// ============================================================ +// Invitation +// ============================================================ + +/** + * Validation schema for an invitation. + */ +export const xoInvitationSchema: z.ZodType = z + .object({ + invitationIdentifier: z.string().describe('The identifier of this invitation.'), + createdAtTimestamp: z.number().int().nonnegative().describe('When this invitation was created, as a Unix timestamp in milliseconds.'), + templateIdentifier: z.string().describe('The template this invitation uses.'), + actionIdentifier: z.string().describe('The template action this invitation performs.'), + commits: z.array(xoInvitationCommitSchema), + result: xoInvitationResultSchema.exactOptional(), + }) + .strict(); diff --git a/source/invitation/serialization.ts b/source/invitation/serialization.ts new file mode 100644 index 0000000..b1eca80 --- /dev/null +++ b/source/invitation/serialization.ts @@ -0,0 +1,46 @@ +import type { XOInvitation } from '@xo-cash/types'; +import { fromExtendedJson, toExtendedJson } from '../extended-json.ts'; +import { InvitationJsonMalformedError, InvitationSerializationFailedError } from './errors.ts'; +import { parseInvitation } from './parser.ts'; + +/** + * Serializes an invitation for sharing. Encodes bigint and Uint8Array fields using Extended JSON. + * + * @param invitation Shared invitation to export. + * @returns JSON string that {@link deserializeInvitation} can restore. + * @throws {InvitationSerializationFailedError} When JSON serialization fails. + */ +export const serializeInvitation = (invitation: XOInvitation): string => { + try { + return toExtendedJson(invitation); + } catch (serializationError) { + const reason = serializationError instanceof Error ? serializationError.message : 'unknown error while serializing invitation'; + + throw new InvitationSerializationFailedError(reason); + } +}; + +/** + * Deserializes a shared invitation JSON string into an invitation. + * + * Every field is checked against its declared type, so the returned invitation can be trusted to match + * {@link XOInvitation}. + * + * @param serializedInvitation JSON produced by {@link serializeInvitation}. + * @returns Parsed invitation whose fields match their declared types. + * @throws {InvitationJsonMalformedError} When the string is not valid JSON. + * @throws {InvitationInvalidError} When the parsed value does not match the XOInvitation type. + */ +export const deserializeInvitation = (serializedInvitation: string): XOInvitation => { + let parsedValue: unknown; + + try { + parsedValue = fromExtendedJson(serializedInvitation); + } catch (parsingError) { + const reason = parsingError instanceof Error ? parsingError.message : 'unknown error while deserializing invitation'; + + throw new InvitationJsonMalformedError(reason); + } + + return parseInvitation(parsedValue); +}; diff --git a/source/sse-session/sse-event-parser.ts b/source/sse-session/sse-event-parser.ts index c3bd1e8..7545a97 100644 --- a/source/sse-session/sse-event-parser.ts +++ b/source/sse-session/sse-event-parser.ts @@ -79,7 +79,7 @@ export class SSEEventParser { * @returns Zero or more complete parsed SSE events from this chunk. */ public parseEvents(chunk: Uint8Array): SSEvent[] { - const lines = this.getBufferedLines(chunk); + const lines = this.#getBufferedLines(chunk); const eventLines = lines.slice(0, -1); @@ -91,7 +91,7 @@ export class SSEEventParser { // A blank line indicates the end of an event. If we have received data, we can complete the event if (line === '') { if (event.data !== undefined) { - events.push(this.completeEvent(event)); + events.push(this.#completeEvent(event)); event = {}; processedLineCount = index + 1; } @@ -99,10 +99,10 @@ export class SSEEventParser { continue; } - this.parseLine(line, event); + this.#parseLine(line, event); } - this.storeRemainingLines(lines, processedLineCount); + this.#storeRemainingLines(lines, processedLineCount); return events; } @@ -114,7 +114,7 @@ export class SSEEventParser { * Accepts `\r\n`, `\r`, and `\n` line endings so events parse correctly * regardless of server or platform conventions. */ - private getBufferedLines(chunk: Uint8Array): string[] { + #getBufferedLines(chunk: Uint8Array): string[] { this.#messageBuffer += this.#textDecoder.decode(chunk, { stream: true }); return this.#messageBuffer.split(SSE_LINE_ENDINGS); @@ -126,7 +126,7 @@ export class SSEEventParser { * Lines without a colon are ignored. A single optional space after the colon * is stripped from the field value, per the SSE spec. */ - private parseLine(line: string, event: Partial): void { + #parseLine(line: string, event: Partial): void { const colonIndex = line.indexOf(':'); if (colonIndex === -1) return; @@ -150,7 +150,7 @@ export class SSEEventParser { return; case 'retry': - this.parseRetry(value, event); + this.#parseRetry(value, event); return; } @@ -161,7 +161,7 @@ export class SSEEventParser { * * Non-numeric values are ignored rather than failing the parse. */ - private parseRetry(value: string, event: Partial): void { + #parseRetry(value: string, event: Partial): void { const retry = parseInt(value, 10); if (!isNaN(retry)) { @@ -175,7 +175,7 @@ export class SSEEventParser { * Trims a trailing newline from multi-line `data` values so callers receive * the payload without an extra line break at the end. */ - private completeEvent(event: Partial): SSEvent { + #completeEvent(event: Partial): SSEvent { return { ...event, data: event.data?.replace(SSE_TRAILING_NEWLINE_REGEX, ''), @@ -188,7 +188,7 @@ export class SSEEventParser { * Only lines that were fully processed (through a completed event boundary) * are discarded; the remainder is re-encoded into {@link messageBuffer}. */ - private storeRemainingLines(lines: string[], processedLineCount: number): void { + #storeRemainingLines(lines: string[], processedLineCount: number): void { this.#messageBuffer = lines.slice(processedLineCount).join(NEW_LINE); } } diff --git a/source/template/errors.ts b/source/template/errors.ts index 73a89f2..ae739ef 100644 --- a/source/template/errors.ts +++ b/source/template/errors.ts @@ -1,37 +1,5 @@ /* eslint-disable max-classes-per-file */ -import type { $ZodIssue } from 'zod/v4/core'; - -/** - * Formats the Zod validation failures into a single string with one line each: "- : " and top level failures - * with no field path show as "(root)" for better readability. - * - * @param issues The Zod validation failures to format. - * @returns A human readable error string for better debugging. - */ -export const buildErrorDescription = (issues: $ZodIssue[]): string => { - // Initialize an empty array to store the formatted lines. - const lines: string[] = []; - - // Iterate over the issues and format them into a string. - for (const issue of issues) { - // Get the issue path. - const issuePath = issue.path.length > 0 ? issue.path.join('.') : '(root)'; - - // The prefix that Zod adds to messages. - const messagePrefix = 'Invalid input: '; - - // Remove the prefix for better readability. - const issueMessage = issue.message.startsWith(messagePrefix) ? issue.message.slice(messagePrefix.length) : issue.message; - - // Add the formatted line to the array. - lines.push(`- ${issuePath}: ${issueMessage}`); - } - - // Return the formatted string. - return `\n${lines.join('\n')}`; -}; - /** * Thrown when the provided template does not satisfy the XOTemplate schema. */ diff --git a/source/template/index.ts b/source/template/index.ts new file mode 100644 index 0000000..4bfcff5 --- /dev/null +++ b/source/template/index.ts @@ -0,0 +1,7 @@ +export * from './errors.ts'; +export * from './identifier.ts'; +export * from './parser.ts'; +export * from './schemas.ts'; + +// Only exporting serializeTemplate as deserializeTemplate is only used internally and parseTemplate should be used instead. +export { serializeTemplate } from './serialization.ts'; diff --git a/source/template/parser.ts b/source/template/parser.ts index 8b5b84c..1ef53c2 100644 --- a/source/template/parser.ts +++ b/source/template/parser.ts @@ -1,6 +1,7 @@ import type { XOTemplate } from '@xo-cash/types'; import { xoTemplateSchema } from './schemas.ts'; -import { TemplateInvalidError, buildErrorDescription } from './errors.ts'; +import { buildErrorDescription } from '../errors.ts'; +import { TemplateInvalidError } from './errors.ts'; import { deserializeTemplate, serializeTemplate } from './serialization.ts'; /** diff --git a/source/template/schemas.ts b/source/template/schemas.ts index 76aa868..19ced85 100644 --- a/source/template/schemas.ts +++ b/source/template/schemas.ts @@ -863,7 +863,9 @@ export const xoTemplateIconSchema = xoTemplateViewPropertiesSchema export const xoTemplateDefaultsSchema = z .object({ change: xoTemplateOutputIntentSchema.optional().describe('Instructions for how to construct automated change output.'), - address: xoTemplateLockingScriptIntentSchema.optional().describe('Configure the default intent structure for creating receiving addresses / locking scripts.'), + address: xoTemplateLockingScriptIntentSchema + .optional() + .describe('Configure the default intent structure for creating receiving addresses / locking scripts.'), receive: xoTemplateOutputIntentSchema.optional().describe('Configure the default intent structure for creating receive outputs.'), }) .strict(); diff --git a/test/invitation.test.ts b/test/invitation.test.ts new file mode 100644 index 0000000..19f4dee --- /dev/null +++ b/test/invitation.test.ts @@ -0,0 +1,248 @@ +import { expect, test } from 'vitest'; +import { binToHex, generateRandomBytes, hexToBin } from '@bitauth/libauth'; +import { + COMMIT_IDENTIFIER_BYTE_LENGTH, + INVITATION_IDENTIFIER_BYTE_LENGTH, + TEMPLATE_IDENTIFIER_BYTE_LENGTH, + TOKEN_CATEGORY_BYTE_LENGTH, + TRANSACTION_HASH_BYTE_LENGTH, +} from '@xo-cash/primitives'; +import type { XOInvitation } from '@xo-cash/types'; +import { + InvitationInvalidError, + InvitationJsonMalformedError, + InvitationSerializationFailedError, + deserializeInvitation, + parseInvitation, + serializeInvitation, +} from '../source/index.ts'; + +const invitationIdentifier = binToHex(generateRandomBytes(INVITATION_IDENTIFIER_BYTE_LENGTH)); +const templateIdentifier = binToHex(generateRandomBytes(TEMPLATE_IDENTIFIER_BYTE_LENGTH)); +const commitIdentifier = binToHex(generateRandomBytes(COMMIT_IDENTIFIER_BYTE_LENGTH)); +const outpointTransactionHash = binToHex(generateRandomBytes(TRANSACTION_HASH_BYTE_LENGTH)); +const tokenCategory = binToHex(generateRandomBytes(TOKEN_CATEGORY_BYTE_LENGTH)); + +// Valid invitation +const buildValidInvitation = (): XOInvitation => { + return { + invitationIdentifier, + createdAtTimestamp: 1700000000000, + templateIdentifier, + actionIdentifier: 'receive', + commits: [ + { + commitIdentifier, + previousCommitIdentifier: undefined, + entityIdentifier: 'xpub661MyMwAqRbcEngHLkoUWCKMBMGeqdqCGkqtzrHaTZub9ALw2oRfHA6fQP5n5X9VHStaNTBYomkSb8BFhUGavwD3RG1qvMkEKceTavTp2Tm', + createdAtTimestamp: 1700000000000, + data: { + transactionVersion: 2, + transactionLocktime: 0, + variables: [{ variableIdentifier: 'amount', value: 18014398509481985n, roleIdentifier: 'sender' }], + inputs: [ + { + outpointTransactionHash: hexToBin(outpointTransactionHash), + outpointIndex: 0, + sequenceNumber: 0, + inputIdentifier: 'fundingInput', + roleIdentifier: 'sender', + }, + ], + outputs: [ + { + outputIdentifier: 'receiveOutput', + valueSatoshis: 1000n, + lockingBytecode: hexToBin('76a914000000000000000000000000000000000000000088ac'), + token: { category: hexToBin(tokenCategory), amount: 21n }, + roleIdentifier: 'receiver', + }, + ], + }, + signature: + '5f487c045657f3939ecfeaaacf239a7cfd44b485c2be591f5280bf0cc3a6e5fe304e8ea23311d82b2afa4f0ad7e0a6d07ec1e0b1aaee9c44097613694390966b', + expiresAtTimestamp: 1700003600000, + }, + ], + }; +}; + +/** + * Tests that parseInvitation accepts a valid invitation and returns the same object it received. + */ +const testParseInvitationReturnsReceivedObject = (): void => { + const invitation = buildValidInvitation(); + + expect(parseInvitation(invitation)).toBe(invitation); +}; + +/** + * Tests that an invitation survives a serialize and deserialize round trip. + */ +const testSerializeInvitationRoundTripsBigintAndBytes = (): void => { + const invitation = buildValidInvitation(); + + const restoredInvitation = deserializeInvitation(serializeInvitation(invitation)); + + expect(restoredInvitation).toEqual(invitation); + + const [ restoredCommit ] = restoredInvitation.commits; + expect(restoredCommit!.data.variables![0]!.value).toBe(18014398509481985n); + expect(restoredCommit!.data.outputs![0]!.valueSatoshis).toBe(1000n); + expect(restoredCommit!.data.outputs![0]!.lockingBytecode).toBeInstanceOf(Uint8Array); + expect(binToHex(restoredCommit!.data.inputs![0]!.outpointTransactionHash!)).toBe(outpointTransactionHash); +}; + +/** + * Tests that parseInvitation throws InvitationInvalidError listing every missing required field. + */ +const testParseInvitationThrowsOnMissingRequiredFields = (): void => { + let thrownError: unknown; + + try { + parseInvitation({}); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationInvalidError); + + const expectedMessage = + 'Invitation invalid, expected a value matching the XOInvitation type, but got: \n' + + '- invitationIdentifier: expected string, received undefined\n' + + '- createdAtTimestamp: expected number, received undefined\n' + + '- templateIdentifier: expected string, received undefined\n' + + '- actionIdentifier: expected string, received undefined\n' + + '- commits: expected array, received undefined'; + + expect((thrownError as InvitationInvalidError).message).toBe(expectedMessage); +}; + +/** + * Tests that parseInvitation throws InvitationInvalidError reporting every field with a wrong type or out of range value. + */ +const testParseInvitationThrowsOnFieldTypeViolations = (): void => { + const invitationWithWrongFieldTypes = { ...buildValidInvitation(), createdAtTimestamp: -1, commits: 'none' }; + + let thrownError: unknown; + + try { + parseInvitation(invitationWithWrongFieldTypes); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationInvalidError); + + const expectedMessage = + 'Invitation invalid, expected a value matching the XOInvitation type, but got: \n' + + '- createdAtTimestamp: Too small: expected number to be >=0\n' + + '- commits: expected array, received string'; + + expect((thrownError as InvitationInvalidError).message).toBe(expectedMessage); +}; + +/** + * Tests that parseInvitation throws InvitationInvalidError for an unrecognized property at the top level. + */ +const testParseInvitationThrowsOnUnknownProperties = (): void => { + const invitationWithUnknownProperty = { ...buildValidInvitation(), unknownProperty: 'unexpected' }; + + let thrownError: unknown; + + try { + parseInvitation(invitationWithUnknownProperty); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationInvalidError); + + const expectedMessage = + 'Invitation invalid, expected a value matching the XOInvitation type, but got: \n' + '- (root): Unrecognized key: "unknownProperty"'; + + expect((thrownError as InvitationInvalidError).message).toBe(expectedMessage); +}; + +/** + * Tests that parseInvitation throws InvitationInvalidError with the full path of an unrecognized property inside commit data. + */ +const testParseInvitationThrowsOnDeepUnknownProperties = (): void => { + const invitation = buildValidInvitation(); + const [ firstCommit ] = invitation.commits; + const invitationWithDeepUnknown = { ...invitation, commits: [{ ...firstCommit, data: { ...firstCommit!.data, unknownData: 1 } }] }; + + let thrownError: unknown; + + try { + parseInvitation(invitationWithDeepUnknown); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationInvalidError); + + const expectedMessage = + 'Invitation invalid, expected a value matching the XOInvitation type, but got: \n' + '- commits.0.data: Unrecognized key: "unknownData"'; + + expect((thrownError as InvitationInvalidError).message).toBe(expectedMessage); +}; + +/** + * Tests that deserializeInvitation throws InvitationJsonMalformedError for text that is not valid JSON. + */ +const testDeserializeInvitationThrowsOnMalformedJson = (): void => { + let thrownError: unknown; + + try { + deserializeInvitation('{"invitationIdentifier":'); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationJsonMalformedError); + + const expectedMessage = 'Invitation JSON malformed: expected a valid JSON string, but got: Unexpected end of JSON input'; + + expect((thrownError as InvitationJsonMalformedError).message).toBe(expectedMessage); + expect((thrownError as InvitationJsonMalformedError).reason).toBe('Unexpected end of JSON input'); +}; + +/** + * Tests that serializeInvitation throws InvitationSerializationFailedError when the value cannot be written as JSON. + */ +const testSerializeInvitationThrowsOnCircularValue = (): void => { + // A value that refers to itself has no JSON form + const circularInvitation: Record = { ...buildValidInvitation() }; + circularInvitation.self = circularInvitation; + + let thrownError: unknown; + + try { + serializeInvitation(circularInvitation as XOInvitation); + } catch (caughtError) { + thrownError = caughtError; + } + + expect(thrownError).toBeInstanceOf(InvitationSerializationFailedError); + + const expectedMessage = + 'Invitation serialization failed: Converting circular structure to JSON\n' + + " --> starting at object with constructor 'Object'\n" + + " --- property 'self' closes the circle"; + + expect((thrownError as InvitationSerializationFailedError).message).toBe(expectedMessage); +}; + +const runTests = async (): Promise => { + test('parseInvitation: returns the received object for a valid invitation', testParseInvitationReturnsReceivedObject); + test('serializeInvitation: round trips bigint and Uint8Array fields', testSerializeInvitationRoundTripsBigintAndBytes); + test('parseInvitation: throws InvitationInvalidError on missing required fields', testParseInvitationThrowsOnMissingRequiredFields); + test('parseInvitation: throws InvitationInvalidError on field type violations', testParseInvitationThrowsOnFieldTypeViolations); + test('parseInvitation: throws InvitationInvalidError on unknown top-level property', testParseInvitationThrowsOnUnknownProperties); + test('parseInvitation: throws InvitationInvalidError on unknown property inside commit data', testParseInvitationThrowsOnDeepUnknownProperties); + test('deserializeInvitation: throws InvitationJsonMalformedError on malformed JSON', testDeserializeInvitationThrowsOnMalformedJson); + test('serializeInvitation: throws InvitationSerializationFailedError on a circular value', testSerializeInvitationThrowsOnCircularValue); +}; + +await runTests();