Mid-rewrite
This commit is contained in:
631
package-lock.json
generated
631
package-lock.json
generated
@@ -14,14 +14,16 @@
|
||||
"@xo-cash/state": "file:../state",
|
||||
"@xo-cash/templates": "file:../templates",
|
||||
"@xo-cash/types": "file:../types",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"clipboardy": "^5.1.0",
|
||||
"ink": "^5.1.0",
|
||||
"ink": "^6.6.0",
|
||||
"ink-select-input": "^6.0.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/react": "^18.3.18",
|
||||
"tsx": "^4.21.0",
|
||||
@@ -157,16 +159,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@alcalzone/ansi-tokenize": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz",
|
||||
"integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==",
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
||||
"integrity": "sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.13.1"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@bitauth/libauth": {
|
||||
@@ -638,6 +640,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/better-sqlite3": {
|
||||
"version": "7.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz",
|
||||
@@ -733,6 +745,84 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "12.6.2",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
|
||||
"integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
@@ -745,6 +835,12 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/cli-boxes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
|
||||
@@ -785,35 +881,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
|
||||
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
|
||||
"integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^5.0.0",
|
||||
"string-width": "^7.0.0"
|
||||
"slice-ansi": "^7.1.0",
|
||||
"string-width": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/slice-ansi": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
|
||||
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
||||
"node_modules/cli-truncate/node_modules/string-width": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
|
||||
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.0.0",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
"get-east-asian-width": "^1.3.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/clipboardy": {
|
||||
@@ -877,12 +973,54 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
|
||||
@@ -982,6 +1120,15 @@
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
|
||||
@@ -997,6 +1144,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1053,6 +1212,12 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
|
||||
@@ -1062,6 +1227,26 @@
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/indent-string": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
|
||||
@@ -1074,31 +1259,42 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ink": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz",
|
||||
"integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.6.0.tgz",
|
||||
"integrity": "sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.1.3",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||
"ansi-escapes": "^7.2.0",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"auto-bind": "^5.0.1",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk": "^5.6.0",
|
||||
"cli-boxes": "^3.0.0",
|
||||
"cli-cursor": "^4.0.0",
|
||||
"cli-truncate": "^4.0.0",
|
||||
"cli-truncate": "^5.1.1",
|
||||
"code-excerpt": "^4.0.0",
|
||||
"es-toolkit": "^1.22.0",
|
||||
"es-toolkit": "^1.39.10",
|
||||
"indent-string": "^5.0.0",
|
||||
"is-in-ci": "^1.0.0",
|
||||
"is-in-ci": "^2.0.0",
|
||||
"patch-console": "^2.0.0",
|
||||
"react-reconciler": "^0.29.0",
|
||||
"scheduler": "^0.23.0",
|
||||
"react-reconciler": "^0.33.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"stack-utils": "^2.0.6",
|
||||
"string-width": "^7.2.0",
|
||||
"string-width": "^8.1.0",
|
||||
"type-fest": "^4.27.0",
|
||||
"widest-line": "^5.0.0",
|
||||
"wrap-ansi": "^9.0.0",
|
||||
@@ -1106,12 +1302,12 @@
|
||||
"yoga-layout": "~3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.0.0",
|
||||
"react": ">=18.0.0",
|
||||
"react-devtools-core": "^4.19.1"
|
||||
"@types/react": ">=19.0.0",
|
||||
"react": ">=19.0.0",
|
||||
"react-devtools-core": "^6.1.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -1178,6 +1374,22 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ink/node_modules/string-width": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
|
||||
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
|
||||
@@ -1194,27 +1406,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
|
||||
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-in-ci": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz",
|
||||
"integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"is-in-ci": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -1322,24 +1537,6 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
@@ -1349,6 +1546,51 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.87.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
|
||||
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
|
||||
@@ -1377,6 +1619,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
@@ -1434,6 +1685,32 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-ms": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
|
||||
@@ -1449,32 +1726,67 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler": {
|
||||
"version": "0.29.2",
|
||||
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz",
|
||||
"integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==",
|
||||
"version": "0.33.0",
|
||||
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz",
|
||||
"integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
@@ -1509,13 +1821,42 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
@@ -1551,6 +1892,51 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
@@ -1567,21 +1953,6 @@
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -1594,6 +1965,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
@@ -1638,6 +2018,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/system-architecture": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz",
|
||||
@@ -1650,6 +2039,34 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/to-rotated": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-rotated/-/to-rotated-1.0.0.tgz",
|
||||
@@ -1682,6 +2099,18 @@
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
@@ -1727,6 +2156,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -1774,6 +2209,12 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
|
||||
@@ -24,14 +24,16 @@
|
||||
"@xo-cash/state": "file:../state",
|
||||
"@xo-cash/templates": "file:../templates",
|
||||
"@xo-cash/types": "file:../types",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"clipboardy": "^5.1.0",
|
||||
"ink": "^5.1.0",
|
||||
"ink": "^6.6.0",
|
||||
"ink-select-input": "^6.0.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/react": "^18.3.18",
|
||||
"tsx": "^4.21.0",
|
||||
|
||||
99
src/services/app.ts
Normal file
99
src/services/app.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Engine, type XOEngineOptions } from '@xo-cash/engine';
|
||||
import type { XOInvitation } from '@xo-cash/types';
|
||||
|
||||
import { Invitation } from './invitation.js';
|
||||
import { Storage } from './storage.js';
|
||||
import { SyncServer } from '../utils/sync-server.js';
|
||||
|
||||
import { EventEmitter } from '../utils/event-emitter.js';
|
||||
|
||||
export type AppEventMap = {
|
||||
'invitation-added': Invitation;
|
||||
'invitation-removed': Invitation;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
syncServerUrl: string;
|
||||
engineConfig: XOEngineOptions;
|
||||
invitationStoragePath: string;
|
||||
}
|
||||
|
||||
export class AppService extends EventEmitter<AppEventMap> {
|
||||
public engine: Engine;
|
||||
public storage: Storage;
|
||||
public config: AppConfig;
|
||||
|
||||
public invitations: Invitation[] = [];
|
||||
|
||||
static async create(seed: string, config: AppConfig): Promise<AppService> {
|
||||
// Create the engine
|
||||
const engine = await Engine.create(seed, config.engineConfig);
|
||||
|
||||
// Create our own storage for the invitations
|
||||
const storage = await Storage.create(config.invitationStoragePath);
|
||||
|
||||
// Create the app service
|
||||
return new AppService(engine, storage, config);
|
||||
}
|
||||
|
||||
constructor(engine: Engine, storage: Storage, config: AppConfig) {
|
||||
super();
|
||||
|
||||
this.engine = engine;
|
||||
this.storage = storage;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
async createInvitation(invitation: XOInvitation | string): Promise<Invitation> {
|
||||
// Make sure the engine has the template imported
|
||||
const invitationStorage = this.storage.child('invitations')
|
||||
const invitationSyncServer = new SyncServer(this.config.syncServerUrl, typeof invitation === 'string' ? invitation : invitation.invitationIdentifier);
|
||||
|
||||
const deps = {
|
||||
engine: this.engine,
|
||||
syncServer: invitationSyncServer,
|
||||
storage: invitationStorage,
|
||||
};
|
||||
|
||||
// Create the invitation
|
||||
const invitationInstance = await Invitation.create(invitation, deps);
|
||||
|
||||
// Add the invitation to the invitations array
|
||||
await this.addInvitation(invitationInstance);
|
||||
|
||||
return invitationInstance;
|
||||
}
|
||||
|
||||
async addInvitation(invitation: Invitation): Promise<void> {
|
||||
// Add the invitation to the invitations array
|
||||
this.invitations.push(invitation);
|
||||
|
||||
// Make sure the invitation is started
|
||||
await invitation.start();
|
||||
|
||||
// Emit the invitation-added event
|
||||
this.emit('invitation-added', invitation);
|
||||
}
|
||||
|
||||
async removeInvitation(invitation: Invitation): Promise<void> {
|
||||
// Remove the invitation from the invitations array
|
||||
this.invitations = this.invitations.filter(i => i !== invitation);
|
||||
|
||||
// Emit the invitation-removed event
|
||||
this.emit('invitation-removed', invitation);
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
// Get the invitations db
|
||||
const invitationsDb = this.storage.child('invitations');
|
||||
|
||||
// Load invitations from storage
|
||||
const invitations = await invitationsDb.all() as { key: string; value: XOInvitation }[];
|
||||
|
||||
// Start the invitations
|
||||
for (const { key } of invitations) {
|
||||
// TODO: This is doing some double work of grabbing the invitation data. We can probably skip it, but who knows.
|
||||
await this.createInvitation(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
305
src/services/invitation.ts
Normal file
305
src/services/invitation.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import type { AppendInvitationParameters, Engine, FindSuitableResourcesParameters } from '@xo-cash/engine';
|
||||
import type { XOInvitation, XOInvitationInput, XOInvitationOutput, XOInvitationVariable } from '@xo-cash/types';
|
||||
import type { UnspentOutputData } from '@xo-cash/state';
|
||||
|
||||
import type { SSEvent } from '../utils/sse-client.js';
|
||||
import type { SyncServer } from '../utils/sync-server.js';
|
||||
import type { Storage } from './storage.js';
|
||||
|
||||
import { EventEmitter } from '../utils/event-emitter.js'
|
||||
import { decodeExtendedJsonObject } from '../utils/ext-json.js';
|
||||
|
||||
export type InvitationEventMap = {
|
||||
'invitation-updated': XOInvitation;
|
||||
'invitation-status-changed': string;
|
||||
}
|
||||
|
||||
export type InvitationDependencies = {
|
||||
syncServer: SyncServer;
|
||||
storage: Storage;
|
||||
engine: Engine;
|
||||
}
|
||||
|
||||
export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
/**
|
||||
* Create an invitation and start the SSE Session required for it.
|
||||
*/
|
||||
static async create(invitation: XOInvitation | string, dependencies: InvitationDependencies): Promise<Invitation> {
|
||||
// If the invitation is a string, its probably an invitation identifier.
|
||||
// We will try to find the data then just call the create method again, but this time with the data.
|
||||
if(typeof invitation === 'string') {
|
||||
// Try to get the invitation from the storage
|
||||
const invitationFromStorage = await dependencies.storage.get(invitation);
|
||||
if (invitationFromStorage) {
|
||||
return this.create(invitationFromStorage, dependencies);
|
||||
}
|
||||
|
||||
// Try to get the invitation from the sync server
|
||||
const invitationFromSyncServer = await dependencies.syncServer.getInvitation(invitation);
|
||||
if (invitationFromSyncServer) {
|
||||
return this.create(invitationFromSyncServer, dependencies);
|
||||
}
|
||||
|
||||
// We cant find it. Throw an error.
|
||||
throw new Error(`Invitation not found in local or remote storage: ${invitation}`);
|
||||
}
|
||||
|
||||
// Make sure the engine has the template imported
|
||||
await dependencies.engine.importTemplate(invitation.templateIdentifier);
|
||||
|
||||
// Create the invitation
|
||||
const invitationInstance = new Invitation(invitation, dependencies);
|
||||
|
||||
// Start the invitation and its tracking
|
||||
await invitationInstance.start();
|
||||
|
||||
return invitationInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The invitation data.
|
||||
*/
|
||||
public data: XOInvitation = {
|
||||
invitationIdentifier: '',
|
||||
commits: [],
|
||||
createdAtTimestamp: 0,
|
||||
templateIdentifier: '',
|
||||
actionIdentifier: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* The sync server instance.
|
||||
*/
|
||||
private syncServer: SyncServer;
|
||||
|
||||
/**
|
||||
* The engine instance.
|
||||
*/
|
||||
private engine: Engine;
|
||||
|
||||
/**
|
||||
* The storage instance.
|
||||
* TODO: This should be a composite with the sync server (probably. We currently double handle this work, which is stupid)
|
||||
*/
|
||||
private storage: Storage;
|
||||
|
||||
/**
|
||||
* Create an invitation and start the SSE Session required for it.
|
||||
*/
|
||||
constructor(
|
||||
invitation: XOInvitation,
|
||||
dependencies: InvitationDependencies
|
||||
) {
|
||||
super();
|
||||
|
||||
this.data = invitation;
|
||||
this.engine = dependencies.engine;
|
||||
this.syncServer = dependencies.syncServer;
|
||||
this.storage = dependencies.storage;
|
||||
|
||||
// I cannot express this enough, but the event handler does not need a clean up.
|
||||
// There is this beautiful thing called a "garbage collector". Once this class is removed from scope (removed from the invitations array) all the references
|
||||
// will be removed, including the SSE Session (and therefore this handler).
|
||||
this.syncServer.on('message', this.handleSSEMessage.bind(this));
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
// Connect to the sync server and get the invitation (in parallel)
|
||||
const [_, invitation] = await Promise.all([
|
||||
this.syncServer.connect(),
|
||||
this.syncServer.getInvitation(this.data.invitationIdentifier),
|
||||
]);
|
||||
|
||||
// There is a chance we get SSE messages before the invitation is returned, so we want to combine any commits
|
||||
const sseCommits = this.data.commits;
|
||||
|
||||
// Set the invitation data with the combined commits
|
||||
this.data = { ...invitation, commits: [...sseCommits, ...invitation.commits] };
|
||||
|
||||
// Store the invitation in the storage
|
||||
await this.storage.set(this.data.invitationIdentifier, this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an SSE message.
|
||||
*
|
||||
* TODO: Invitation should sync up the initial data (top level) then everything after that should be the commits. This makes it easier to merge as we go instead of just having to overwrite the entire invitation.
|
||||
* Why this level of thought is required is beyond me. We should be given a `mergeCommits` method or "something" that lets us take whole invitation and merge commits into it.
|
||||
* NOTE: signInvitation does merge the commits... But we want to be able to add commits in *before* signing the invitation. So, we are just going to receive a single commit at a time, then just invitation.commits.push(commit); to get around this.
|
||||
* I hope we dont end up with duplicate commits :/... We also dont have a way to list invitiations, which is an... interesting choice.
|
||||
*/
|
||||
private handleSSEMessage(event: SSEvent): void {
|
||||
const data = JSON.parse(event.data) as { topic?: string; data?: unknown };
|
||||
if (data.topic === 'invitation-updated') {
|
||||
const invitation = decodeExtendedJsonObject(data.data) as XOInvitation;
|
||||
|
||||
if (invitation.invitationIdentifier !== this.data.invitationIdentifier) {
|
||||
return;
|
||||
}
|
||||
// Filter out commits that already exist (probably a faster way to do this. This is n^2)
|
||||
const newCommits = invitation.commits.filter(commit => !this.data.commits.some(c => c.commitIdentifier === commit.commitIdentifier));
|
||||
this.data.commits.push(...newCommits);
|
||||
|
||||
// Calculate the new status of the invitation
|
||||
this.updateStatus();
|
||||
|
||||
// Emit the updated event
|
||||
this.emit('invitation-updated', this.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of the invitation based on the filled in information
|
||||
*/
|
||||
private updateStatus(): void {
|
||||
// Calculate the status of the invitation
|
||||
this.emit('invitation-status-changed', 'pending - this is temporary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the invitation
|
||||
*/
|
||||
async accept(): Promise<void> {
|
||||
// Accept the invitation
|
||||
this.data = await this.engine.acceptInvitation(this.data);
|
||||
|
||||
// Sync the invitation to the sync server
|
||||
this.syncServer.publishInvitation(this.data);
|
||||
|
||||
// Update the status of the invitation
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the invitation
|
||||
*/
|
||||
async sign(): Promise<void> {
|
||||
// Sign the invitation
|
||||
const signedInvitation = await this.engine.signInvitation(this.data);
|
||||
|
||||
// Publish the signed invitation to the sync server
|
||||
this.syncServer.publishInvitation(signedInvitation);
|
||||
|
||||
// Store the signed invitation in the storage
|
||||
await this.storage.set(this.data.invitationIdentifier, signedInvitation);
|
||||
|
||||
// Update the status of the invitation
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the invitation
|
||||
*/
|
||||
async broadcast(): Promise<void> {
|
||||
// Broadcast the invitation
|
||||
const broadcastedInvitation = await this.engine.executeAction(this.data, {
|
||||
broadcastTransaction: true,
|
||||
});
|
||||
|
||||
// Store the broadcasted invitation in the storage
|
||||
await this.storage.set(this.data.invitationIdentifier, broadcastedInvitation);
|
||||
|
||||
// TODO: Am I supposed to do something here?
|
||||
|
||||
// Update the status of the invitation
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Append Operations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Append a commit to the invitation
|
||||
*/
|
||||
async append(data: AppendInvitationParameters): Promise<void> {
|
||||
// Append the commit to the invitation
|
||||
this.data = await this.engine.appendInvitation(this.data, data);
|
||||
|
||||
// Sync the invitation to the sync server
|
||||
await this.syncServer.publishInvitation(this.data);
|
||||
|
||||
// Store the invitation in the storage
|
||||
await this.storage.set(this.data.invitationIdentifier, this.data);
|
||||
|
||||
// Update the status of the invitation
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add inputs to the invitation
|
||||
*/
|
||||
async addInputs(inputs: XOInvitationInput[]): Promise<void> {
|
||||
// Append the inputs to the invitation
|
||||
await this.append({ inputs });
|
||||
|
||||
// Sync the invitation to the sync server
|
||||
await this.syncServer.publishInvitation(this.data);
|
||||
}
|
||||
|
||||
async addOutputs(outputs: XOInvitationOutput[]): Promise<void> {
|
||||
// Add the outputs to the invitation
|
||||
await this.append({ outputs });
|
||||
|
||||
// Sync the invitation to the sync server
|
||||
await this.syncServer.publishInvitation(this.data);
|
||||
}
|
||||
|
||||
async addVariables(variables: XOInvitationVariable[]): Promise<void> {
|
||||
// Add the variables to the invitation
|
||||
await this.append({ variables });
|
||||
|
||||
// Sync the invitation to the sync server
|
||||
await this.syncServer.publishInvitation(this.data);
|
||||
}
|
||||
|
||||
async findSuitableResources(options: FindSuitableResourcesParameters): Promise<UnspentOutputData[]> {
|
||||
// Find the suitable resources
|
||||
const { unspentOutputs } = await this.engine.findSuitableResources(this.data, options);
|
||||
|
||||
// Update the status of the invitation
|
||||
this.updateStatus();
|
||||
|
||||
// Return the suitable resources
|
||||
return unspentOutputs;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Getters and Queries
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get the missing requirements for the invitation
|
||||
*/
|
||||
get missingRequirements() {
|
||||
return this.engine.listMissingRequirements(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requirements for the invitation
|
||||
*/
|
||||
get requirements() {
|
||||
return this.engine.listRequirements(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available roles for the invitation
|
||||
*/
|
||||
get availableRoles() {
|
||||
return this.engine.listAvailableRoles(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the starting actions for the invitation
|
||||
*/
|
||||
get startingActions() {
|
||||
return this.engine.listStartingActions(this.data.templateIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locking bytecode for the invitation
|
||||
*/
|
||||
async getLockingBytecode(outputIdentifier: string, roleIdentifier?: string): Promise<string> {
|
||||
return this.engine.generateLockingBytecode(this.data.templateIdentifier, outputIdentifier, roleIdentifier);
|
||||
}
|
||||
}
|
||||
106
src/services/storage.ts
Normal file
106
src/services/storage.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { decodeExtendedJsonObject, encodeExtendedJsonObject } from '../utils/ext-json.js';
|
||||
|
||||
export class Storage {
|
||||
static async create(dbPath: string): Promise<Storage> {
|
||||
// Create the database
|
||||
const database = new Database(dbPath);
|
||||
|
||||
// Create the storage table if it doesn't exist
|
||||
database.prepare('CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT)').run();
|
||||
|
||||
return new Storage(database, '');
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly database: Database.Database,
|
||||
private readonly basePath: string,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the full key with basePath prefix
|
||||
*/
|
||||
private getFullKey(key: string): string {
|
||||
return this.basePath ? `${this.basePath}.${key}` : key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the basePath prefix from a key
|
||||
*/
|
||||
private stripBasePath(fullKey: string): string {
|
||||
if (!this.basePath) return fullKey;
|
||||
const prefix = `${this.basePath}.`;
|
||||
return fullKey.startsWith(prefix) ? fullKey.slice(prefix.length) : fullKey;
|
||||
}
|
||||
|
||||
async set(key: string, value: any): Promise<void> {
|
||||
// Encode the extended json object
|
||||
const encodedValue = encodeExtendedJsonObject(value);
|
||||
|
||||
// Insert or replace the value into the database with full key (including basePath)
|
||||
const fullKey = this.getFullKey(key);
|
||||
this.database.prepare('INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)').run(fullKey, encodedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all key-value pairs from this storage namespace (shallow only - no nested children)
|
||||
*/
|
||||
async all(): Promise<{ key: string; value: any }[]> {
|
||||
let query = 'SELECT key, value FROM storage';
|
||||
const params: any[] = [];
|
||||
|
||||
if (this.basePath) {
|
||||
// Filter by basePath prefix
|
||||
query += ' WHERE key LIKE ?';
|
||||
params.push(`${this.basePath}.%`);
|
||||
}
|
||||
|
||||
// Get all the rows from the database
|
||||
const rows = await this.database.prepare(query).all(...params) as { key: string; value: any }[];
|
||||
|
||||
// Filter for shallow results (only direct children)
|
||||
const filteredRows = rows.filter(row => {
|
||||
const strippedKey = this.stripBasePath(row.key);
|
||||
// Only include keys that don't have additional dots (no deeper nesting)
|
||||
return !strippedKey.includes('.');
|
||||
});
|
||||
|
||||
// Decode the extended json objects and strip basePath from keys
|
||||
return filteredRows.map(row => ({
|
||||
key: this.stripBasePath(row.key),
|
||||
value: decodeExtendedJsonObject(row.value)
|
||||
}));
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
// Get the row from the database using full key
|
||||
const fullKey = this.getFullKey(key);
|
||||
const row = await this.database.prepare('SELECT value FROM storage WHERE key = ?').get(fullKey) as { value: any };
|
||||
|
||||
// Return null if not found
|
||||
if (!row) return null;
|
||||
|
||||
// Decode the extended json object
|
||||
return decodeExtendedJsonObject(row.value);
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<void> {
|
||||
// Delete using full key
|
||||
const fullKey = this.getFullKey(key);
|
||||
this.database.prepare('DELETE FROM storage WHERE key = ?').run(fullKey);
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
if (this.basePath) {
|
||||
// Clear only items under this namespace
|
||||
this.database.prepare('DELETE FROM storage WHERE key LIKE ?').run(`${this.basePath}.%`);
|
||||
} else {
|
||||
// Clear everything
|
||||
this.database.prepare('DELETE FROM storage').run();
|
||||
}
|
||||
}
|
||||
|
||||
child(key: string): Storage {
|
||||
return new Storage(this.database, this.getFullKey(key));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ interface DialogWrapperProps {
|
||||
children: React.ReactNode;
|
||||
/** Dialog width */
|
||||
width?: number;
|
||||
/** Dialog Background Color */
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,12 +31,15 @@ function DialogWrapper({
|
||||
borderColor = colors.primary,
|
||||
children,
|
||||
width = 60,
|
||||
backgroundColor = colors.bg,
|
||||
}: DialogWrapperProps): React.ReactElement {
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="double"
|
||||
borderColor={borderColor}
|
||||
// backgroundColor={backgroundColor || 'white'}
|
||||
backgroundColor="white"
|
||||
paddingX={2}
|
||||
paddingY={1}
|
||||
width={width}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import TextInput from 'ink-text-input';
|
||||
import { InputDialog } from '../components/Dialog.js';
|
||||
import { useNavigation } from '../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import TextInput from 'ink-text-input';
|
||||
import { Screen } from '../components/Screen.js';
|
||||
import { Button, ButtonRow } from '../components/Button.js';
|
||||
import { Button } from '../components/Button.js';
|
||||
import { useNavigation } from '../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
|
||||
import { colors, logo } from '../theme.js';
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import { Screen } from '../components/Screen.js';
|
||||
import { useNavigation } from '../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
|
||||
import { colors, logoSmall } from '../theme.js';
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import SelectInput from 'ink-select-input';
|
||||
import { Screen } from '../components/Screen.js';
|
||||
import { List, type ListItem } from '../components/List.js';
|
||||
import { type ListItem } from '../components/List.js';
|
||||
import { useNavigation } from '../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../hooks/useAppContext.js';
|
||||
import { colors, formatSatoshis, formatHex, logoSmall } from '../theme.js';
|
||||
|
||||
152
src/utils/event-emitter.ts
Normal file
152
src/utils/event-emitter.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// TODO: You'll probably want to use WeakRef's here.
|
||||
|
||||
export type EventMap = Record<string, unknown>;
|
||||
|
||||
type Listener<T> = (detail: T) => void;
|
||||
|
||||
interface ListenerEntry<T> {
|
||||
listener: Listener<T>;
|
||||
wrappedListener: Listener<T>;
|
||||
debounceTime?: number;
|
||||
once?: boolean;
|
||||
}
|
||||
|
||||
export type OffCallback = () => void;
|
||||
|
||||
export class EventEmitter<T extends EventMap> {
|
||||
private listeners: Map<keyof T, Set<ListenerEntry<T[keyof T]>>> = new Map();
|
||||
|
||||
on<K extends keyof T>(
|
||||
type: K,
|
||||
listener: Listener<T[K]>,
|
||||
debounceMilliseconds?: number,
|
||||
): OffCallback {
|
||||
const wrappedListener =
|
||||
debounceMilliseconds && debounceMilliseconds > 0
|
||||
? this.debounce(listener, debounceMilliseconds)
|
||||
: listener;
|
||||
|
||||
if (!this.listeners.has(type)) {
|
||||
this.listeners.set(type, new Set());
|
||||
}
|
||||
|
||||
const listenerEntry: ListenerEntry<T[K]> = {
|
||||
listener,
|
||||
wrappedListener,
|
||||
debounceTime: debounceMilliseconds,
|
||||
};
|
||||
|
||||
this.listeners.get(type)?.add(listenerEntry as ListenerEntry<T[keyof T]>);
|
||||
|
||||
// Return an "off" callback that can be called to stop listening for events.
|
||||
return () => this.off(type, listener);
|
||||
}
|
||||
|
||||
once<K extends keyof T>(
|
||||
type: K,
|
||||
listener: Listener<T[K]>,
|
||||
debounceMilliseconds?: number,
|
||||
): OffCallback {
|
||||
const wrappedListener: Listener<T[K]> = (detail: T[K]) => {
|
||||
this.off(type, listener);
|
||||
listener(detail);
|
||||
};
|
||||
|
||||
const debouncedListener =
|
||||
debounceMilliseconds && debounceMilliseconds > 0
|
||||
? this.debounce(wrappedListener, debounceMilliseconds)
|
||||
: wrappedListener;
|
||||
|
||||
if (!this.listeners.has(type)) {
|
||||
this.listeners.set(type, new Set());
|
||||
}
|
||||
|
||||
const listenerEntry: ListenerEntry<T[K]> = {
|
||||
listener,
|
||||
wrappedListener: debouncedListener,
|
||||
debounceTime: debounceMilliseconds,
|
||||
once: true,
|
||||
};
|
||||
|
||||
this.listeners.get(type)?.add(listenerEntry as ListenerEntry<T[keyof T]>);
|
||||
|
||||
// Return an "off" callback that can be called to stop listening for events.
|
||||
return () => this.off(type, listener);
|
||||
}
|
||||
|
||||
off<K extends keyof T>(type: K, listener: Listener<T[K]>): void {
|
||||
const listeners = this.listeners.get(type);
|
||||
if (!listeners) return;
|
||||
|
||||
const listenerEntry = Array.from(listeners).find(
|
||||
(entry) =>
|
||||
entry.listener === listener || entry.wrappedListener === listener,
|
||||
);
|
||||
|
||||
if (listenerEntry) {
|
||||
listeners.delete(listenerEntry);
|
||||
}
|
||||
}
|
||||
|
||||
emit<K extends keyof T>(type: K, payload: T[K]): boolean {
|
||||
const listeners = this.listeners.get(type);
|
||||
if (!listeners) return false;
|
||||
|
||||
listeners.forEach((entry) => {
|
||||
entry.wrappedListener(payload);
|
||||
});
|
||||
|
||||
return listeners.size > 0;
|
||||
}
|
||||
|
||||
removeAllListeners(): void {
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
async waitFor<K extends keyof T>(
|
||||
type: K,
|
||||
predicate: (payload: T[K]) => boolean,
|
||||
timeoutMs?: number,
|
||||
): Promise<T[K]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const listener = (payload: T[K]) => {
|
||||
if (predicate(payload)) {
|
||||
// Clean up
|
||||
this.off(type, listener);
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
resolve(payload);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up timeout if specified
|
||||
if (timeoutMs !== undefined) {
|
||||
timeoutId = setTimeout(() => {
|
||||
this.off(type, listener);
|
||||
reject(new Error(`Timeout waiting for event "${String(type)}"`));
|
||||
}, timeoutMs);
|
||||
}
|
||||
|
||||
this.on(type, listener);
|
||||
});
|
||||
}
|
||||
|
||||
private debounce<K extends keyof T>(
|
||||
func: Listener<T[K]>,
|
||||
wait: number,
|
||||
): Listener<T[K]> {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
return (detail: T[K]) => {
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
func(detail);
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
}
|
||||
91
src/utils/sync-server.ts
Normal file
91
src/utils/sync-server.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { XOInvitation } from "@xo-cash/types";
|
||||
import { EventEmitter } from "./event-emitter.js";
|
||||
import { SSESession, type SSEvent } from "./sse-client.js";
|
||||
import { decodeExtendedJsonObject, encodeExtendedJson } from "./ext-json.js";
|
||||
|
||||
export type SyncServerEventMap = {
|
||||
'connected': void;
|
||||
'disconnected': void;
|
||||
'error': Error;
|
||||
'message': SSEvent;
|
||||
}
|
||||
|
||||
export class SyncServer extends EventEmitter<SyncServerEventMap> {
|
||||
static async from(baseUrl: string, invitationIdentifier: string): Promise<SyncServer> {
|
||||
const server = new SyncServer(baseUrl, invitationIdentifier);
|
||||
await server.connect();
|
||||
return server;
|
||||
}
|
||||
|
||||
private sse: SSESession;
|
||||
|
||||
constructor(private readonly baseUrl: string, private readonly invitationIdentifier: string) {
|
||||
super();
|
||||
|
||||
// Create an SSE Session
|
||||
this.sse = new SSESession(`${baseUrl}/invitations?invitationIdentifier=${invitationIdentifier}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
|
||||
// Create our event bubblers
|
||||
onMessage: (event: SSEvent) => this.emit('message', event),
|
||||
onError: (error: unknown) => this.emit('error', error instanceof Error ? error : new Error(String(error))),
|
||||
onDisconnected: () => this.emit('disconnected', undefined),
|
||||
onConnected: () => this.emit('connected', undefined),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the sync server.
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
// Connect to the SSE Session
|
||||
await this.sse.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the sync server.
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
// Disconnect from the SSE Session
|
||||
this.sse.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the invitation by identifier.
|
||||
* @param identifier - The invitation identifier.
|
||||
* @returns The invitation.
|
||||
*/
|
||||
async getInvitation(identifier: string): Promise<XOInvitation> {
|
||||
// Send a GET request to the sync server
|
||||
const response = await fetch(`${this.baseUrl}/invitations/${identifier}`);
|
||||
const invitation = await response.json() as XOInvitation;
|
||||
return invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish an invitation.
|
||||
* @param invitation - The invitation to create.
|
||||
* @returns The invitation.
|
||||
*/
|
||||
async publishInvitation(invitation: XOInvitation): Promise<XOInvitation> {
|
||||
// Send a POST request to the sync server
|
||||
const response = await fetch(`${this.baseUrl}/invitations`, {
|
||||
method: 'POST',
|
||||
body: encodeExtendedJson(invitation),
|
||||
});
|
||||
|
||||
// Throw is there was an issue with the request
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to publish invitation: ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Read the returned JSON
|
||||
// TODO: This should use zod to verify the response
|
||||
const data = decodeExtendedJsonObject(await response.text()) as XOInvitation;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user