From 451d64e8dce18b45e164d06fc15803d9d96c5025 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 10:08:30 +0100 Subject: [PATCH 1/9] feat: add vitest --- .github/workflows/release.yml | 26 +++ packages/core/package.json | 64 +++++--- packages/core/src/index.test.ts | 13 ++ packages/core/tsconfig.json | 24 ++- pnpm-lock.yaml | 270 +++++++++++++++++++++++++++++++- 5 files changed, 367 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 packages/core/src/index.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b99de71 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +permissions: + contents: write + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set node + uses: actions/setup-node@v4 + with: + node-version: 18 + + - run: npx changelogithub + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/packages/core/package.json b/packages/core/package.json index 29641d2..17721b9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,24 +1,40 @@ -{ - "name": "tsky", - "version": "0.1.0", - "license": "MIT", - "scripts": { - "lint": "eslint ./src", - "lint:fix": "eslint ./src --fix", - "test": "echo \"Error: no test specified\"", - "test:typescript": "tsc --noEmit" - }, - "devDependencies": { - "@antfu/eslint-config": "^3.11.2", - "@atproto/api": "^0.13.18", - "@eslint/js": "^9.15.0", - "eslint": "^8.57.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0", - "globals": "^15.12.0", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.16.0" - } -} +{ + "name": "tsky", + "type": "module", + "version": "0.0.1", + "license": "MIT", + "exports": { + ".": { + "default": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + } + }, + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "lint": "eslint ./src", + "lint:fix": "eslint ./src --fix", + "test": "vitest", + "test:typescript": "tsc --noEmit" + }, + "devDependencies": { + "@antfu/eslint-config": "^3.11.2", + "@atproto/api": "^0.13.18", + "@eslint/js": "^9.15.0", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0", + "globals": "^15.12.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vitest": "^2.1.6" + } +} diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts new file mode 100644 index 0000000..0ff5aa2 --- /dev/null +++ b/packages/core/src/index.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; +import { TSky } from './index'; + +describe('tSky', () => { + it('get Profile', async () => { + const tSky = new TSky(); + const profile = await tSky.profile(''); + + expect(profile).toEqual({ + handle: 'tSky', + }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index ba26124..74d56b9 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,8 +1,28 @@ { "compilerOptions": { + "target": "ESNext", + "moduleDetection": "force", + "useDefineForClassFields": false, "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Node", "paths": { "~/*": ["src/*"] - } - } + }, + "resolveJsonModule": true, + "allowJs": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "declaration": true, + "outDir": "dist/", + "sourceMap": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49ef604..25e9985 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^3.11.2 - version: 3.11.2(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@8.57.1)(typescript@5.7.2) + version: 3.11.2(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@8.57.1)(typescript@5.7.2)(vitest@2.1.6) '@atproto/api': specifier: ^0.13.18 version: 0.13.18 @@ -53,6 +53,9 @@ importers: typescript-eslint: specifier: ^8.16.0 version: 8.16.0(eslint@8.57.1)(typescript@5.7.2) + vitest: + specifier: ^2.1.6 + version: 2.1.6 packages: @@ -909,6 +912,35 @@ packages: vitest: optional: true + '@vitest/expect@2.1.6': + resolution: {integrity: sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==} + + '@vitest/mocker@2.1.6': + resolution: {integrity: sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.6': + resolution: {integrity: sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==} + + '@vitest/runner@2.1.6': + resolution: {integrity: sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==} + + '@vitest/snapshot@2.1.6': + resolution: {integrity: sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==} + + '@vitest/spy@2.1.6': + resolution: {integrity: sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==} + + '@vitest/utils@2.1.6': + resolution: {integrity: sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==} + '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} @@ -1053,6 +1085,10 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1091,6 +1127,10 @@ packages: builtins@5.1.0: resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -1105,6 +1145,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1118,6 +1162,10 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + ci-info@4.1.0: resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} engines: {node: '>=8'} @@ -1201,6 +1249,10 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1521,10 +1573,17 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1905,6 +1964,9 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + magic-string@0.30.14: resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} @@ -2183,6 +2245,10 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -2360,6 +2426,9 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2395,6 +2464,12 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2459,9 +2534,24 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tlds@1.255.0: resolution: {integrity: sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==} hasBin: true @@ -2586,6 +2676,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@2.1.6: + resolution: {integrity: sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite@5.4.11: resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2629,6 +2724,31 @@ packages: postcss: optional: true + vitest@2.1.6: + resolution: {integrity: sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 2.1.6 + '@vitest/ui': 2.1.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -2674,6 +2794,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2827,7 +2952,7 @@ snapshots: dependencies: '@algolia/client-common': 5.15.0 - '@antfu/eslint-config@3.11.2(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@8.57.1)(typescript@5.7.2)': + '@antfu/eslint-config@3.11.2(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@8.57.1)(typescript@5.7.2)(vitest@2.1.6)': dependencies: '@antfu/install-pkg': 0.5.0 '@clack/prompts': 0.8.2 @@ -2836,7 +2961,7 @@ snapshots: '@stylistic/eslint-plugin': 2.11.0(eslint@8.57.1)(typescript@5.7.2) '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) '@typescript-eslint/parser': 8.16.0(eslint@8.57.1)(typescript@5.7.2) - '@vitest/eslint-plugin': 1.1.12(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) + '@vitest/eslint-plugin': 1.1.12(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)(vitest@2.1.6) eslint: 8.57.1 eslint-config-flat-gitignore: 0.3.0(eslint@8.57.1) eslint-flat-config-utils: 0.4.0 @@ -3458,12 +3583,53 @@ snapshots: vite: 5.4.11 vue: 3.5.13(typescript@5.7.2) - '@vitest/eslint-plugin@1.1.12(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)': + '@vitest/eslint-plugin@1.1.12(@typescript-eslint/utils@8.16.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)(vitest@2.1.6)': dependencies: '@typescript-eslint/utils': 8.16.0(eslint@8.57.1)(typescript@5.7.2) eslint: 8.57.1 optionalDependencies: typescript: 5.7.2 + vitest: 2.1.6 + + '@vitest/expect@2.1.6': + dependencies: + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.6(vite@5.4.11)': + dependencies: + '@vitest/spy': 2.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.14 + optionalDependencies: + vite: 5.4.11 + + '@vitest/pretty-format@2.1.6': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.6': + dependencies: + '@vitest/utils': 2.1.6 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.6': + dependencies: + '@vitest/pretty-format': 2.1.6 + magic-string: 0.30.14 + pathe: 1.1.2 + + '@vitest/spy@2.1.6': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.6': + dependencies: + '@vitest/pretty-format': 2.1.6 + loupe: 3.1.2 + tinyrainbow: 1.2.0 '@vue/compiler-core@3.5.13': dependencies: @@ -3654,6 +3820,8 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 + assertion-error@2.0.1: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -3692,6 +3860,8 @@ snapshots: dependencies: semver: 7.6.3 + cac@6.7.14: {} + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -3706,6 +3876,14 @@ snapshots: ccount@2.0.1: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3717,6 +3895,8 @@ snapshots: character-entities@2.0.2: {} + check-error@2.1.1: {} + ci-info@4.1.0: {} clean-regexp@1.0.0: @@ -3791,6 +3971,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -4312,8 +4494,14 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} + expect-type@1.1.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -4677,6 +4865,8 @@ snapshots: longest-streak@3.1.0: {} + loupe@3.1.2: {} + magic-string@0.30.14: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -5140,6 +5330,8 @@ snapshots: pathe@1.1.2: {} + pathval@2.0.0: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -5347,6 +5539,8 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.3 + siginfo@2.0.0: {} + sisteransi@1.0.5: {} slashes@3.0.12: {} @@ -5378,6 +5572,10 @@ snapshots: stable-hash@0.0.4: {} + stackback@0.0.2: {} + + std-env@3.8.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -5445,8 +5643,16 @@ snapshots: text-table@0.2.0: {} + tinybench@2.9.0: {} + tinyexec@0.3.1: {} + tinypool@1.0.2: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + tlds@1.255.0: {} to-regex-range@5.0.1: @@ -5598,6 +5804,24 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite-node@2.1.6: + dependencies: + cac: 6.7.14 + debug: 4.3.7 + es-module-lexer: 1.5.4 + pathe: 1.1.2 + vite: 5.4.11 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite@5.4.11: dependencies: esbuild: 0.21.5 @@ -5656,6 +5880,39 @@ snapshots: - typescript - universal-cookie + vitest@2.1.6: + dependencies: + '@vitest/expect': 2.1.6 + '@vitest/mocker': 2.1.6(vite@5.4.11) + '@vitest/pretty-format': 2.1.6 + '@vitest/runner': 2.1.6 + '@vitest/snapshot': 2.1.6 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.14 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11 + vite-node: 2.1.6 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)): dependencies: vue: 3.5.13(typescript@5.7.2) @@ -5726,6 +5983,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: From 29f1137302ba91187c0aa362ec85c0d366144378 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 10:18:03 +0100 Subject: [PATCH 2/9] fix config --- packages/core/vitest.config.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 packages/core/vitest.config.mjs diff --git a/packages/core/vitest.config.mjs b/packages/core/vitest.config.mjs new file mode 100644 index 0000000..eb547e3 --- /dev/null +++ b/packages/core/vitest.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + '~': '/src', + }, + }, +}); From b305f7e45587038d9ccef115070ff3ca922df9a8 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 10:31:28 +0100 Subject: [PATCH 3/9] fix --- .vscode/settings.json | 7 +++++-- biome.json | 1 + packages/core/package.json | 4 +--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 29d8589..166d168 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { - "editor.defaultFormatter": "dbaeumer.vscode-eslint", + // "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.defaultFormatter": "biomejs.biome", // Disable the default formatter, use eslint instead "prettier.enable": false, @@ -10,7 +11,9 @@ "source.fixAll": "explicit", "source.organizeImports": "never", "source.fixAll.biome": "explicit", - "source.organizeImports.biome": "explicit" + "source.fixAll.eslint": "explicit", + "source.organizeImports.biome": "explicit", + "quickfix.biome": "explicit" }, // Silent the stylistic rules in you IDE, but still auto fix them diff --git a/biome.json b/biome.json index a6a2129..a9f1f5a 100644 --- a/biome.json +++ b/biome.json @@ -10,6 +10,7 @@ } }, "formatter": { + "enabled": true, "indentStyle": "space", "lineWidth": 80, "lineEnding": "crlf" diff --git a/packages/core/package.json b/packages/core/package.json index 17721b9..1e2f19b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,9 +13,7 @@ }, "module": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "tsc", "lint": "eslint ./src", From 9cc36f8b9b95331ed83138a71e4db38dfd195ba8 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 11:38:59 +0100 Subject: [PATCH 4/9] adjust code a bit --- packages/core/src/index.test.ts | 32 ++++++++++++++++---- packages/core/src/tsky/TSky.ts | 24 +++++++-------- packages/core/src/tsky/session.ts | 49 +++++++++++++++++++++++++++++++ packages/core/src/tsky/xrpc.ts | 34 +++++++++++++++++++++ 4 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 packages/core/src/tsky/session.ts create mode 100644 packages/core/src/tsky/xrpc.ts diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 0ff5aa2..056af50 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -1,13 +1,35 @@ import { describe, expect, it } from 'vitest'; import { TSky } from './index'; +const ALICE_DID = 'did:plc:jguhdmnjclquqf5lsvkyxqy3'; +const BOB_DID = 'did:plc:2ig7akkyfq256j42uxvc4g2h'; + +const env = process.env; +const TEST_CREDENTIALS = { + alice: { + handle: 'tSky', + did: ALICE_DID, + appPassword: env.ALICE_APP_PASSWORD, + }, + bob: { + handle: 'tSky', + did: BOB_DID, + appPassword: env.BOB_APP_PASSWORD, + }, +}; +const TEST_ENDPOINT = 'https://bsky.social'; + describe('tSky', () => { + const tSky = new TSky({ + url: TEST_ENDPOINT, + identifier: TEST_CREDENTIALS.alice.did, + password: TEST_CREDENTIALS.alice.appPassword, + }); + it('get Profile', async () => { - const tSky = new TSky(); - const profile = await tSky.profile(''); + const profile = await tSky.profile(ALICE_DID); - expect(profile).toEqual({ - handle: 'tSky', - }); + expect(profile).toBeDefined(); + expect(profile).toHaveProperty('handle', 'alice.tsky.dev'); }); }); diff --git a/packages/core/src/tsky/TSky.ts b/packages/core/src/tsky/TSky.ts index 22faaad..22fcdec 100644 --- a/packages/core/src/tsky/TSky.ts +++ b/packages/core/src/tsky/TSky.ts @@ -4,12 +4,19 @@ import type { AppBskyActorGetProfiles, AppBskyActorSearchActors, AppBskyActorSearchActorsTypeahead, - AppBskyNS, } from '@atproto/api'; +import { AppBskyNS } from '@atproto/api'; import { Paginator } from './Paginator'; +import { SessionManager } from './session'; +import { XrpcClient } from './xrpc'; export class TSky { - constructor(private instance: AppBskyNS) {} + xrpc: XrpcClient; + + constructor({ url, identifier, password }: { url: string; identifier: string; password: string }) { + const session = new SessionManager(url); + this.xrpc = new XrpcClient(session); + } /** * Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth. @@ -33,18 +40,11 @@ export class TSky { | AppBskyActorGetProfiles.CallOptions, ) { if (Array.isArray(identifier)) { - const res = await this.instance.actor.getProfiles( - { actors: identifier }, - options, - ); - - return res.data.profiles; + const profiles = await Promise.all(identifier.map(i => this.profile(i, options))); + return profiles; } - const res = await this.instance.actor.getProfile( - { actor: identifier[0] }, - options, - ); + const res = await this.xrpc.request('app.bsky.actor.getProfile', 'GET', { actor: identifier }); return res.data; } diff --git a/packages/core/src/tsky/session.ts b/packages/core/src/tsky/session.ts new file mode 100644 index 0000000..7b87fef --- /dev/null +++ b/packages/core/src/tsky/session.ts @@ -0,0 +1,49 @@ +interface Session { + token: string; +} + +export class SessionManager { + token?: string; + + constructor(private _baseUrl: string) { + this.token = process.env.TOKEN; // TODO: remove this hack + } + + private get baseUrl() { + return this._baseUrl; // TODO: support session-based URLs + } + + async login(identifier: string, password: string) { + // TODO: implement login + } + + async loadSession(session: Session) { + this.token = session.token; + } + + private async refreshSessionIfNecessary() { + // Check if the token is expired + // If it is, refresh it + // If it's not, do nothing + } + + async fetch(endpoint: string, options: RequestInit) { + await this.refreshSessionIfNecessary(); + + const url = new URL(endpoint, this.baseUrl); + const headers = new Headers(options.headers); + + if (!headers.has('authorization')) { + headers.set('authorization', `Bearer ${this.token}`); + } + + const response = await fetch(url, { + ...options, + headers, + }); + + // TODO: check expired token + + return response; + } +} diff --git a/packages/core/src/tsky/xrpc.ts b/packages/core/src/tsky/xrpc.ts new file mode 100644 index 0000000..8f79e33 --- /dev/null +++ b/packages/core/src/tsky/xrpc.ts @@ -0,0 +1,34 @@ +import type { SessionManager } from './session'; + +export class XrpcClient { + session: SessionManager; + + constructor(session: SessionManager) { + this.session = session; + } + + async request(nsid: string, method = 'GET', params?: Record): Promise<{ + data: T; + headers: Record; + }> { + const searchParams = new URLSearchParams(params); + + const response = await this.session.fetch(`/xrpc/${nsid}?${searchParams.toString()}`, { + method, + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.status === 200) { + const data = response.headers.get('Content-Type')?.includes('application/json') ? await response.json() : await response.text(); + return { + data, + headers: Object.fromEntries(response.headers.entries()), + }; + } + + console.error(response, await response.text()); + throw new Error('Request failed'); + } +} From b9d294c0657abf2734dee8ce18b9826588fabb72 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 12:29:31 +0100 Subject: [PATCH 5/9] adjust --- packages/core/src/tsky/session.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/tsky/session.ts b/packages/core/src/tsky/session.ts index 7b87fef..120f139 100644 --- a/packages/core/src/tsky/session.ts +++ b/packages/core/src/tsky/session.ts @@ -1,8 +1,8 @@ interface Session { - token: string; + fetchHandler: (pathname: string, init?: RequestInit) => Promise; } -export class SessionManager { +export class PasswordSession implements Session { token?: string; constructor(private _baseUrl: string) { @@ -27,18 +27,18 @@ export class SessionManager { // If it's not, do nothing } - async fetch(endpoint: string, options: RequestInit) { + async fetchHandler(endpoint: string, init?: RequestInit) { await this.refreshSessionIfNecessary(); const url = new URL(endpoint, this.baseUrl); - const headers = new Headers(options.headers); + const headers = new Headers(init?.headers); if (!headers.has('authorization')) { headers.set('authorization', `Bearer ${this.token}`); } const response = await fetch(url, { - ...options, + ...init, headers, }); From c8fd8844a65b5a8e979aef57258b4bb26ca6956d Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 12:43:12 +0100 Subject: [PATCH 6/9] undo and fix lint --- .vscode/settings.json | 7 +-- biome.json | 1 - packages/core/src/index.test.ts | 70 +++++++++++----------- packages/core/src/tsky/session.ts | 98 +++++++++++++++---------------- packages/core/src/tsky/tsky.ts | 14 ++++- packages/core/src/tsky/xrpc.ts | 79 ++++++++++++++----------- packages/core/vitest.config.mjs | 18 +++--- 7 files changed, 151 insertions(+), 136 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 166d168..29d8589 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ { - // "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.defaultFormatter": "biomejs.biome", + "editor.defaultFormatter": "dbaeumer.vscode-eslint", // Disable the default formatter, use eslint instead "prettier.enable": false, @@ -11,9 +10,7 @@ "source.fixAll": "explicit", "source.organizeImports": "never", "source.fixAll.biome": "explicit", - "source.fixAll.eslint": "explicit", - "source.organizeImports.biome": "explicit", - "quickfix.biome": "explicit" + "source.organizeImports.biome": "explicit" }, // Silent the stylistic rules in you IDE, but still auto fix them diff --git a/biome.json b/biome.json index 0135a9a..bfc453f 100644 --- a/biome.json +++ b/biome.json @@ -26,7 +26,6 @@ } }, "formatter": { - "enabled": true, "indentStyle": "space", "lineWidth": 80, "lineEnding": "crlf" diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 056af50..2f1bd5f 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -1,35 +1,35 @@ -import { describe, expect, it } from 'vitest'; -import { TSky } from './index'; - -const ALICE_DID = 'did:plc:jguhdmnjclquqf5lsvkyxqy3'; -const BOB_DID = 'did:plc:2ig7akkyfq256j42uxvc4g2h'; - -const env = process.env; -const TEST_CREDENTIALS = { - alice: { - handle: 'tSky', - did: ALICE_DID, - appPassword: env.ALICE_APP_PASSWORD, - }, - bob: { - handle: 'tSky', - did: BOB_DID, - appPassword: env.BOB_APP_PASSWORD, - }, -}; -const TEST_ENDPOINT = 'https://bsky.social'; - -describe('tSky', () => { - const tSky = new TSky({ - url: TEST_ENDPOINT, - identifier: TEST_CREDENTIALS.alice.did, - password: TEST_CREDENTIALS.alice.appPassword, - }); - - it('get Profile', async () => { - const profile = await tSky.profile(ALICE_DID); - - expect(profile).toBeDefined(); - expect(profile).toHaveProperty('handle', 'alice.tsky.dev'); - }); -}); +import { describe, expect, it } from 'vitest'; +import { TSky } from './index'; + +const ALICE_DID = 'did:plc:jguhdmnjclquqf5lsvkyxqy3'; +const BOB_DID = 'did:plc:2ig7akkyfq256j42uxvc4g2h'; + +const env = process.env; +const TEST_CREDENTIALS = { + alice: { + handle: 'tSky', + did: ALICE_DID, + appPassword: env.ALICE_APP_PASSWORD, + }, + bob: { + handle: 'tSky', + did: BOB_DID, + appPassword: env.BOB_APP_PASSWORD, + }, +}; +const TEST_ENDPOINT = 'https://bsky.social'; + +describe('tSky', () => { + const tSky = new TSky({ + url: TEST_ENDPOINT, + identifier: TEST_CREDENTIALS.alice.did, + password: TEST_CREDENTIALS.alice.appPassword, + }); + + it('get Profile', async () => { + const profile = await tSky.profile(ALICE_DID); + + expect(profile).toBeDefined(); + expect(profile).toHaveProperty('handle', 'alice.tsky.dev'); + }); +}); diff --git a/packages/core/src/tsky/session.ts b/packages/core/src/tsky/session.ts index 120f139..23fa6c0 100644 --- a/packages/core/src/tsky/session.ts +++ b/packages/core/src/tsky/session.ts @@ -1,49 +1,49 @@ -interface Session { - fetchHandler: (pathname: string, init?: RequestInit) => Promise; -} - -export class PasswordSession implements Session { - token?: string; - - constructor(private _baseUrl: string) { - this.token = process.env.TOKEN; // TODO: remove this hack - } - - private get baseUrl() { - return this._baseUrl; // TODO: support session-based URLs - } - - async login(identifier: string, password: string) { - // TODO: implement login - } - - async loadSession(session: Session) { - this.token = session.token; - } - - private async refreshSessionIfNecessary() { - // Check if the token is expired - // If it is, refresh it - // If it's not, do nothing - } - - async fetchHandler(endpoint: string, init?: RequestInit) { - await this.refreshSessionIfNecessary(); - - const url = new URL(endpoint, this.baseUrl); - const headers = new Headers(init?.headers); - - if (!headers.has('authorization')) { - headers.set('authorization', `Bearer ${this.token}`); - } - - const response = await fetch(url, { - ...init, - headers, - }); - - // TODO: check expired token - - return response; - } -} +interface Session { + fetchHandler: (pathname: string, init?: RequestInit) => Promise; +} + +export class PasswordSession implements Session { + token?: string; + + constructor(private _baseUrl: string) { + this.token = process.env.TOKEN; // TODO: remove this hack + } + + private get baseUrl() { + return this._baseUrl; // TODO: support session-based URLs + } + + async login(identifier: string, password: string) { + // TODO: implement login + } + + async loadSession(session: Session) { + this.token = session.token; + } + + private async refreshSessionIfNecessary() { + // Check if the token is expired + // If it is, refresh it + // If it's not, do nothing + } + + async fetchHandler(endpoint: string, init?: RequestInit) { + await this.refreshSessionIfNecessary(); + + const url = new URL(endpoint, this.baseUrl); + const headers = new Headers(init?.headers); + + if (!headers.has('authorization')) { + headers.set('authorization', `Bearer ${this.token}`); + } + + const response = await fetch(url, { + ...init, + headers, + }); + + // TODO: check expired token + + return response; + } +} diff --git a/packages/core/src/tsky/tsky.ts b/packages/core/src/tsky/tsky.ts index e5329c1..7327624 100644 --- a/packages/core/src/tsky/tsky.ts +++ b/packages/core/src/tsky/tsky.ts @@ -13,7 +13,11 @@ import { XrpcClient } from './xrpc'; export class TSky { xrpc: XrpcClient; - constructor({ url, identifier, password }: { url: string; identifier: string; password: string }) { + constructor({ + url, + identifier, + password, + }: { url: string; identifier: string; password: string }) { const session = new SessionManager(url); this.xrpc = new XrpcClient(session); } @@ -40,11 +44,15 @@ export class TSky { | AppBskyActorGetProfiles.CallOptions, ) { if (Array.isArray(identifier)) { - const profiles = await Promise.all(identifier.map(i => this.profile(i, options))); + const profiles = await Promise.all( + identifier.map((i) => this.profile(i, options)), + ); return profiles; } - const res = await this.xrpc.request('app.bsky.actor.getProfile', 'GET', { actor: identifier }); + const res = await this.xrpc.request('app.bsky.actor.getProfile', 'GET', { + actor: identifier, + }); return res.data; } diff --git a/packages/core/src/tsky/xrpc.ts b/packages/core/src/tsky/xrpc.ts index 8f79e33..d5e9980 100644 --- a/packages/core/src/tsky/xrpc.ts +++ b/packages/core/src/tsky/xrpc.ts @@ -1,34 +1,45 @@ -import type { SessionManager } from './session'; - -export class XrpcClient { - session: SessionManager; - - constructor(session: SessionManager) { - this.session = session; - } - - async request(nsid: string, method = 'GET', params?: Record): Promise<{ - data: T; - headers: Record; - }> { - const searchParams = new URLSearchParams(params); - - const response = await this.session.fetch(`/xrpc/${nsid}?${searchParams.toString()}`, { - method, - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (response.status === 200) { - const data = response.headers.get('Content-Type')?.includes('application/json') ? await response.json() : await response.text(); - return { - data, - headers: Object.fromEntries(response.headers.entries()), - }; - } - - console.error(response, await response.text()); - throw new Error('Request failed'); - } -} +import type { SessionManager } from './session'; + +export class XrpcClient { + session: SessionManager; + + constructor(session: SessionManager) { + this.session = session; + } + + async request( + nsid: string, + method = 'GET', + params?: Record, + ): Promise<{ + data: T; + headers: Record; + }> { + const searchParams = new URLSearchParams(params); + + const response = await this.session.fetch( + `/xrpc/${nsid}?${searchParams.toString()}`, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + if (response.status === 200) { + const data = response.headers + .get('Content-Type') + ?.includes('application/json') + ? await response.json() + : await response.text(); + return { + data, + headers: Object.fromEntries(response.headers.entries()), + }; + } + + console.error(response, await response.text()); + throw new Error('Request failed'); + } +} diff --git a/packages/core/vitest.config.mjs b/packages/core/vitest.config.mjs index eb547e3..75ff975 100644 --- a/packages/core/vitest.config.mjs +++ b/packages/core/vitest.config.mjs @@ -1,9 +1,9 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - resolve: { - alias: { - '~': '/src', - }, - }, -}); +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + resolve: { + alias: { + '~': '/src', + }, + }, +}); From 3d8b5c3014e3f644074b3f3de0706d591b7f52c2 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 13:36:49 +0100 Subject: [PATCH 7/9] enahnce code --- packages/core/src/index.test.ts | 27 +++++------ packages/core/src/tsky/session.ts | 60 +++++++++++++++++++----- packages/core/src/tsky/tsky.ts | 10 +--- packages/core/src/tsky/xrpc.ts | 78 +++++++++++++++++++++++++------ packages/core/tsconfig.json | 18 +++---- 5 files changed, 136 insertions(+), 57 deletions(-) diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 2f1bd5f..15060fb 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -1,35 +1,32 @@ import { describe, expect, it } from 'vitest'; import { TSky } from './index'; - -const ALICE_DID = 'did:plc:jguhdmnjclquqf5lsvkyxqy3'; -const BOB_DID = 'did:plc:2ig7akkyfq256j42uxvc4g2h'; +import { PasswordSession } from './tsky/session'; const env = process.env; const TEST_CREDENTIALS = { alice: { - handle: 'tSky', - did: ALICE_DID, + handle: 'alice.tsky.dev', + did: 'did:plc:jguhdmnjclquqf5lsvkyxqy3', appPassword: env.ALICE_APP_PASSWORD, }, bob: { - handle: 'tSky', - did: BOB_DID, + handle: 'bob.tsky.dev', + did: 'did:plc:2ig7akkyfq256j42uxvc4g2h', appPassword: env.BOB_APP_PASSWORD, }, }; const TEST_ENDPOINT = 'https://bsky.social'; describe('tSky', () => { - const tSky = new TSky({ - url: TEST_ENDPOINT, - identifier: TEST_CREDENTIALS.alice.did, - password: TEST_CREDENTIALS.alice.appPassword, - }); + it('.profile()', async () => { + const session = new PasswordSession(TEST_ENDPOINT); + await session.login(TEST_CREDENTIALS.alice.did, TEST_CREDENTIALS.alice.appPassword); + + const tSky = new TSky(session); - it('get Profile', async () => { - const profile = await tSky.profile(ALICE_DID); + const profile = await tSky.profile(TEST_CREDENTIALS.alice.did); expect(profile).toBeDefined(); - expect(profile).toHaveProperty('handle', 'alice.tsky.dev'); + expect(profile).toHaveProperty('handle', TEST_CREDENTIALS.alice.handle); }); }); diff --git a/packages/core/src/tsky/session.ts b/packages/core/src/tsky/session.ts index 23fa6c0..9d54c71 100644 --- a/packages/core/src/tsky/session.ts +++ b/packages/core/src/tsky/session.ts @@ -1,9 +1,33 @@ -interface Session { +export interface Session { fetchHandler: (pathname: string, init?: RequestInit) => Promise; } + +function isTokenExpired(response: Response) { + if (response.status !== 400) return false + + const contentLength = Number(response.headers.get('content-length') ?? '0'); + + // FROM: https://github.com/mary-ext/atcute/blob/3fcf7f990d494049f87d07e940fcc6550b7fbc67/packages/core/client/lib/credential-manager.ts#L293 + // {"error":"ExpiredToken","message":"Token has expired"} + // {"error":"ExpiredToken","message":"Token is expired"} + if (contentLength > 54 * 1.5) { + return false; + } + + return response.clone().json().then((json) => { + if (json.error === 'ExpiredToken') { + return true; + } + + return false; + }).catch(() => false); +} + export class PasswordSession implements Session { token?: string; + identifier?: string; + password?: string; constructor(private _baseUrl: string) { this.token = process.env.TOKEN; // TODO: remove this hack @@ -17,18 +41,12 @@ export class PasswordSession implements Session { // TODO: implement login } - async loadSession(session: Session) { - this.token = session.token; - } - - private async refreshSessionIfNecessary() { - // Check if the token is expired - // If it is, refresh it - // If it's not, do nothing + private async refresh(force = false) { + console.log('Refreshing token', { force }); } async fetchHandler(endpoint: string, init?: RequestInit) { - await this.refreshSessionIfNecessary(); + await this.refresh(); const url = new URL(endpoint, this.baseUrl); const headers = new Headers(init?.headers); @@ -42,8 +60,26 @@ export class PasswordSession implements Session { headers, }); - // TODO: check expired token + if (!isTokenExpired(response)) { + return response; + } - return response; + try { + await this.refresh(true); + } catch (e) { + return response; + } + + // if the body is a stream, we can't retry + if (ReadableStream && init?.body instanceof ReadableStream) { + return response + } + + // try again with the new token + headers.set('authorization', `Bearer ${this.token}`); + return fetch(url, { + ...init, + headers, + }); } } diff --git a/packages/core/src/tsky/tsky.ts b/packages/core/src/tsky/tsky.ts index 7327624..226b9c4 100644 --- a/packages/core/src/tsky/tsky.ts +++ b/packages/core/src/tsky/tsky.ts @@ -5,20 +5,14 @@ import type { AppBskyActorSearchActors, AppBskyActorSearchActorsTypeahead, } from '@atproto/api'; -import { AppBskyNS } from '@atproto/api'; import { Paginator } from './paginator'; -import { SessionManager } from './session'; +import type { Session } from './session'; import { XrpcClient } from './xrpc'; export class TSky { xrpc: XrpcClient; - constructor({ - url, - identifier, - password, - }: { url: string; identifier: string; password: string }) { - const session = new SessionManager(url); + constructor(session: Session) { this.xrpc = new XrpcClient(session); } diff --git a/packages/core/src/tsky/xrpc.ts b/packages/core/src/tsky/xrpc.ts index d5e9980..a3b9a82 100644 --- a/packages/core/src/tsky/xrpc.ts +++ b/packages/core/src/tsky/xrpc.ts @@ -1,9 +1,66 @@ -import type { SessionManager } from './session'; +import type { Session } from './session'; + +async function getResponseJSONData( + response: Response, + fail?: true, +): Promise; +async function getResponseJSONData( + response: Response, + fail: false, +): Promise; +async function getResponseJSONData( + response: Response, + fail = true, +): Promise { + if (response.headers.get('Content-Type')?.includes('application/json')) { + return response.json(); + } + + if (fail) { + throw new Error('Response is not JSON'); + } + + return null; +} + +async function getResponseContent(response: Response): Promise { + const json = await getResponseJSONData(response); + if (json) { + return json; + } + + return response.text(); +} + +export class XrpcError extends Error { + statusCode: number; + + constructor(statusCode: number, error?: string, message?: string) { + super(message || error); + this.statusCode = statusCode; + } + + static async fromResponse(response: Response): Promise { + const data = await getResponseJSONData<{ + error?: string; + message?: string; + }>(response, false); + if (data) { + return new XrpcError(response.status, data.error, data.message); + } + + return new XrpcError( + response.status, + response.statusText, + response.statusText, + ); + } +} export class XrpcClient { - session: SessionManager; + session: Session; - constructor(session: SessionManager) { + constructor(session: Session) { this.session = session; } @@ -17,7 +74,7 @@ export class XrpcClient { }> { const searchParams = new URLSearchParams(params); - const response = await this.session.fetch( + const response = await this.session.fetchHandler( `/xrpc/${nsid}?${searchParams.toString()}`, { method, @@ -27,19 +84,14 @@ export class XrpcClient { }, ); - if (response.status === 200) { - const data = response.headers - .get('Content-Type') - ?.includes('application/json') - ? await response.json() - : await response.text(); + if (response.status >= 200 && response.status < 300) { + const data = await getResponseContent(response); return { - data, + data: data as T, headers: Object.fromEntries(response.headers.entries()), }; } - console.error(response, await response.text()); - throw new Error('Request failed'); + throw await XrpcError.fromResponse(response); } } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index f68a77a..ef52a1d 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "target": "ESNext", - "lib": ["ESNext"], + "lib": ["ESNext", "DOM"], + "moduleDetection": "force", "useDefineForClassFields": false, "experimentalDecorators": true, "baseUrl": ".", @@ -10,24 +11,23 @@ "paths": { "~/*": ["src/*"] }, + "resolveJsonModule": true, + "allowJs": true, "strict": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, - "declaration": false, - "noEmit": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "moduleDetection": "force", - "resolveJsonModule": true, - "allowJs": true, "noUnusedLocals": true, "noUnusedParameters": true, + "declaration": false, + "noEmit": true, "outDir": "dist/", "sourceMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, "isolatedModules": true, "verbatimModuleSyntax": true, + "skipLibCheck": true }, "include": ["./src/**/*", "./tests/**/*", "./test-utils/**/*"], "typedocOptions": { From eb36b52e8a51c8d7f31e9c2726459fb0c91db685 Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 14:08:03 +0100 Subject: [PATCH 8/9] structure feed --- packages/core/src/bsky/feed.ts | 19 +++++++++--------- packages/core/src/index.test.ts | 20 +++++++++++++++++++ packages/core/src/tsky/tsky.ts | 34 ++++++--------------------------- packages/core/src/tsky/xrpc.ts | 10 +++++----- 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/packages/core/src/bsky/feed.ts b/packages/core/src/bsky/feed.ts index 5d434f8..112576e 100644 --- a/packages/core/src/bsky/feed.ts +++ b/packages/core/src/bsky/feed.ts @@ -9,9 +9,10 @@ import type { AppBskyNS, } from '@atproto/api'; import { Paginator } from '~/tsky/paginator'; +import type { XrpcClient } from '~/tsky/xrpc'; export class Feed { - constructor(private instance: AppBskyNS) {} + constructor(private client: XrpcClient) {} /** * Get a hydrated feed from an actor's selected feed generator. Implemented by App View. @@ -21,10 +22,10 @@ export class Feed { options?: AppBskyFeedGetFeed.CallOptions, ) { return new Paginator(async (cursor) => { - const res = await this.instance.feed.getFeed( - { cursor, ...params }, - options, - ); + const res = await this.client.request('app.bsky.feed.getFeed', 'GET', { + cursor, + ...params, + }); return res.data; }); @@ -38,10 +39,10 @@ export class Feed { options?: AppBskyFeedGetTimeline.CallOptions, ) { return new Paginator(async (cursor) => { - const res = await this.instance.feed.getTimeline( - { cursor, ...params }, - options, - ); + const res = await this.client.request('app.bsky.feed.getTimeline', 'GET', { + cursor, + ...params, + }); return res.data; }); diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 15060fb..6a0bdcd 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -29,4 +29,24 @@ describe('tSky', () => { expect(profile).toBeDefined(); expect(profile).toHaveProperty('handle', TEST_CREDENTIALS.alice.handle); }); + + describe('feed', () => { + it('.timeline()', async () => { + const session = new PasswordSession(TEST_ENDPOINT); + await session.login(TEST_CREDENTIALS.alice.did, TEST_CREDENTIALS.alice.appPassword); + + const tSky = new TSky(session); + + const paginator = tSky.feed.timeline({ + limit: 30, + }); + + await paginator.next(); + + expect(paginator).toBeDefined(); + expect(paginator.values).toBeDefined(); + expect(paginator.values).toBeInstanceOf(Array); + expect(paginator.values).toHaveLength(0); + }); + }); }); diff --git a/packages/core/src/tsky/tsky.ts b/packages/core/src/tsky/tsky.ts index 226b9c4..29ebde2 100644 --- a/packages/core/src/tsky/tsky.ts +++ b/packages/core/src/tsky/tsky.ts @@ -1,10 +1,8 @@ import type { - AppBskyActorDefs, - AppBskyActorGetProfile, - AppBskyActorGetProfiles, AppBskyActorSearchActors, AppBskyActorSearchActorsTypeahead, } from '@atproto/api'; +import { Feed } from '~/bsky'; import { Paginator } from './paginator'; import type { Session } from './session'; import { XrpcClient } from './xrpc'; @@ -19,31 +17,7 @@ export class TSky { /** * Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth. */ - profile( - identifier: string, - options?: AppBskyActorGetProfile.CallOptions, - ): Promise; - /** - * Get detailed profile views of multiple actors. - */ - profile( - identifiers: string[], - options?: AppBskyActorGetProfiles.CallOptions, - ): Promise; - - async profile( - identifier: string | string[], - options?: - | AppBskyActorGetProfile.CallOptions - | AppBskyActorGetProfiles.CallOptions, - ) { - if (Array.isArray(identifier)) { - const profiles = await Promise.all( - identifier.map((i) => this.profile(i, options)), - ); - return profiles; - } - + async profile(identifier: string | string[]) { const res = await this.xrpc.request('app.bsky.actor.getProfile', 'GET', { actor: identifier, }); @@ -51,6 +25,10 @@ export class TSky { return res.data; } + get feed() { + return new Feed(this.xrpc); + } + /** * Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth. */ diff --git a/packages/core/src/tsky/xrpc.ts b/packages/core/src/tsky/xrpc.ts index a3b9a82..aa3bc7d 100644 --- a/packages/core/src/tsky/xrpc.ts +++ b/packages/core/src/tsky/xrpc.ts @@ -64,15 +64,15 @@ export class XrpcClient { this.session = session; } - async request( + async request

, R = Record>( nsid: string, - method = 'GET', - params?: Record, + method: 'GET' | 'POST' = 'GET', + params?: P, ): Promise<{ - data: T; + data: R; headers: Record; }> { - const searchParams = new URLSearchParams(params); + const searchParams = new URLSearchParams(params ?? []); const response = await this.session.fetchHandler( `/xrpc/${nsid}?${searchParams.toString()}`, From 0acd74a41360688c5ab43cf89a93dac3dc592ced Mon Sep 17 00:00:00 2001 From: Anbraten Date: Sat, 30 Nov 2024 14:29:54 +0100 Subject: [PATCH 9/9] fix tests --- packages/core/src/bsky/feed.ts | 2 +- packages/core/src/index.test.ts | 4 +++- packages/core/src/tsky/xrpc.ts | 20 +++++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/core/src/bsky/feed.ts b/packages/core/src/bsky/feed.ts index 112576e..718d4aa 100644 --- a/packages/core/src/bsky/feed.ts +++ b/packages/core/src/bsky/feed.ts @@ -39,7 +39,7 @@ export class Feed { options?: AppBskyFeedGetTimeline.CallOptions, ) { return new Paginator(async (cursor) => { - const res = await this.client.request('app.bsky.feed.getTimeline', 'GET', { + const res = await this.client.request('app.bsky.feed.getTimeline', 'GET', { cursor, ...params, }); diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 6a0bdcd..89014b0 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -46,7 +46,9 @@ describe('tSky', () => { expect(paginator).toBeDefined(); expect(paginator.values).toBeDefined(); expect(paginator.values).toBeInstanceOf(Array); - expect(paginator.values).toHaveLength(0); + expect(paginator.values.length).toBe(1); // we should get the first page from the paginator + expect(paginator.values[0].feed.length).toBeGreaterThan(0); // alice has some posts ;) + expect(paginator.values[0].feed[0]).toHaveProperty('post'); }); }); }); diff --git a/packages/core/src/tsky/xrpc.ts b/packages/core/src/tsky/xrpc.ts index aa3bc7d..39ac315 100644 --- a/packages/core/src/tsky/xrpc.ts +++ b/packages/core/src/tsky/xrpc.ts @@ -49,12 +49,18 @@ export class XrpcError extends Error { return new XrpcError(response.status, data.error, data.message); } - return new XrpcError( - response.status, - response.statusText, - response.statusText, - ); + return new XrpcError(response.status, response.statusText); + } +} + +function dropEmptyValues>(obj: T): T { + const _obj = { ...obj }; + for (const key of Object.keys(_obj)) { + if (_obj[key] === undefined) { + delete _obj[key]; + } } + return _obj; } export class XrpcClient { @@ -64,7 +70,7 @@ export class XrpcClient { this.session = session; } - async request

, R = Record>( + async request

, R = unknown>( nsid: string, method: 'GET' | 'POST' = 'GET', params?: P, @@ -72,7 +78,7 @@ export class XrpcClient { data: R; headers: Record; }> { - const searchParams = new URLSearchParams(params ?? []); + const searchParams = new URLSearchParams(dropEmptyValues(params ?? {})); const response = await this.session.fetchHandler( `/xrpc/${nsid}?${searchParams.toString()}`,